Development record
The issue ledger is working-tier data, not authored record
Context
The capture ledger (itd-4) writes schema-checked iss-N files with folder-as-status
(open/, resolved/, wontfix/). It was sited at .abcd/development/activity/issues/ (historical) —
inside the durable design-record tree that record-lint governs.
Dogfooding surfaced a shipped-vs-shipped contradiction: capture's issue schema requires
a created: field, while record-lint's no_git_metadata rule bans created: anywhere
under .abcd/development (git log/blame is canonical). Running capture therefore broke
record-lint — two shipped components mutually incompatible, the moment the tool is used on
its own repo. The finding is itself in the ledger as iss-15.
The root cause is a category error: the ledger is generated, schema-governed data that opens and resolves, but it was living in the tree meant for authored, git-canonical prose.
Decision
- Tier. The issue ledger lives in the work tier —
.abcd/work/issues/— not the design record. It is committed, shared working state (folder-as-status per adr-3), not durable design record. This takes it out ofrecord-lint's root and dissolves the conflict at its source.
- No git-inferable timestamps.
createdandupdatedare dropped from the schema; git is the canonical source of time (consistent withno_git_metadataand the derive-don't-store principle). The reader tolerates legacycreated/updatedon read (accept, then drop) so an older ledger degrades gracefully rather than being rejected.
- Priority is derived, never stored. Issues carry a one-direction
blocked_by: [iss-N]dependency edge; the inverse and the ranking are computed.capture list/status order unblocked-first, then by severity, annotating blocked rows[blocked-by iss-N]. This reuses the spec engine's dependency-graph model (adr-26, the run-seam selector) rather than inventing a parallel priority label.
- README contracts by category. Per-folder READMEs stay for the authored record — they
are local membership contracts. A generated-data store gets one README at its root; its
state leaves are self-evident. The tier boundary keeps
directory_coverageaimed only at prose, so the lint needs no change.
Alternatives Considered
- Exempt
.abcd/development/activity/(historical) inrecord-lint. The first-pass fix, briefly recommended and then reversed. Rejected as symptom suppression: it carves the lint around a category error rather than fixing it, and leaves the ledger in the wrong tier. The reversal is the methodology-over-local-fixes principle in action — recorded so the reasoning is not re-litigated. - A stored
priorityfield. Rejected: priority is volatile and contextual; a stored label is the hand-maintained-status drift the roadmap dashboard and adr-5 already avoid. - Blanket-exempt all of
.abcd/from READMEs. Rejected: it discards the local contracts the authored record relies on, to fix a problem that only exists for generated data. - Keep the timestamps as portable data. Rejected: the ledger is read in-repo with git present; git derives creation and the lifecycle moves, and the store is not bundled in the release artefact.
Consequences
- The
capture⊥record-lintconflict is gone at the root; runningcaptureno longer breaks the gate. - Design-record references to the old path (e.g.
build-sequence.md) are now drift to reconcile, tracked as ledger issues. - Future generated stores (logbook, lifeboat) inherit the rule: working/generated data lives outside the record tier and carries no git-inferable metadata.
- A self-hosting datapoint: abcd's first real contradiction was found by running abcd on abcd
(
iss-15) — the dogfooding thesis paying out in practice. - ADR-12 is superseded. It settled where the structured ledger would live by deferring the migration to a path inside the design-record tier; this ADR redecides the same question and sites the ledger in the working tier instead, so the two cannot both be in force.
Frontmatter
| id | adr-32 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-07-08 |
.abcd/development/decisions/adrs/0032-issue-ledger-is-working-tier-data.md
open on GitHub ↗ · commit history ↗
References7
- adr-3Directory location is the source of truth for lifecycle staterelated
- adr-5Brief is the current state; no version label, no archive directoryrelated
- adr-12`.work/issues.md` stays the live operational ledger; the structured per-issue store is deferred until the native spec layer schedules the migration (historical)supersedes
- adr-26A native minimal spec layer with the companion harness `ccpm` as the primary deeper backendrelated
- adr-30Design-record information architecture — flat artefact-type foldersrelated
- itd-4Nothing You Notice Gets Lostrelated
- itd-36Knowledge That Compounds, Not Knowledge That Re-Derivesrelated
Referenced by1
- adr-47abcdev.app is rendered from this repository alonerelated
mentions13
- dcn-26080200000000042026-08-02 — iss-39: record-lint gains `record_schema`, the first rule that reasons ACROSS the record's stores (ADRs, intents, specs, the issue ledger) rather than inside one, and the `superseded/` exemption narrows for the intent tree: both rules that read it (`intent_lifecycle`, `intent_impact_valid`) now run there too, while the spec-store rules (`spec_lifecycle`, `spec_id_unique`) still honour it as before and the cross-store `record_schema` never consulted it at all. Four invariants, one scan: a cross-reference field names a record the corpus has; a supersession is declared from BOTH sides; a filename and the id inside it agree; and every lifecycle directory is enumerated, so an undeclared bucket is a finding rather than a state no rule reads. Scope is deliberate on two axes. (1) Cross-references are checked in the machine-readable frontmatter fields (`supersedes`, `related_adrs`, `related_intents`, `builds_on`, `blocked_by`), not in bare prose: a frontmatter handle is a claim that the record exists and is a live input, whereas prose legitimately narrates ids that do not resolve ("itd-38's id was released, not reserved"; a test fixture's `itd-9999`; adr-29's account of what ADR-6 settled) — arming prose would have cost ten false positives against one true one, the same false-positive budget that scopes GL002. (2) A handle resolves either to a file or to a `supersedes:` declaration elsewhere, because the ADR lifecycle PRUNES a superseded record and keeps the trace in its successor — that is what distinguishes adr-4/adr-8/adr-14..18 (pruned, accounted for) from adr-6 (cited, never accounted for). The exemption narrowing is the root cause of the corpus defect it exposes: `intent_lifecycle` is a schema rule, but the `superseded/` bucket exempted its own records from the one rule that checks a supersession is recorded, so itd-47 and itd-49 sat there for months with a prose blockquote and no `superseded_by` field. Being historical excuses a record from how it is WRITTEN, never from being well-formed. `superseded_by` also widens from `^itd-\d+` to `^(itd|adr)-\d+`: an ADR that redecides the question an intent rested on retires that intent as surely as a successor intent does, and refusing to spell that forced those two records to say nothing at all. Corpus repairs in the same change: adr-6 is dropped from the two `related_adrs` lists and the two brief pages that cited it re-point at adr-29 (which records the two-stage redaction model and states in its own body that adr-6's decision stands — adr-29 does not supersede it, so claiming it does would have written a falsehood to clear a finding); adr-12 and adr-32 gain the bidirectional pair the ledger-location redecision always implied, adr-12's status moving to `superseded` and the index row with it; itd-47/itd-49 gain `superseded_by: adr-22`/`adr-26` with `kind_at_supersession`, and those ADRs name them back; and the pre-existing one-way links itd-32→itd-31 and itd-31→itd-48 gain their reverse halves. CHANGELOG impact `additive`, matching iss-38's disposition on the same axis: the rule ships in the lint engine `abcd docs lint` runs, and any abcd-managed repo gains it by declaring its own `record_stores`.
- dcn-26080200000000062026-08-02 — iss-39 (merge-gate pass): four more `record_schema` corrections, all of the same family as the previous round — a check that was right for one shape and absent for its sibling. (1) The undeclared-directory finding was implemented for store ROOTS only; a directory inside a declared bucket (`intents/shipped/archive/`) was skipped with no finding at all, so the previous round's flat-store fix closed the escape for one store of four. Both cases are now reported from the one place that reads a directory of records, because a flat store and a declared bucket hold records the same way and a directory in either is a lifecycle nobody declared. (2) `spc` was missing from the handle pattern although the spec store is indexed and armed: a bidirectional spec supersession tripped a false blocker whose message named the very prefixes it omitted, while the reverse direction parsed to zero handles and was never checked. The alternation now covers every store the rule indexes, and the message is composed from one constant so it cannot drift from the pattern again. (3) `superseded_by: []` was a false blocker: the empty flow sequence is this record's house spelling for an empty list (`related_rfcs: []` across six ADRs), so it means absence. Fixed with a local `isAbsentValue` rather than by widening the shared `isNull`, which also judges scalar fields (kind, impact, slug) where a list literal is a wrong value rather than an unset one. (4) The filename/id check compared strings, so `id: adr-0012` in `0012-*.md` was reported as a mismatch — contradicting the rule's own stated design that the padded and bare spellings are one handle; it now parses both sides and compares numerically, as every other comparison in the rule already did. Record-side: iss-179 now cites the recoverable evidence for what adr-6 was (`.abcd/work/reviews/2026-07-06-plan-consistency/05-link-structure.md` lines 47-49 name the original file, `03-cross-corpus-consistency.md` line 42 records it as voided by DECISIONS.md lines 8-9 together with adr-8 and adr-17 — the only other two ADRs that decision voids, both since pruned by a successor that names them (adr-25, adr-22), which is what leaves adr-6 alone — and observes that the unused `deprecated` status is the state it fits) so the disposition is a decision with evidence rather than an open question; the two brief re-pointings drop a reflexive attribution that had adr-29 reusing a model from itself; and adr-22, adr-26, and adr-32 gain one body clause each naming what their newly-declared `supersedes` handles retire, so the frontmatter and the prose say the same thing.
- dcn-26082000000000012026-08-20 — bughunt round 2 (branch bughunt-a/round-2): captured iss-331..iss-343 and fixed (ids 339-343 re-minted from an initial 326-330 after main concurrently took those numbers in the v0.6.1 cut)
- iss-15capture wrote a schema-required created field into the record-lint root, so running capture broke record-lint. This drove moving the ledger to work/issues and dropping created/updated. Fixed by refactor/capture-ledger-schema-tier.
- iss-39record schema validation: adr-6 is cited as a live design input but does not exist and no successor records superseding it; adr-12 is not marked superseded by adr-32; itd-47 and itd-49 sit in superseded/ without supersession frontmatter; the superseded/ lint exemption is broad enough to hide content-rule violations. Detector: a mechanical record-schema check — every prose handle (adr-N, itd-N, iss-N) resolves to a file; supersession is bidirectional (superseded-by and supersedes both present); filenames match their patterns; every lifecycle directory is covered by a catch-all so no state escapes linting; the superseded/ exemption narrows to content rules only. Acceptance corpus: the four instances above.
- iss-317development README conventions sentence says issues graduate rather than a ledger contradicting accepted adr-32 and the live work-tier ledger
- iss-332.abcd/development/README.md conventions line says issues graduate into intents/ or principles/ rather than a ledger, but the repo runs on a committed issue ledger at .abcd/work/issues/ (adr-32); the map of the durable record denies the store it governs, in the file iss-42 ruled must be current
- iss-356round-2 fixed nitpick batch: guard check help omits the disclosed backtick limit; urlguard misses CGNAT 100.64/10 and other RFC 6890 ranges; readHookInput misreports an over-cap payload as malformed (iss-201's class at the shared helper); repolint privacy-hygiene silently skips textual files over the 4 MiB cap; capture reCommitSha refuses 64-hex commits; launch Short hides that --dry-run is required; release.yml verify-tag comment overstates what gh verifies; release.yml header says dispatch runs only the rehearsal job; dependabot covers no pip ecosystem for docs; brief pointer lines enumerate five of nine internals chapters; phase-6 and 06-lint carry change-narration blocks; 05-internals cites a ledger entry that does not exist; 03-evidence names the retired activity record nitpick documentation
- iss-2608211432483805the .abcd/work tier roster in AGENTS.md and .abcd/README.md names only CONTEXT.md and DECISIONS.md, omitting the committed issue ledger (adr-32), reviews, and rulesets that also live there
- itd-139Carol's team adopts abcd and gets a readable record the next day, without writing a line of site code
- itd-140The Generic/Specific Boundary of the Site Generator Is a Gate, Not a Hope
- spc-38alice-opens-the-record-explorer-and-reads-the-whole-developm
- spc-2610110931381316append-only-logs-conflict-on-every-merge