Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
4596c00
docs(intents): qualify the predecessor-store spec ids five intents cite
REPPL Sep 30, 2026
542737b
docs(intents): correct itd-36 and itd-4 on where their predecessor sp…
REPPL Sep 30, 2026
3d18845
chore: resolve iss-2609300025372991 — itd-36 and itd-4 name their liv…
REPPL Sep 30, 2026
d381395
docs(intents): qualify the predecessor-store spec ids six superseded …
REPPL Sep 30, 2026
8f5b726
docs(intents): correct itd-47 on where its predecessor specs stand
REPPL Sep 30, 2026
855ebc9
chore: resolve iss-2609300032219282 — itd-47 names no spec_id it does…
REPPL Sep 30, 2026
058c392
chore(issues): record the predecessor-qualifier sweep's progress
REPPL Sep 30, 2026
4c2c163
refactor(changelog): unexport LatestVersionIn and MaxImpact
REPPL Sep 30, 2026
0963b1a
refactor(glossary,grounds,identity): unexport in-package-only functions
REPPL Sep 30, 2026
83e558c
refactor(lifeboat): unexport four in-package-only functions
REPPL Sep 30, 2026
142ee51
refactor(cite,machineload): unexport in-package-only functions
REPPL Sep 30, 2026
dfa9e98
refactor(memory): unexport eleven in-package-only functions
REPPL Sep 30, 2026
7c3c055
refactor(mode): name QuestionOpen a declared test seam
REPPL Sep 30, 2026
eb26346
refactor(reading): unexport fifteen in-package-only functions
REPPL Sep 30, 2026
881d346
refactor(statusline): unexport six in-package-only functions
REPPL Sep 30, 2026
5eeca96
refactor(reviews): unexport Pin and Read
REPPL Sep 30, 2026
b027269
refactor(readingitem,scribe,source,spec): unexport in-package-only fu…
REPPL Sep 30, 2026
89d2d26
chore(issues): record the 2026-09-30 sort on iss-2609252211487887
REPPL Sep 30, 2026
112bb49
chore: capture iss-2609300109005165, a lock descriptor inherited acro…
REPPL Sep 30, 2026
38a442f
fix(fsutil): open the WithFileLock descriptor close-on-exec
REPPL Sep 30, 2026
0ff7edd
refactor(fsutil): add WithDirLock, the directory-lock primitive
REPPL Sep 30, 2026
d700302
refactor(decide): take the mint lock through fsutil.WithDirLock
REPPL Sep 30, 2026
92df0c3
refactor(spec): take the store lock through fsutil.WithDirLock
REPPL Sep 30, 2026
c60b424
refactor(intent): take the mint lock through fsutil.WithDirLock
REPPL Sep 30, 2026
f5ecac5
refactor(memory): take the store lock through fsutil.WithFileLock
REPPL Sep 30, 2026
6cb1d0c
refactor(history): take the records lock through fsutil.WithFileLock
REPPL Sep 30, 2026
21e78bc
test(fsutil): refuse a flock call outside internal/fsutil
REPPL Sep 30, 2026
7ba5363
chore: resolve iss-2609300109005165 — lock descriptors are close-on-exec
REPPL Sep 30, 2026
52fa5b9
chore: resolve iss-129 — every flock goes through fsutil's two lock p…
REPPL Sep 30, 2026
2a96c46
merge: bring main (#756, integ23) into flockSites
REPPL Sep 30, 2026
6daedeb
merge: bring main (#756) under fidelityOnce
REPPL Sep 30, 2026
1ed950b
feat(build): the validate stage, with the fidelity audit once on the …
REPPL Sep 30, 2026
4b5162b
chore(spec): mark spc-2609202134338445 piece 8 landed
REPPL Sep 30, 2026
c5b4305
feat(build): an undecided audit reopens the work, and fix rounds are …
REPPL Sep 30, 2026
f2c11d3
fix(drainrule): refuse a decision store reached through a symlink
REPPL Sep 30, 2026
9b1c17e
chore: relink intent edges that name superseded records
REPPL Sep 30, 2026
b34a5d7
chore: capture the runner and loop builds_on cycle
REPPL Sep 30, 2026
4bc37c6
fix(drain): hand back a deferral past a release tag the checkout lacks
REPPL Sep 30, 2026
76f3320
chore: resolve iss-2609300841407812 and iss-2609300841466575 — drain …
REPPL Sep 30, 2026
3d60073
feat(scanner): run a repository's opt-in gitleaks in every scan
REPPL Sep 30, 2026
195a1f6
fix(term): judge a terminal by the kernel's termios answer, not the d…
REPPL Sep 30, 2026
c3bed2b
chore: resolve iss-2608291814575788 — gitleaks reaches every scanner …
REPPL Sep 30, 2026
2b61fe0
chore: capture the dependency-edge lint gap and two more builds_on cy…
REPPL Sep 30, 2026
8ede481
feat(build): the landing and the run record of the implement loop
REPPL Sep 30, 2026
8771f0c
feat(lint): record-lint flags a stale dependency edge and an edge cycle
REPPL Sep 30, 2026
1411cac
chore: resolve iss-2609300903497125 — record-lint checks dependency e…
REPPL Sep 30, 2026
a1f5ed7
chore(spec): mark spc-2609202134338445 pieces 9 and 10 landed
REPPL Sep 30, 2026
ed96918
fix(ahoy): a program inside the checkout does not count as installed
REPPL Sep 30, 2026
d436894
chore: resolve iss-2609300905174086 — consent gates no longer judge /…
REPPL Sep 30, 2026
33067b3
chore: resolve iss-2609300905225841 — a program inside the checkout i…
REPPL Sep 30, 2026
a492530
fix(build): keep the local tier out of the landing records with a reset
REPPL Sep 30, 2026
978a00c
test(cli): wire the gitleaks augmenter in the drain-notice home test
REPPL Sep 30, 2026
51b80fa
test(update): stand /dev/null in for a terminal through an isTTY seam
REPPL Sep 30, 2026
532b8ac
fix(guard): bound a taught lesson, say when the guard is off, name wh…
REPPL Sep 30, 2026
2cde160
chore: resolve the three guard-teaching review follow-ups
REPPL Sep 30, 2026
d9dd8d1
refactor(build): unexport the record builder the reach audit names
REPPL Sep 30, 2026
403f2de
merge: bring anchorStale (drainOwnRule + o2) into drainLoop
REPPL Sep 30, 2026
4de49e2
fix(oracle): skip a repository route with a malformed name or value
REPPL Sep 30, 2026
53e7502
chore: resolve iss-2609300929557796 — a malformed repository route is…
REPPL Sep 30, 2026
31c2f93
fix(lint): resolve a record's reverses link as a cross-reference field
REPPL Sep 30, 2026
ccf5834
docs(adr): state the promote route's match lock exactly
REPPL Sep 30, 2026
6d3c932
refactor(capture): drop the dead run-directory check in the reading m…
REPPL Sep 30, 2026
22dbb24
fix(lint): refuse two ADR files that claim one id
REPPL Sep 30, 2026
f6cd7b2
chore: resolve iss-2609301000153822 — ADR id collisions refused
REPPL Sep 30, 2026
675519d
fix(decide): write the ADR head through one helper, with no private d…
REPPL Sep 30, 2026
1f248da
chore: resolve iss-2609301002043276 — the ADR head is written once
REPPL Sep 30, 2026
2cec1e3
feat(drain): the issue-keyed lane, the hand-back by kind, and the pac…
REPPL Sep 30, 2026
d5f41c2
chore(spec): record the issue key, the hand-back and the drain run as…
REPPL Sep 30, 2026
8155416
chore(records): draft the pin half of dependency re-authoring and poi…
REPPL Sep 30, 2026
374a7fd
fix(version): name the superseded plugin root behind a confident answer
REPPL Sep 30, 2026
9e71e29
chore: resolve iss-2609020113012227 — a superseded plugin root names …
REPPL Sep 30, 2026
f86ec1d
fix(loop): the landing's records commit names its model and runs the …
REPPL Sep 30, 2026
30a904f
chore: resolve iss-2609301046433372 — the records commit names its model
REPPL Sep 30, 2026
cfd226c
fix(drain): hand back a deferral at the local anchor even when the an…
REPPL Sep 30, 2026
fde8a5d
chore: amend the resolution of the stale-anchor record to the final rule
REPPL Sep 30, 2026
3c41d67
fix(loop): refuse a zero-padded issue key
REPPL Sep 30, 2026
b6b7670
fix(loop): quoted brief text cannot close its fence
REPPL Sep 30, 2026
71cb413
chore: resolve iss-2609301128211767 — the loop refuses a zero-padded …
REPPL Sep 30, 2026
2030d4f
chore: resolve iss-2609301128253254 — quoted brief text cannot close …
REPPL Sep 30, 2026
025ff50
merge: land feat/scanner-gitleaks-augmenter (gitleaksAug, 978a00c31)
REPPL Sep 30, 2026
426a0bd
merge: land fix/guard-teach-followups (teachFollow, 2cde160d6)
REPPL Sep 30, 2026
0cd2a70
merge: land fix/oracle-bad-route-name-skip (routeNameSkip, 53e7502d1)
REPPL Sep 30, 2026
526341b
merge: land fix/filing-match-followups (filingFollow, 6d3c932ff)
REPPL Sep 30, 2026
62424ff
test(oracle): expect only the repository's denylist entry after the b…
REPPL Sep 30, 2026
4a59f7f
merge: land fix/term-isatty (ttyDetect, 51b80fa5a)
REPPL Sep 30, 2026
c38030a
merge: land fix/lint-adr-id-unique (adrIdUnique, f6cd7b2d7)
REPPL Sep 30, 2026
9ecb0da
test(intent): an ADR id two files claim refuses the blocked check
REPPL Sep 30, 2026
3e4619d
fix(intent): resolve an ADR successor through the record-id seam
REPPL Sep 30, 2026
e7dbf31
merge: land chore/record-defects-2609-30 (recDefects, b34a5d70b)
REPPL Sep 30, 2026
9f98dc7
merge: land feat/record-lint-stale-edges (lintEdges, 1411cac46)
REPPL Sep 30, 2026
c28a7e3
chore: drop the two draft edges on itd-2 that record-lint names stale
REPPL Sep 30, 2026
84487ac
merge: land docs/predecessor-store-qualifier (drainCitations, 058c392dd)
REPPL Sep 30, 2026
b8868a2
merge: land chore/reach-baseline-shrink (drainReach, 89d2d26fe)
REPPL Sep 30, 2026
01f3f0c
docs(intents): name itd-29's spc-29-42i and spc-9-kbe as legacy roadm…
REPPL Sep 30, 2026
761f9a2
merge: land fix/superseded-root-note (supersededRoot, 9e71e29b2)
REPPL Sep 30, 2026
d4786c0
merge: land chore/iss209-pin-sync-intent (iss209, 815541622)
REPPL Sep 30, 2026
6460f44
test(version): an unknown executable names no superseded root
REPPL Sep 30, 2026
8b1e90c
merge: land fix/loop-audit-rulings (fixLoop, c5b4305f6)
REPPL Sep 30, 2026
6147b5a
merge: land feat/loop-landing-run-record (loopLanding, 30a904f73)
REPPL Sep 30, 2026
f77430a
merge: land feat/drain-loop-lanes (drainLoop, 2030d4ff6)
REPPL Sep 30, 2026
33f8add
test(capture): the augmenter fixtures carry the remedy every capture …
REPPL Sep 30, 2026
b4c4a6e
merge: land fix/drain-anchor-stale (anchorStale, fde8a5d0b)
REPPL Sep 30, 2026
528fbe0
fix(implement): a handed-back run names the way out
REPPL Sep 30, 2026
247e808
chore: resolve iss-2609301303434847 — a handed-back run names the way…
REPPL Sep 30, 2026
63c5ca1
chore(issues): the reach baseline holds 136 names at the integration tip
REPPL Sep 30, 2026
f41a1a7
fix(implement): a run record names its transcripts' scan gap
REPPL Sep 30, 2026
cb46dbe
chore: resolve iss-2609301307566557 — a run record names its transcri…
REPPL Sep 30, 2026
a0b878b
chore: recalibrate the reading windows at the integration tip
REPPL Sep 30, 2026
9d94315
docs(brief): the implement chapter names no command shape for the sca…
REPPL Sep 30, 2026
b373f3c
chore(decisions): say why six ledger entries appear twice
REPPL Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 12 additions & 12 deletions .abcd/config/reading-presets.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
"test"
],
"window": {
"tokens_est": 1460000,
"measured_tokens_est": 1441769,
"measured_bytes": 5550812,
"measured_at": "a586d62ff35df482f45a93989466d3a4d1b230e4"
"tokens_est": 1480000,
"measured_tokens_est": 1463871,
"measured_bytes": 5635907,
"measured_at": "cb46dbea2c0eb37f4aa976f59046f785a32a86eb"
}
},
"entailment": {
Expand Down Expand Up @@ -132,10 +132,10 @@
"intent-projection"
],
"window": {
"tokens_est": 420000,
"measured_tokens_est": 411876,
"measured_bytes": 1585724,
"measured_at": "a586d62ff35df482f45a93989466d3a4d1b230e4"
"tokens_est": 430000,
"measured_tokens_est": 418715,
"measured_bytes": 1612056,
"measured_at": "cb46dbea2c0eb37f4aa976f59046f785a32a86eb"
}
},
"comparative": {
Expand Down Expand Up @@ -216,10 +216,10 @@
"test"
],
"window": {
"tokens_est": 1470000,
"measured_tokens_est": 1450805,
"measured_bytes": 5585600,
"measured_at": "a586d62ff35df482f45a93989466d3a4d1b230e4"
"tokens_est": 1490000,
"measured_tokens_est": 1472907,
"measured_bytes": 5670695,
"measured_at": "cb46dbea2c0eb37f4aa976f59046f785a32a86eb"
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions .abcd/development/brief/02-constraints/02-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ they run behind the same seam and harden the scan; when absent, the native
default still runs and still gates. Scanning never depends on a tool being
installed.

gitleaks is the one adapter wired. The scanner declares the seam it plugs into,
`scanner.Augmenter` (`Available() error` and `Scan(text, file) []Finding`),
and never imports gitleaks; the composition root (`cmd/abcd`) registers the
gitleaks augmenter, and every scanner the core builds picks it up for the
repository it scans. The opt-in is that repository's own
`.abcd/config/gitleaks.json`: absent or `enabled: false`, nothing is looked up
and nothing runs. Armed, gitleaks runs over every text the scanner reads —
the launch payload, transcripts, issue captures, memory pages, the privacy
lint's tracked files, a lifeboat's planned bytes — and its findings are appended
to the native ones, deduplicated on file, line and span. Its output is untrusted
input: a report is bounded in size and count, a finding is kept only when its
bytes sit at the line and column it names, and the scanner rebuilds every other
field itself, so a report or record carries the rule's kind and a masked
fingerprint, never the value in clear. The binary runs in the isolated child
environment every abcd subprocess gets, its own output discarded.

Armed with no gitleaks binary installed is one state with one consequence per
consumer. A release (`launch`) and a lifeboat (`disembark pack`) refuse on it,
the privacy lint reports it as an error, and the write paths — transcript
capture, issue capture, memory ingest — write with the native scanner and name
the gap in their receipt. A gitleaks run that fails, a report the scanner cannot
place, or a configured path the adapter refuses degrades the scanner instead:
every consumer treats that as it treats a broken `pii.json`.

## Plugin interop

abcd interoperates with peer tools — notably the companion harness
Expand Down
16 changes: 10 additions & 6 deletions .abcd/development/brief/04-surfaces/01-ahoy.md
Original file line number Diff line number Diff line change
Expand Up @@ -682,12 +682,16 @@ and notes the orphaned-predecessor possibility in the summary.

**Bare `abcd ahoy`** prints the status board: the folder kind, plugin-root
status, root SHA, install mode where one resolves, vintage and staleness, the
citation baseline's coverage and age on a repo that has armed the citation gate,
the gap count, and — on a repo — guard health and the banlist block with its
reach, closing on a next-step line for the unmanaged kinds. In JSON form the
same pass renders the detection envelope plus vintage and staleness, and the
plugin command reads those two from exactly this render, so they are a contract
with the plugin surface rather than a convenience.
superseded-root note when the answering binary sits in a plugin root other than
the one this session resolves, the citation baseline's coverage and age on a
repo that has armed the citation gate, the gap count, and — on a repo — guard
health and the banlist block with its reach, closing on a next-step line for the
unmanaged kinds. In JSON form the same pass renders the detection envelope plus
vintage and staleness, and `superseded_root` when the note applies; the plugin
command reads those from exactly this render, so they are a contract with the
plugin surface rather than a convenience. The note is the one the version flag
carries, under the same conditions
([`12-version.md`](12-version.md#a-superseded-plugin-root-names-itself)).

**The dry run** renders the detection envelope as JSON and nothing else, so the
plugin command can summarise state off the folder kind and the gaps and name
Expand Down
1 change: 1 addition & 0 deletions .abcd/development/brief/04-surfaces/02-disembark.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ DESTINATION SAFETY GATE
▼
SECRET SCAN (before any write)
scan the planned bytes; a hard-fail secret refuses the whole pack — never redact
(an armed gitleaks scans them too; armed and not installed, it refuses the pack)
│
▼
WRITE
Expand Down
5 changes: 4 additions & 1 deletion .abcd/development/brief/04-surfaces/04-launch.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ that bind a run to a plugin follow the kind. For a kind other than `plugin` the
preview scans the tree the release tag would archive (`git archive`'s view of
`HEAD`, `export-ignore` honoured, links excluded) minus the record namespace,
denied by the same rule a plugin payload is held to, unless it declares an
include set; the report names which tree it scanned. Its lockstep check reads
include set; the report names which tree it scanned. In a repository that armed
gitleaks, its findings join the scan's, and an armed gitleaks with no binary
installed is an unscanned entry and a hard fail, so the preview and the cut
refuse on it ([dependencies](../02-constraints/02-dependencies.md)). Its lockstep check reads
the primary and every declared file, reads no plugin manifest, and refuses a
declared file it cannot read. The rows that judge a plugin payload — the
installability smoke and its deep tier, hook compliance, the parity diff — report
Expand Down
3 changes: 2 additions & 1 deletion .abcd/development/brief/04-surfaces/05-intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,14 +485,15 @@ Both the press-release intent and the frozen PRD are immutable input artefacts p

## 6. Acceptance gates and bidirectional link verification

`internal/core/lint` (cross-cutting; its shipped wiring is the docs currency lint and the `cmd/record-lint` gate) is the record-lint over the committed intent tree — it does not run inside planning; the acceptance-criteria refusal at plan time is the intent package's own `hasAcceptanceCriteria` check (`internal/core/intent`). The armed record-lint rules that bear on the intent tree are `intent_lifecycle` (the directory/kind/`spec_id` invariants and the `status:`-key ban below), `intent_impact_valid` (the `impact:` field's legal value set), `intent_sota` (a `planned/` intent carries a non-empty `## SOTA` declaration, armed at warn), `persona_registry` (press-release quote attributions resolve to the persona roster), `record_schema` (the `itd` store's filename↔id agreement, and `superseded_by` handle validity with two-way agreement across stores), `record_provenance` (the `origin`/`production_mode` disclosure pair and the `related_issues` back-edge), `spec_lifecycle` and `spec_id_unique` (the itd↔spc bidirectional agreement below, and the buckets' agreement: a planned intent has an open spec, and a shipped intent has none left open), and `delivery_state` (no CHANGELOG delivery entry, under `Added` or `Changed`, cites an intent still sitting in `drafts/`). The `IL0xx` codes per [`05-internals/06-lint.md`](../05-internals/06-lint.md) are plan-time design, a later phase.
`internal/core/lint` (cross-cutting; its shipped wiring is the docs currency lint and the `cmd/record-lint` gate) is the record-lint over the committed intent tree — it does not run inside planning; the acceptance-criteria refusal at plan time is the intent package's own `hasAcceptanceCriteria` check (`internal/core/intent`). The armed record-lint rules that bear on the intent tree are `intent_lifecycle` (the directory/kind/`spec_id` invariants and the `status:`-key ban below), `intent_impact_valid` (the `impact:` field's legal value set), `intent_sota` (a `planned/` intent carries a non-empty `## SOTA` declaration, armed at warn), `persona_registry` (press-release quote attributions resolve to the persona roster), `record_schema` (the `itd` store's filename↔id agreement, and `superseded_by` handle validity with two-way agreement across stores), `record_provenance` (the `origin`/`production_mode` disclosure pair and the `related_issues` back-edge), `spec_lifecycle` and `spec_id_unique` (the itd↔spc bidirectional agreement below, and the buckets' agreement: a planned intent has an open spec, and a shipped intent has none left open), `delivery_state` (no CHANGELOG delivery entry, under `Added` or `Changed`, cites an intent still sitting in `drafts/`), and `stale_edge` and `edge_cycle` (the dependency edges below, armed at warn). The `IL0xx` codes per [`05-internals/06-lint.md`](../05-internals/06-lint.md) are plan-time design, a later phase.

The invariants below are the contract the tree is held to, and each names what holds it. A bullet marked **(convention)** is practice the corpus follows by hand, with no shipped check behind it:

- **Acceptance criteria present and well-formed** (per the itd-1 discipline): an intent cannot be planned without a `## Acceptance Criteria` section carrying at least one Given-When-Then bullet. The block is at plan time, not in the record-lint: the refusal is the intent package's own `hasAcceptanceCriteria` check on a draft, plus the `acceptance_criteria` row of the readiness gate. Everything in `planned/` and `shipped/` has therefore passed it. The two buckets the plan step never crosses are held by hand and are **(convention)**: a draft still on the bench may carry none, and so may a discipline, whose route into `disciplines/` does not run through planning at all. Both are true of this corpus today — four bench drafts and seven of the fourteen disciplines carry no section. No record-lint rule reads it, so a committed intent that lost one still passes the gate.
- **A planned intent declares the state of the art** (per [sota-per-intent](../../principles/sota-per-intent.md)): a `## SOTA` section naming the existing alternatives, each one's rough maturity, and the path taken. `intent_sota` flags a `planned/` intent with no such section, or with a heading and nothing under it, at warn severity — the warn-first rung of a ratchet whose next rung is blocker once the planned bucket is back-filled. It judges presence, not the path's spelling, and reads neither `drafts/` (not yet shaped) nor `shipped/` (history, most of it older than the principle).
- **`kind` is set on intents in `planned/`, `shipped/`, `disciplines/`, and `superseded/`.** Intents in `drafts/` may have `kind: null`. The shipped plan step neither infers a kind nor asks for one: it writes `standalone` wherever the draft left the field null, so `standalone` is what an unstated kind becomes. **A later phase** replaces that default with the proposal the user confirms or overrides (§ 1, "Later phase — plan grows a PRD-freeze front end and multi-kind dispatch"). What the record lint holds meanwhile is the value set per bucket: a draft's kind must be null, `standalone` or `bundle-member`, and a planned or shipped record's must be one of the latter two, non-null (`intent_lifecycle`).
- **`kind: bundle-member` requires a `bundle:` field** pointing to a bundle ID; *all* members of a bundle reference the same bundle ID, and bundles are bidirectional in their members' frontmatter. `record_schema` refuses a bundle-member naming a bundle no other record names, unless its `reclassification_history` states the bundle now has one member (the survivor a supersession leaves); a bundle-member carrying no `bundle:` at all is **(convention)**, since both writers stamp the name and the shipped records without one are settled. **Exception for superseded bundle-members:** intents in `superseded/` with `kind_at_supersession: bundle-member` carry `bundle: null` AND `bundle_at_supersession: <bundle-id>` (preserves the bundle the intent was part of when retired, while signalling the bundle is no longer active); the reclassify verb writes both, and no lint reads `bundle_at_supersession`.
- **A dependency edge names a live record, and the edges form no cycle.** `stale_edge` flags a `planned/` or `drafts/` intent whose `builds_on` or `blocked_by` names an intent in `superseded/`, one finding per edge, naming the record the superseded intent's chain ends at: the same walk along `superseded_by` the build's blocked check follows (`intent.SupersessionChainOf`), or why the chain cannot be finished. `edge_cycle` flags a cycle through `builds_on` and `blocked_by` together, across every intent not superseded, in one finding naming every record on it. Both are armed at warn, because this tree carries five findings that each wait on a decision rather than a repair: the drafts itd-22 (`blocked_by`) and itd-33 (`builds_on`) name itd-2, whose successor does not carry the in-session dispatch contract they depended on; itd-2609201916056194 and itd-2609201916151817 build on each other (iss-2609300848016421); and itd-14 and itd-15, and itd-2609081951381895 and itd-2609170822093401, build on each other (iss-2609300903551032). Both rules become blockers once those are decided.
- **Bundle invariant: no member is blocked by another.** Planning several intents as a bundle refuses a member naming another member in `blocked_by`, naming the edge, before anything moves. See § 1 "Bundle invariant" for the canonical statement.
- **`surface_history` entries are well-formed.** Every entry must include `date` (ISO YYYY-MM-DD), `from` (free-form surface descriptor), `to`, and `reason` (non-empty). Lint code `IL012` (severity: warn — it's an audit trail, not a gate). See itd-27's `surface_history` (skill → sub-verb on 2026-05-07) for a worked example.
- **`kind: discipline` lives only in `disciplines/` or `superseded/`.** A discipline-kind record in `drafts/` is an error, caught by the record lint over the committed tree rather than at plan time: the `intent_lifecycle` drafts rule admits only a null, `standalone` or `bundle-member` kind, and the disciplines rule demands `discipline`. The gate is the commit, not the promotion.
Expand Down
7 changes: 6 additions & 1 deletion .abcd/development/brief/04-surfaces/07-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ stored text (`MR001`). `MR001` is the read side of the write-time redactor, run
over every page, the source registry and each stored original, and over every
page name, which it judges as the write side judges a filename: split into its
parts and held to the hard-fail bar alone. It names the kind and the line, never
the span, and the lint never rewrites the store.
the span, and the lint never rewrites the store. The write-time redactor carries
a repository's armed gitleaks (`.abcd/config/gitleaks.json`): its findings are
masked with the native ones, a gitleaks run that fails refuses the ingest, and
armed with no binary installed the ingest writes on the native scanner and its
`scan_gap` names what is missing
([dependencies](../02-constraints/02-dependencies.md)).

Four of the seven can stop the run. `MR001` is the sharpest: residue in the
store is a fault, never advice. `ML001` and `MS002` join it, because a source
Expand Down
5 changes: 4 additions & 1 deletion .abcd/development/brief/04-surfaces/11-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ Every transcript that reaches the store has been redacted on write, so what
accrues is a durable, searchable account of how a repo was built that is safe
to keep, safe to read back, and safe to feed a later distiller. Capture is
automatic: the session's end stages the transcript, the next session's start
files it away.
files it away. A repository that armed gitleaks (`.abcd/config/gitleaks.json`)
has gitleaks' findings masked too; armed with no binary installed, the
transcript is stored on the native scanner and the capture's `scan_gap` names
what is missing ([dependencies](../02-constraints/02-dependencies.md)).

The store is **user-level** and lives outside every repo at
`~/.abcd/transcripts/<root-sha>/records/`, keyed on the repo's root-commit SHA.
Expand Down
48 changes: 47 additions & 1 deletion .abcd/development/brief/04-surfaces/12-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ The version flag prints a short block: the version line, then `install:`
read-only render of the binary's own state, not a board for the repository, and
it answers alone: a record id beside the flag is refused rather than silently
dropped. The JSON form emits the same facts as `name`, `version`, `vintage` and
`staleness`, with `install_mode` present only when it resolves. The update
`staleness`, with `install_mode` present only when it resolves, and
`superseded_root` present only when the answering binary sits in a plugin root
other than the one this session resolves (see *A superseded plugin root names
itself* below); the plain render prints that note on a `note:` line. The update
verb's check prints the same report with a `check` object added.

When the online check finds an update, the answer carries the command that takes it,
Expand Down Expand Up @@ -83,6 +86,49 @@ renders stands in. When neither says anything, the framework's line stands
byte-for-byte. The exit code, the stream and the JSON envelope are the
framework's own.

## A superseded plugin root names itself

The two shapes above are loud: the binary is asked for something it does not
have, so there is an error to hang a line on. A third shape has none. A plugin
root is named for the commit it was installed from, so every update mints a new
root and nothing prunes the old ones; a command page interpolates an absolute,
hash-pinned binary path into its own prose, and that path is designed to
expire. Between an update and the reload that re-interpolates it, following the
page runs a superseded binary that is still on disk, answers normally — exit 0,
no diagnostic — and reports a version that is true of that root and false of
this machine (iss-2609020113012227, refining iss-2608230943088357).

What the disk proves, with no network and no heuristic, is the divergence: the
plugin root this session resolves — through the same ladder every other surface
uses, which prefers the environment's own plugin-root variable over the
executable's ancestors — against the plugin root the running binary sits in,
found by that ladder's own executable-ancestor walk and layout check. When those
are two different roots, the version flag's report (and the update verb's check,
which extends it) and bare `ahoy` add a `superseded_root` note naming both roots
by the commit each was installed from, in the plain render as well as in the
JSON form. It is a note beside the answer: the reported version, vintage and
staleness are unchanged, and nothing refuses.

The two names are directory names read off the disk, so each passes through the
terminal sanitiser before it is printed: a control or bidirectional character in
one is replaced, never rendered. The command pages tell the agent to relay the
note as abcd printed it and never to rebuild the names from a path, which would
undo that.

The note is silent in three cases. A binary inside no plugin root at all — a
PATH copy, a `go run` build — has no superseded root to name, and the vintage
comparison already covers it. A binary in the root this session resolves has
nothing to disclose. And a binary served from a source checkout of abcd says
nothing: a checkout is a valid plugin root (`hooks/` sits at its top), so a
developer running the `make build` artefact while a harness session resolves its
own cache root satisfies the divergence test, but a checkout is not named for a
commit it was installed from, and the note's remedy would point at the
plugin-root binary the dogfooding rule calls the stale one. That case is the
vintage comparison's, and the stale-binary line above keys its rebuild remedy on
the same source-checkout test. The guard is keyed on the root that served the
answer: a provisioned root answering into a session whose own root is a source
checkout still names itself.

## Where the version comes from

The version is **derived, never hand-authored**: it is read from the shipped
Expand Down
Loading
Loading