Development record
Grounds accumulate as an append-only section, in one shared form
Context
itd-179 records the conjecture behind a decision. It grew two writers. The
intent half wrote an append-only ## Grounds section and argued for it in
terms: the earlier conjecture is precisely what a later reader checks the
outcome against, so rewriting it would leave the record saying only what was
believed last. The ledger half wrote a single grounds: frontmatter scalar and
set it, so promote recorded a conjecture and a later resolve or wontfix
destroyed it. Nothing warned; the result reported success.
The obvious remedy — refuse a transition that would overwrite a non-empty
grounds — was checked and does not work. promote, resolve and wontfix
all REQUIRE --grounds, so refusing the overwrite makes every promoted issue
impossible to resolve. Fourteen records in resolved/ already carry
promoted_to (historical).
So the two halves held opposite rules for one concept, and the ledger's rule was the one that lost data.
Decision
Grounds accumulate. One entry per grounds-bearing act, appended, never replaced — and in ONE form across both halves.
- The ledger's grounds move from a frontmatter scalar to a
## Groundsbody section, the shape the intent half already used. - The reading and writing primitive lives once, in
core/mdrecord(masking, section bounds, bullet blocks, link-reference peel) with the vocabulary and section operations incore/grounds.core/intentandcore/captureboth consume it. Neither owns the other's parser. - One asymmetry between the halves is real and is preserved explicitly rather
than flattened: the intent reader applies the substance floor because its
gate claims it, and the ledger reader stops at the grammar because a
wontfix's grounds derive from a reason whose contract is only non-emptiness.
That is expressed as
ParseSectionAboveFloorlayered ON TOP OFParseSection, so what an entry IS stays one definition and only the admission bar differs.
Alternatives Considered
Refuse the overwrite. The reviewer's proposal, and the reason this ADR exists rather than a one-line fix: it deadlocks the ledger's mainline sequence, because every route into a terminal folder requires grounds.
Last-write-wins, documented. Smallest change, and honest — the loss becomes a stated tradeoff rather than an accident. Rejected because the ledger would then record the opposite rule to the intent half for the same concept, and the thing lost is exactly what the intent half argues a later reader most needs.
An append-only list in frontmatter. Keeps the data where the ledger reader already looks. Rejected as a second shape for one concept: it would differ from the intent half's bullets, so the repository would hold two definitions of how grounds accumulate, which is what one-canonical-primitive forbids.
Consequences
- Sixteen committed records carrying the frontmatter scalar were migrated through the real writer, so their shape is byte-identical to what the verbs produce. Expensive to reverse: that is half of why this is an ADR.
- The retired
groundskey stays in the issue schema's known set rather than being refused. A refusal makes capture SKIP the record, and a record invisible to every surface while it sits in the ledger is worse than a key nothing reads;record_schemablocks the misplacement and names the section instead. - Coverage moves, and not only forward. A malformed
grounds:VALUE was something the schema gate could judge; a malformed bullet under## Groundsreads as prose, so nothing is wrong to any gate and nothing says so (iss-2608301747001641). That is the price of the shared form and it is recorded rather than absorbed. Issue.Groundsbecomes a list in the JSON envelope. Unreleased field on an unreleased branch, so nothing external moves.core/mdrecordis a new package in the core DAG, which is the other half of why this is an ADR: a future session asking why the two halves do not each keep their own reader would otherwise re-derive the whole argument.
Frontmatter
| id | adr-57 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-08-30 |
.abcd/development/decisions/adrs/0057-grounds-accumulate-as-an-append-only.md
open on GitHub ↗ · commit history ↗
References1
- itd-179The reasoning behind what was pursued no longer evaporates at the gate — readiness and triage record grounds for the conjecture, not only the decisionrelated
mentions5
- dcn-26090200000000112026-09-02 — adr-56's third rule is refined, not contradicted, by the source-and-tests ruling (maintainer): a control that admits an input it cannot examine may pass it only with a per-item unscanned mark in its attestation, so the second rule (an attestation never says more than the examination) carries what the third rule carried; refusal stays the rule for an input the control claims to have examined and could not. Invariant 16 amended to say so. Every ADR is now checked against the framework v4 and the companion v4: adr-58 is superseded by adr-2609021016286571, adr-2609021016272867 is corrected to the derived run, and none of adr-55, adr-57, adr-2609021016275803, adr-2609021016270132, adr-2609021016288378 or adr-2609021016286571 contradicts either document. The two-hundred-thousand-token target is a clarification the documents do not speak to, kept as such.
- dcn-26092300000000162026-09-23 — itd-4 AC3 built as ruled (B3): the promote join's back-links are `related_issues` on the intent and `related_intents` on the ledger record, and `abcd intent audit --issue-drift` checks them. Seven implementation decisions the ruling left open, taken by the implementing lane. (1) No reader tolerates the retired `promoted_to` / `promoted_from`: the ledger reader refuses and skips a record carrying `promoted_to`, the intent reader ignores `promoted_from`, and a promote write meeting either refuses. The refusal, the record gate's finding and the drift finding each name the successor and the remedy, `abcd capture migrate --apply`, a verb that reports by default, writes only with `--apply`, and is idempotent. It is a verb and not a one-off script because managed repositories hold the same retired names and need the same repair. (2) "Promoted" is the PAIR, an intent in the record's `related_intents` that names the record back in its `related_issues`, because an issue's `related_intents` already carried loose relations before the rename and still does; the double-promote refusal and the record page's next move read the pair. So the migration completes a join an older abcd wrote from one end only (iss-327 and itd-93, iss-2609091642508005 and itd-2609111003026787), or those records would stop reading as promoted. (3) Link mode on the issue route writes both halves; before the rename it stamped the issue alone, which left a hand-filed draft with no edge back. (4) `related_issues` holds `rdi-N` as well as `iss-N`: a reading item lives in the issue ledger's tree and graduates the same way, and one key per side keeps the join one shape. The first entry is the record the intent was promoted from; a later link appends, so `back_edge_kept` still reports the first. (5) The drift check reports an intent naming a record that does not name it back, but not an ISSUE naming an intent that does not name it back, since that is a loose relation; a reading item carries no loose relation, so from its end a one-way stamp is drift. A shipped intent naming an issue in `wontfix/` is drift, like one in `open/`, because the itd-4 scope asks whether the issue "actually moved to resolved/". The check is not wired into preflight or CI; `--strict` is there for whoever wires it. (6) The historical passages in shipped intents, closed specs and adr-0057 carry a `(historical)` marker beside the retired names, the precedent `development/activity` set, so the new `retired-promote-back-links` ban can gate every current record. (7) The impact is `breaking`: itd-4 declares none, and the rename removes `promoted_to` from `capture list --json`, `promoted_from` from the intent JSON and both link keys from the record page, and makes an unmigrated ledger unreadable until the migration runs.
- iss-2608301747001641a malformed grounds bullet on an issue record reads as prose so no gate can review it losing coverage the frontmatter rule had
- iss-2608301803425790five nits from the itd-179 delta review including two exported functions with no caller and a renamed citation that now names the wrong predicate
- iss-2608301805069999the grounds writer judges the whole file while the reader judges the body so a frontmatter Grounds comment makes triage report success and record nothing