Development record
dangling-supersedes-and-spec-targets-nothing-checks-them
Summary
Arms a red-gate detector for dangling typed cross-references — a supersedes
naming a record absent from the tree, or a spec_id naming a spc-N with no
file — ratcheted through .abcd/site-baseline.json so the existing eight-entry
backlog is baselined and only new dangles fail. The reference graph and the
baseline machinery already exist in the site build; this spec is the design that
wires them into the gate, seeds the backlog, and proves the four ratchet
behaviours.
Scope
In:
internal/core/site/recordjson.go—measureHealth(line 416), which walksgraph.Danglingand counts unresolved refs includingsupersedesand thespec_idgraph field.internal/core/site/check.go—checkBaseline(line 1369), the ratchet red gate that fails a live unresolved ref outside the baseline and passes a baselined one..abcd/site-baseline.json— the seededunresolved_referencesbacklog (the eight entries: adr-22→adr-14/15/17, adr-25→adr-8, adr-27→adr-16, adr-28→adr-18, adr-35→adr-4, itd-3→spc-1).- Tests in
internal/core/site/check_test.goandhealth_test.go.
Out:
- No decision on how tombstones/stubs render (itd-136/itd-137 owns that); this spec gates the dangle, it does not add a target page for it.
- No change to the reference parser itself (
internal/core/lint/graph.go,schema.go) beyond consuming what it already emits.
Approach
The reference graph already distinguishes dangles. LoadRecordGraph
(lint/graph.go:87) reuses the record_schema scan; the parsed reference fields
(schema.go:79–91) include supersedes/superseded_by (handle fields) and
spec_id/intent (graph fields). The graph emits absent-target refs as
g.Dangling and retired ids as g.Retired (graph.go:101–149).
Count the dangle, not-excusing supersedes. measureHealth
(recordjson.go:416) walks graph.Dangling into Health.Unresolved. The
load-bearing line is recordjson.go:429: a retired target is excused except for
supersedes (if e.Field != "supersedes" && retired[e.To] { continue }) — so a
supersedes naming an absent id counts even when that id is retired, and a
spec_id naming an absent spc-N counts as a graph-field dangle. This is the
detector the intent asks for; the design confirms both field kinds reach
Health.Unresolved.
Ratchet as the red gate. checkBaseline (check.go:1369) reads
Health.Unresolved against .abcd/site-baseline.json (Baseline/BaselineEntry
in site/paths.go:27–36, loaded by LoadBaseline, path
BaselineRelPath=".abcd/site-baseline.json"). Direction: a live unresolved ref
not in the baseline is c.fail(...) — a red gate (check.go:1401–1408); a
baseline entry whose ref now resolves is a shrink invitation (c.note), never a
failure (check.go:1413–1421). This is exactly the ratchet semantics the intent
specifies. The eight backlog entries are seeded into the baseline so they do not
newly fail; a ninth, new dangle fails red.
How it satisfies each acceptance criterion
- A new
supersedesnaming an absent record fails as a red gate —measureHealthcounts the supersedes dangle (recordjson.go:429 does not excuse it), andcheckBaselinefails it because it is not in the baseline. Test (check_test.go): a record introducingsupersedes: adr-999(no such file) produces a red gate. - A
spec_idnaming aspc-Nwith no file fails as a red gate — thespec_idgraph-field dangle reachesHealth.Unresolvedand fails the same way. Test: an intent whosespec_idnames a missing spec fails red. - The existing backlog is baselined and does not newly fail — the eight entries
seeded in
.abcd/site-baseline.json. Test: with the seeded baseline, the build passes; assert the eight known dangles are matched by baseline entries, not reported as failures. - A baselined dangle whose target is later added still passes —
checkBaselinetreats a now-resolving baseline entry as a shrinkc.note, not a failure (check.go:1413–1421). Test: add the target for a baselined entry and assert the gate still passes (and invites the baseline to shrink).
Decisions
Gate at the site build via the existing ratchet, not at record-lint: the intent
says the planned site build arms the detector via the baseline, and the
reference graph plus checkBaseline already live there, so seeding the backlog
and confirming both field kinds reach the gate is the whole delivery. The
tombstones-or-stubs rendering question is deliberately deferred to itd-136/itd-137
— this spec makes the dangle fail, and leaves how a resolved target renders to
that decision.
Frontmatter
| id | spc-52 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-52-dangling-supersedes-and-spec-targets.md
open on GitHub ↗ · commit history ↗
References1
- itd-160Eight typed cross-references point at targets absent from the tree — adr-22 supersedes adr-14, adr-15 and adr-17; adr-25 supersedes adr-8; adr-27 supersedes adr-16; adr-28 supersedes adr-18; adr-35 supersedes adr-4 (all retired under retire-the-name); itd-3 names spec_id spc-1 which has no file — and nothing checks supersedes targets today. The 2026-08-21 site investigation counted six; the in-session grep found eight. The planned site build arms the detector via the .abcd/site-baseline.json ratchet, seeded with what the build finds, and the tombstones-or-stubs question (itd-136/itd-137) decides how they renderimplements
mentions11
- adr-22Bundled dependencies become pluggable adapters over a native default
- adr-25The LLM is host-delegated by default; oracles are opt-in adapters
- adr-27The autonomous run is a pluggable seam, not a Ralph port
- adr-28One repository, a curated release artifact — no dev→public mirror
- adr-35The lifeboat is a coverage experiment — read-only, out-of-tree, and proven before it is packed (supersedes adr-4)
- iss-2609261536147903Planned intents and disciplines still cite specs of the retired predecessor store unqualified, and such an id at or below spc-70 resolves to a live spec on another subject (spc-12, for one, is live as the disembark grounding spec): itd-48, itd-50 and itd-53 name spc-12, spc-28, spc-29, spc-31, spc-33, spc-43 and spc-52 for the fidelity reviewer, lifecycle hook and cleanup work (which of these are predecessor ids is itself the reading this record owes); itd-24 names spc-66 as the phase-audit receipt; itd-1 and itd-37 name spc-12 as the manual reviewer. The specs charter (Two spc-N Namespaces) rules that every predecessor citation carries the '(predecessor store)' qualifier; iss-239 applied it to the draft corpus only. Each site needs a reading to tell a predecessor citation from a live one, which is why the sweep was not folded into iss-239.
- iss-2609290448510918The predecessor-store qualifier sweep reaches past the six intents iss-2609261536147903 named: outside drafts/ and those six, 192 citation sites across about forty intents name a spc-N at or below spc-70 that is not the citing intent's own spec, with no '(predecessor store)' on the line. Some are live cross-references (the cold-reading family's intents cite each other's live specs), and some are the predecessor store's (itd-4, itd-6, itd-29, itd-47 and itd-49 describe pre-rebuild work in its terms), so each site needs the same reading against the live spec it collides with; the specs charter's Two spc-N Namespaces section is the rule. Found while sweeping the pattern of iss-2609261536147903 in lane drainDrift3; a census script over intents/{planned,shipped,disciplines} reproduces the count.
- itd-3abcd Loads Its Own Rules On Demand
- itd-50The Audit Loop Drives An Intent To Acceptance — Or Calls For A Replan
- itd-136Alice opens the record explorer and reads the whole development record — every decision, intent, spec and issue as a page, with contributors and references — without reading YAML on GitHub
- itd-137Maya sees how every decision connects — one relationship chart with a date coil and a links-only layout, and a five-lane genealogy of the record over time