Development record
abcd Lets You Consult Sources You Cannot Cite — and Remembers Every Debt
Press Release
Consult any source freely; cite only by deliberate human choice — abcd keeps the ledger in between. A developer-researcher often works from material they are not free to name in public: working papers under review, a collaborator's private repository, notes shared under an NDA. The agent should be able to read that material when it bears on a decision — and must never cite it in anything published. abcd manages this with a personal corpus and a provenance ledger, guarded mechanically. A local-only corpus (abcd's user-level home,
~/.abcd/sources/by default, itself a no-remote git repository) holds the documents and a machine-readable bibliography (CSL-JSON; acustomblock carriesconfidential,permission_status, retrieval keywords, and banned aliases). An append-only provenance ledger (JSONL, one file per consuming repo) records every meaningful influence — which source, which decision, what claim, what kind of influence — gated twice: the source'spermission_statusgrants the right to cite, and each ledger line'scited_publiclyflag exercises it, flipped only by the human. And the guardrails are mechanical for what mechanism can catch: the confidential entries generate the pattern block in each repo's untracked private-names banlist, the pre-commit guard refreshes and enforces it on every commit, and acite-checkscan clears any text before it is shared."I could never let an agent near my working papers before, because one helpful footnote could burn a collaborator's trust," said Alice, a researcher-developer. "Now it reads everything, records what influenced what, and cites nothing. When the paper behind a decision is finally published, I flip one flag — and the whole influence trail is already written."
Why This Matters
Automatic citation is a virtue that becomes a breach the moment a source is confidential: an agent that helpfully names "the working paper this design follows" in a commit message has leaked something no history rewrite fully recalls. The naive fix — keep the material away from the agent — throws away exactly the context that makes its design work good. The resolution is to split consultation from citation and put a durable, machine-readable record between them: influence is captured eagerly and automatically (cheap, local, append-only), citation happens lazily and manually (when permission exists). The ledger is also the seed of something bigger — a team bibliography and a reconstructable paper — but those travel as their own intents (itd-126, itd-127, each refines this one); this intent is the personal core they stand on.
This composes existing abcd designs rather than inventing new machinery: the two-layer name banlist (itd-74) supplies the leak guard; the append-only audit chain (itd-16) is a possible later integrity backend for the ledger — the corpus repo's git history carries tamper-evidence until then, and nothing here depends on itd-16 shipping; and the provenance substrate (09-provenance-substrate.md) already defines citation blocks, a source registry, and an NDA-aware publish gate for ingested content — this intent extends the same stance to consulted content. The trust boundary itself — documents and ledgers never leave the user tier; a public citation requires both gates — is recorded as adr-41 and brief invariant 9, which this intent cites rather than declares; the standing stance is the consult-freely-cite-deliberately principle.
What It Looks Like
abcd source addregisters a document: CSL-JSON entry (permission status, keywords, aliases), original and extracted text stored in a per-source folder whose location —confidential/<key>/orpublic/<key>/— is the classification, declared once at ingestion. Derived artifacts (summaries, notes) live in the same folder and inherit the class; declassification is a visible move, never a silent flag edit. Consultation is plain search over the folders — no index, no service. The corpus lives in abcd's user-level home (~/.abcd/, path configurable; relocation is itd-77) — the first user-tier surface alongside the repo's.abcd/tiers.abcd source ledgerappends an influence record —{decision_ref, claim, source_key, locator, influence, cited_publicly}— and commits it; corrections are new lines, never edits. A public citation requires both gates: the source permits (permission_status) and the line is flipped (cited_publicly: true).abcd source sync-banlistprojects every confidential entry's identifying strings — title and aliases always; author names only by per-source opt-in (ban_authors: true, for the rare collaboration that is itself secret — the common confidential types, one's own submitted work, purchased reports, and private repos, are protected by title and aliases, and banning their authors would mostly ban legitimate names, including one's own) — into the repo's untracked private-names banlist (the itd-74 private layer). The pre-commit guard auto-refreshes this block when the corpus is present, and when the corpus is absent every corpus-dependent step no-ops and says so — never a silent skip, never a failure.abcd source cite-checkscans any text and reports offending entries by key only — its output is safe to relay.
What It Cannot Enforce
The mechanical layer blocks literal identifying strings. It cannot detect a paraphrase that identifies a source without naming it — "a forthcoming paper shows X beats Y", or a description of a private repository's distinctive architecture. That residual risk is handled behaviourally (the consultation skill forbids identifying description, not just naming) and by the human review that gates every publish; abcd states this boundary plainly rather than implying coverage it does not have. Durability is likewise bounded: a no-remote corpus survives disk loss only via machine backup and offline git bundle snapshots — abcd documents that discipline; it cannot perform it.
Dogfood (target)
A convention-first scaffold (corpus layout, ledger, guard scripts, and an agent-side consultation skill) was prototyped for this repo's development and is recorded in the plan (2026-07-08-confidential-sources-scaffold.md) and the SOTA survey (2026-07-08-confidential-sources-provenance-sota.md); no live corpus currently exists on the development machine. The feature is to deliver these as abcd verbs so any managed repo inherits them — and re-establishing this repo's own corpus through those verbs is the first validation.
Scope Conditions
None stated.
Acceptance Criteria
- Given a document and its metadata, when Alice runs
abcd source adddeclaring it confidential, then the corpus gains a CSL-JSON entry (with thecustomblock) and the document plus extracted text land underconfidential/<key>/— folder location is the classification. - Given a consulted source influencing a decision, when Alice records it, then
abcd source ledgerappends{decision_ref, claim, source_key, locator, influence, cited_publicly: false}as a new line; corrections are new lines, never edits. - Given confidential entries in the corpus, when a commit runs in a managed repo, then the pre-commit guard's generated block is refreshed (titles and aliases always; author names only under
ban_authors: true) and a commit containing a banned string is refused. - Given any text about to leave the machine, when Alice runs
abcd source cite-check, then offending sources are reported by key only, so the output itself is safe to relay. - Given a ledger line whose source lacks citation permission, when Alice attempts to set
cited_publicly: true, then the flip is refused naming the failing gate; with permission present, the flip succeeds and is itself a new ledger line. - Given a machine with no corpus, when abcd runs in a managed repo, then every corpus-dependent step no-ops and says so — never a silent skip, never a failure.
- Given a confidential source that gets published, when Alice moves its folder
confidential/ → public/, then the next banlist refresh drops its strings and its ledger lines become eligible for the citation flip.
Open Questions
- Ledger ownership once work spans machines: explicitly deferred (maintainer ruling, 2026-08-16) — per-repo files in the user-level corpus serve one machine; revisit when a second machine actually exists.
- The share/ingest questions that previously lived here (conflict shape between teammates, provenance marks on ingested entries) travel with itd-126.
Audit Notes
Fidelity review — receipt rcp-595934bbc552 (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:9c702d8c1070e2b1d1a46d9c4e1b52d251dc706e2e889b0e0f24d95adb3998a0 Input attestations: commit:5bc0ebdc2b235a1221fc43934547906fe4b02bae (spc-31 close, itd-76 ships); tree audited at 52c2236a55830421c2af5fa58f5a196c4eb7fdd5@-;
Acceptance rollup: MET 5 · MET_WITH_CONCERNS 2 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: Add refuses without a declared class, writes the CSL-JSON entry with its custom block and lays original plus text.md under < class>/< key>/, committed in the corpus; TestAddConfidentialLandsUnderItsClassFolder passes at BASE evidence: internal/core/source/add.go:154 — "declare the class — confidential or public; it is decided once, here, and never defaulted" evidence: internal/core/source/add.go:98 — "folder := filepath.Join(c.Dir, req.Class, entry.ID)" evidence: internal/core/source/add.go:129 — "writeSources(c.Dir, append(fresh.raw, raw))" evidence: internal/core/source/source_test.go:119 — "func TestAddConfidentialLandsUnderItsClassFolder"
- ac-2 — MET: Record carries decision_ref, claim, source_key, locator, influence and cited_publicly; Append never sets cited_publicly and writes through AppendLineIn only, a correction is a new line naming the earlier one; TestLedgerAppendsAndNeverEdits proves the prefix is preserved evidence: internal/core/source/ledger.go:25 — "type Record struct" evidence: internal/core/source/ledger.go:118 — "cited_publicly is always false here: exercising the right to cite is Flip's, and only Flip's" evidence: internal/core/source/ledger.go:237 — "fsutil.AppendLineIn(root, rel, b, 0o600)" evidence: internal/core/source/source_test.go:229 — "a correction rewrote an earlier line"
- ac-3 — MET_WITH_CONCERNS: Projection bans titles and aliases always and authors only under ban_authors; SyncBanlist writes the owned block; this repository's guard rebuilds ./cmd/abcd and refreshes on every commit (TestTheRepositoryGuardRefreshesWithItsOwnBuild) and a banned string refuses the commit (TestSyncBanlistFeedsTheGuard). Concern: the hook
ahoy installscaffolds into a MANAGED repository refreshes only after a repo-local opt-in (git config --local abcd.sourcesBinary), otherwise one line and no refresh — a signed-off narrowing (dcn-2609250000000027) pending the ruling in open iss-2609250834251447 evidence: internal/core/source/guard.go:17 — "func (c *Corpus) Projection() ([]banlist.KeyedPattern, error)" evidence: internal/core/source/add.go:441 — "if e.Custom.BanAuthors {" evidence: .githooks/pre-commit:599 — "if declares_format "$sources_first"; then sources_refresh" evidence: internal/core/source/source_test.go:572 — "func TestTheRepositoryGuardRefreshesWithItsOwnBuild" evidence: internal/core/source/source_test.go:401 — "the guard let a confidential title through" evidence: internal/core/ahoy/defaults/pre-commit:481 — "elif [ -z "$sources_bin" ]; then" evidence: dcn-2609250000000027 — "The copyabcd ahoyscaffolds refreshes only when the clone opts in with `git config --local abcd.sourcesBinary" - ac-4 — MET: CiteCheck scans through the private layer's matcher and a Finding carries source key, field, line and offset only; TestCiteCheckReportsByKeyOnly asserts the JSON report holds no title, alias or author evidence: internal/core/source/guard.go:91 — "type Finding struct" evidence: internal/core/source/guard.go:135 — "Finding{Source: key, Field: field, Line: h.Line, Offset: h.Offset}" evidence: internal/core/source/source_test.go:415 — "func TestCiteCheckReportsByKeyOnly"
- ac-5 — MET: Flip refuses naming gate 1 when permission_status is not citable or the folder is not public/, appends nothing on refusal, and a successful flip is a new line with flips naming the original; TestFlipNeedsBothGates covers both outcomes evidence: internal/core/source/ledger.go:194 — "gate 1 — source %q has permission_status %q, and only %q grants the right to cite" evidence: internal/core/source/ledger.go:202 — "flip := orig flip.TS = stamp(now) flip.CitedPublicly = true" evidence: internal/core/source/source_test.go:270 — "func TestFlipNeedsBothGates"
- ac-6 — MET_WITH_CONCERNS: Every core step returns ErrNoCorpus and creates nothing (TestNoCorpusIsNamedByEveryStep); the guard prints one line and proceeds;
sync-banlist --refreshis one line and exit 0; every other verb says so on one line. Concern: the verbs exit 3, a non-zero code, where the criterion says 'never a failure' — the spec's design (item 6) chose a distinct no-corpus exit for the verbs and 0 for the guard, so the narrowing is signed off evidence: internal/core/source/source.go:92 — "ErrNoCorpus = errors.New("sources corpus is absent")" evidence: internal/surface/cli/source.go:54 — "if errors.Is(err, source.ErrNoCorpus) { return &exitError{Code: 3" evidence: internal/surface/cli/source.go:451 — "if refresh && errors.Is(err, source.ErrNoCorpus) {" evidence: .githooks/pre-commit:593 — "no sources corpus at ~/.abcd/sources — generated banlist block not refreshed (skipped)" evidence: internal/core/source/source_test.go:518 — "func TestNoCorpusIsNamedByEveryStep" evidence: internal/surface/cli/source_surface_test.go:42 — "func TestSourceNoCorpusSaysSoOnEveryVerb" evidence: .abcd/development/specs/closed/spc-31-source-provenance-ledger.md:73 — "exit 0 (guard) / a distinct no-corpus exit (verbs)" - ac-7 — MET: Declassify is a
git mv confidential/<key> public/<key>plus the entry update in one commit; the next SyncBanlist drops the key's block lines and Flip then succeeds; TestDeclassifyDropsTheBanAndOpensTheFlip proves all three evidence: internal/core/source/add.go:405 — "corpusGit(c.Dir, "mv", "--", from, to)" evidence: internal/core/source/guard.go:20 — "if c.Class(e.ID) != ClassConfidential { continue" evidence: internal/core/source/source_test.go:449 — "func TestDeclassifyDropsTheBanAndOpensTheFlip"
Gap audit:
- honoured:
- folder location is the classification and a corpus whose folders and entries disagree is refused by every step that derives a ban evidence: internal/core/source/source.go:297 — "func (c *Corpus) requireConsistent() error" evidence: internal/core/source/source_test.go:488 — "func TestAMismatchedClassRefusesTheSync"
- the two-gate citation boundary of adr-41: permission_status grants and a human-flipped ledger line exercises evidence: internal/core/source/ledger.go:161 — "Flip exercises the right to cite for one ledger line (adr-41 gate 2), and only after gate 1 grants it"
- one matcher for guard and cite-check: the projection runs through banlist.PhrasePattern and banlist.ScanText evidence: internal/core/source/add.go:426 — "p, err := banlist.PhrasePattern(phrases...)" evidence: internal/core/source/guard.go:127 — "hits, err := banlist.ScanText(pats, text)"
- the corpus is a no-remote git repository in the user-level home and every write is committed evidence: internal/core/source/source.go:411 — "corpusGit(dir, "init", "-q")" evidence: internal/core/source/source.go:405 — "the location is inside another git working tree"
- the verbs are wired on the CLI and the plugin surface evidence: commands/source.md:4 — "add [document] --key K --confidential|--public [...] | declassify < key> | ledger" evidence: internal/surface/cli/source.go:62 — "func newSourceCommand(asJSON *bool) *cobra.Command"
- diverged:
- the pre-commit guard auto-refreshes the block on every commit in a managed repo — delivered as opt-in per clone (abcd.sourcesBinary) for the scaffolded hook, automatic only in abcd's own checkout, pending the ruling in iss-2609250834251447 evidence: internal/core/ahoy/defaults/pre-commit:482 — "this hook refreshes its banlist block only on opt-in: git config --local abcd.sourcesBinary" evidence: .abcd/work/issues/resolved/iss-2609252007419997-the-scaffolded-pre-commit-template.md:14 — "iss-2609250834251447's ruling stays open and can widen it"
- every corpus-dependent step no-ops and says so, never a failure — the verbs answer with exit 3 (the spec's distinct no-corpus code), the guard with exit 0 evidence: internal/surface/cli/source.go:8 — "3 there is no corpus at the configured location — the distinct no-corpus code"
- missing:
- the dogfood target — re-establishing this repository's own corpus through the shipped verbs as the first validation — is not evidenced; the ship commit records it as left to the person whose corpus it is evidence: .abcd/development/specs/closed/spc-31-source-provenance-ledger.md:79 — "## First validation" evidence: .abcd/development/intents/shipped/itd-76-source-provenance-ledger.md:41 — "re-establishing this repo's own corpus through those verbs is the first validation"
Frontmatter
| id | itd-76 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | major |
.abcd/development/intents/shipped/itd-76-source-provenance-ledger.md
open on GitHub ↗ · commit history ↗
References2
Referenced by16
- adr-41Documents and ledgers never leave the user tier; a public citation requires both gatesrelated
- iss-2610262032158901itd-76 puts abcd's user-level home at `~/.abcd/`; the invariants register gives the home as `~/.abcd.noindex`related
- iss-2610262033224778itd-76 places the sources corpus at `~/.abcd/sources/`; 01-ahoy lists it under the user-scope home `~/.abcd.noindex/`related
- iss-2610262035135419itd-76 puts the sources corpus at ~/.abcd/sources/ by default; the library chapter puts it at ~/.abcd.noindex/sourcesrelated
- iss-2610262036474703itd-76 puts abcd's user-level home at ~/.abcd/; the history chapter treats ~/.abcd as a legacy locationrelated
- iss-2610262038084949itd-76 puts the sources corpus under `~/.abcd/sources/`, abcd's user-level home; 29-report puts the sources store beside the inbox in the machine-scoped storerelated
- iss-2610262040149808The configuration chapter treats ~/.abcd as the retired home, read by no reader; itd-76 puts the sources corpus at ~/.abcd/sources/ by defaultrelated
- iss-2610262042193372itd-76 calls its log of which source influenced which decision 'the provenance ledger'; the ledger entry keeps that name for another sense and calls this one 'the sources ledger'.related
- iss-2610262042201742itd-76 puts abcd's user-level home at `~/.abcd/`; the memory chapter puts the user scope under `~/.abcd.noindex/`.related
- iss-2610262043501753itd-76 puts abcd's user-level home at ~/.abcd/; itd-2609301918174237 puts the owner's machine-layer settings under ~/.abcd.noindex/related
- iss-2610262044449331itd-77 says abcd's user-level home defaults to `~/.abcd.noindex/`; shipped itd-76 says it defaults to `~/.abcd/`, with the sources corpus at `~/.abcd/sources/`related
- iss-2610262045119850abcd's user-level home is ~/.abcd.noindex/ in itd-39 and ~/.abcd/ in itd-76related
- itd-126A Team Shares One Bibliography Without Sharing Anyone's Corpusbuilt on by
- itd-127A Paper Is Reconstructed From the Provenance Ledgerbuilt on by
- itd-2609292108089653Ingesting and consulting sources are abcd verbs, and no corpus entry is a keyword stubbuilt on by
- spc-31source-provenance-ledgerimplemented by
mentions21
- consult-freely-cite-deliberatelyConsult Freely, Cite Deliberately
- dcn-26070800000000012026-07-08 — Confidential sources: global user-level corpus (CSL-JSON + grep
- dcn-26070800000000082026-07-08 — itd-76 grilled: leak guard promises literal strings only
- dcn-26070800000000122026-07-08 — Severity ≠ priority (records an earlier-session decision):
- dcn-26073000000000062026-07-30 — itd-74 (round 7), increment 2: the scaffolding and honest-reach halves (AC5, AC7), closing spc-20's mapping. `ahoy install` writes three artefacts, all create-if-absent: the committed guard hook at `.githooks/pre-commit`, a `.abcd/docs-lint.json` carrying an EMPTY public banned-names family, and the documented private stub in the gitignored local tier. Three decisions worth the record. (1) THE SCAFFOLDED HOOK IS A GENERALISATION, NOT A COPY. It drops this repo's iss-62 identity gate and the itd-76 dogfood refresh and keeps the name guard alone, so a byte-equality drift test between the two files would be wrong; the template is proved BEHAVIOURALLY instead — the scaffold test installs it into a real temp repo and drives three commits through it (loud warning and exit 0 with no entries, refusal naming the key alone with the pattern absent from all output, refusal to stage the store itself), which is the only evidence that distinguishes a guard that works from one that merely parses. Rejected: a symlink from `.githooks/pre-commit` to the embedded default (zero duplication, but breaks a Windows checkout and would have rewired increment 1's own test harness), and copying this repo's hook verbatim (it would scaffold repo-specific gates into every managed repo). (2) THE PUBLIC FAMILY IS SEEDED EMPTY. abcd cannot know which names a repo may not publish, and a ban nobody declared would fail a build over a word the maintainer never chose; the array's PRESENCE is what makes `banlist add --public` usable, which is all AC5 asks for. A docs-lint config that exists but carries no usable array is a NON-resolvable diagnostic gap: the config gates CI and a contributor owns it, so abcd reports the fault and never rewrites a file it cannot read. (3) THE STUB'S GAP IS RESOLVABLE ONLY ONCE THE FENCE COVERS IT. Writing the store into a repo git would track is the exact hazard the layer exists to prevent, so the stub write runs after the visibility step and is gated on the canonical `.gitignore` block being on disk; advertising it as resolvable regardless would leave a repo permanently "partial" on a fix apply refuses to make (the markerSymlink precedent). No new gitignore entry was needed — iss-169's fence already covers `.abcd/.work.local/` under private and `/.abcd/` under public — so AC5's gitignore clause is pinned by a new test against real `git check-ignore`, keyed to `banlist.PrivateRelPath` rather than to a spelling of the tier, and it passed on first run. AC7: the reach sentence lives once as `banlist.PrivateReachNote` and travels INSIDE the reported state (`BanlistHealth.Reach`), not as renderer prose — a machine consumer reading "hook installed" beside a present store would otherwise draw exactly the wrong conclusion, and the JSON envelope is a report surface too. The health pass reads no entry and spawns no subprocess: the store's content is the secret, and a status board is the surface that must not hold it. Stub examples are all commented out (a fresh scaffold parses to zero entries, so the guard warns loudly rather than looking like protection) and every illustrative value is a reserved documentation value or a persona-derived fixture host, judged by the repo's own network-identifier detector — with a control value asserted flagged first, so the assertion cannot pass vacuously. Residual, recorded not fixed: abcd does not set `core.hooksPath`, so a clone arms the hook by hand (the record scopes AC5 to the hook being committed, and silently repointing a repo's hooks path is a git-config change no acceptance criterion asked for); and the scaffolded template and this repo's prototype now share ~250 lines that no mechanism holds in sync. FIX PASS after two fresh adversarial reviews returned BLOCK on this increment's own code; detector-first throughout, each behaviour change watched failing first. (A) THE FENCE IS GIT'S DECISION, NOT A TEXT COMPARISON. Both the stub write and the gap's resolvability were derived from `gitignoreBlockDrifts` — a set comparison of the abcd-managed .gitignore block — which answers a different question from the one that matters. A repo can carry a byte-perfect block and still track the store (a negation after it, a tracked tier), a fully-configured repo never reaches the visibility step at all, and `stepConfigValues` returns nil whenever ANY unrelated config value is missing under a declined ConfigChange, which silently disabled the write while `localTierIgnored` reported the gap as resolvable — a permanent partial. Both now call `gitutil.IsIgnored`, the same primitive increment 1's `requireIgnoredStore` uses, evaluated on disk after the visibility step, and skipped outside a git repo for the same stated reason (a check cannot demand proof no one can supply). Health carries the verdict and a present store git would track is called out on every surface. (B) CONTAINMENT. The three writes used `os.MkdirAll` plus an atomic rename, and a repo that commits a symlink at `.githooks` — which a checkout materialises before abcd runs — took the 0755 hooks OUTSIDE the repo while every surface reported the in-repo path; watched, and confirmed by the reverted code writing `pre-commit` into the link target. They now go through `fsutil.CreateExclusiveIn` under an `os.Root` opened at the repo, which is both guarantees at once (no clobber, no check-then-write window) and refuses symlink traversal at every level. The local-tier case was already contained by increment 1's `os.OpenRoot` read path, which reports such a store as unreadable rather than absent. (C) BOM FAIL-OPEN, IN BOTH READERS. A leading UTF-8 BOM made the first line differ from the format declaration, so a KEYED store read as LEGACY: every keyed entry became a whole-line pattern matching nothing a commit contains, while `entries` stayed >=1 so the zero-entry warning never fired — a store that looked healthy and checked nothing, reproduced as a real commit going through. Fixed in the Go parser, the scaffolded template, and this repo's own prototype; and a first line that is not the declaration but WOULD be after stripping leading blanks is now a DAMAGED declaration that fails closed, because reading it as legacy is the same downgrade by another route. (D) MERGE-COMMIT BYPASS. git runs no pre-commit hook for a merge, so a banned name entered history the moment a branch carrying it was merged; a `pre-merge-commit` half now runs the same guard through one implementation (a shim, not a copy) in both the template and this repo. The reach sentence widened with it: `PrivateReachNote` now names what a hook cannot see — a rebase, `git am`, a cherry-pick, `--no-verify` — because "machines that have opted in" is necessary and not sufficient, and the shorter sentence let a reader believe an opted-in machine was covered. (E) TWO CLAIMS WITHDRAWN. A foreign pre-commit hook was reported as the abcd guard: presence is not identity, so each template carries `# abcd-name-guard: v1`, a hook without it is a non-resolvable diagnostic abcd never replaces, and "installed" became "committed" with the arming instruction beside it (git runs the hook the clone's hooks path selects, which abcd neither sets nor fully observes). And under `visibility: public` the fence ignores the anchored `/.abcd/`, so the "committed, CI-enforced" public family is untracked exactly where public exposure is the risk — detection now reports `banlist.public_family_ignored` and the board reads NOT ENFORCEABLE. The placement question is NOT resolved here (moving the file amends the iss-169 record; carving an exception gives up its one-switch property) and is captured as iss-176 for the maintainer, with three candidate reconciliations. Minors in the same pass: the private layer's shape comes from a new `banlist.SummarisePrivate` — the shared parser without the per-entry grep, so a status pass costs no subprocess per line — the public family's four faults report apart (an unreadable file is not an absent array), the local tier's 0700 is asserted rather than assumed from the creating call, the staged-path refusal is case-folded, the format is announced before the parse loop, and stale scratch files are swept. Residuals the reviews verified as accepted: the scaffolded template and this repo's prototype share ~250 lines that no mechanism holds in sync (byte-equality would be wrong — the template drops the identity gate and the dogfood refresh — and the template is proved behaviourally instead); `--no-verify`, rebase and server-side pushes remain uncoverable by construction; GNU/BSD grep divergence stands as increment 1 recorded it; commit messages, tag names and branch names are outside the guard's scope (it reads staged blobs and paths); a scaffolded repo gets no CI wiring for the public family; abcd still does not set `core.hooksPath`; and `a.note()` renders absolute paths across every apply step, pre-existing and repo-wide, captured as iss-177 rather than half-fixed here. FIX PASS 2 after a second pair of independent BLOCK reviews, both on the fix pass's own code. (F) THE MERGE SHIM NEVER STANDS IN. It was written whenever its own gap was present — including beside a FOREIGN pre-commit hook — so abcd's marker landed in the shim, the board read "pre-merge-commit hook committed", and merges stayed unchecked; worse, the maintainer's hook silently began running on merge commits, which git never did, so apply was taking over exactly the wiring the foreign-hook gap disclaims owning. The shim is now written only beside abcd's own guard (including one the same step just wrote — keyed on the artefact's STATE, not on a gap a fresh repo deliberately does not raise), and `banlist.merge_hook_inert` reports the alternative. The wrong assertion in the increment's own test — which asserted the buggy behaviour — was flipped and watched fail. (G) A COPY OF THE STORE IS A MISTAKE-NET, NOT AN ADVERSARY-NET. The staged-path refusal matched only the local tier, so `cp` of the banlist to notes.txt, or a `git mv` out of the tier, committed every pattern in clear while the guard announced a clean check — the entries cannot catch their own text, because they are escaped regular expressions and `carol-server\.example\.net` does not match itself. Three tests now: the tier path (not escapable, and applied to a rename's SOURCE path too — the loop had been overwriting it with the destination and discarding the one field that identified the file), a staged blob whose FIRST LINE is the format declaration, and a staged path whose basename is the store's filename. Their reach is stated rather than implied, here and in the brief and the spec: a copy with the declaration stripped, altered by a byte or displaced below a preamble passes the first-line test; a LEGACY store declares no format at all and passes it always, and passes the basename test the moment it is renamed; and the rename-source test only fires for a store git already tracks. What they catch is the accident that happens — a `cp`, a `.bak`, a stray duplicate — and they are NOT widened further on purpose: matching a store's key list or scanning deeper into every staged blob would route the secret through more code paths to catch fewer accidents. Round 3 also found the other direction: the first-line test blocked this repo's OWN committed corpus fixtures, and `--no-verify` is an off switch for the whole guard rather than a per-file escape, so the refusals now honour a published second-line `# abcd-banlist-example` marker — exempting a blob from the COPY tests and from nothing else, its content still scanned against every entry — and the two keyed corpora carry it. The tests also moved OUT from behind the absent-store early exit (whether this machine has a store says nothing about whether a commit is carrying one, and that exit was itself how the rename escaped), and the guard no longer creates its local tier in a repo that never opted in: with no store it scratches in a 0700 temp directory instead of leaving an unfenced abcd directory behind on every commit. (H) THE DECLARATION IS LINE 1 OR NOWHERE, in all three readers and for BOTH formats. A blank line or a comment above it, a duplicate below it, or any prefix bytes before it (a UTF-16 byte-order mark, an editor artefact — caught by a suffix test rather than a byte-class check no portable shell has), is a damaged declaration that fails closed. Round 3 found the header scan gated on the store not already being keyed, so a keyed store with the declaration repeated on line 2 read as healthy to Go — "present, keyed, 1 entry" on the status board — while the shell blocked every commit; a store one reader calls healthy and the other refuses is worse than either verdict alone, and a fourth shared corpus now drives both readers over exactly that file. (I) THE ENVIRONMENT IS PINNED FIRST, in all four hooks: xtrace off, IFS, LC_ALL and PATH as the FIRST statements, before anything is read or run — the pin had been landing after `rev-parse`/`cd`, and the merge shim had none at all while resolving its delegate through an external `dirname` (now `${0%/*}`, pure parameter expansion). The pin NARROWS the substitution class and does not close it: two of the pinned directories are user-writable on a typical developer machine, and `#!/usr/bin/env bash` resolves the interpreter through the inherited PATH before any of it runs — the earlier "root-owned" wording in this line was wrong and is corrected here. Every external tool the hooks call is probed after the pin so a missing one blocks loudly instead of surfacing as a mute exit 127, and a nonstandard prefix extends the pin through `git config --local abcd.guardPath` — read through the already-pinned git, and deliberately NOT an environment variable, since a repo-scoped direnv sets the environment and would reopen the exact hole. Minors in the same pass: scaffolded modes are chmod'd explicitly because `CreateExclusiveIn` and `MkdirAll` are umask-masked and a stripped exec bit turns the shim's fail-closed branch into a permanent merge block; the marker is matched by PREFIX so a v2 template cannot reclassify every v1 hook as foreign; `storePathIsSafe` distinguishes "not a repository" (skip, as before) from "repo-shaped and git will not answer" (fail closed) — `InRepo` is false for git-absent-from-PATH too, which the old comment's justification did not cover; `hooksPathArmed` resolves both sides before comparing, so an absolute `core.hooksPath` is no longer read as unarmed and told to downgrade itself; the absent-public-family gap is non-resolvable where git would ignore the path and abcd declines to write a config it would immediately call unenforceable; the detection envelope omits the banlist object for an unmanaged folder rather than serialising undeclared zero states; detection reads now resolve through the same containment root the writes use; the reach note is explicitly non-exhaustive and names `git revert` and a reapplied stash; and `abcd ahoy` scaffolds the `.githooks/* text eol=lf` attribute into managed repos (append-if-missing, never a rewrite) rather than leaving them the CRLF-shebang failure this branch fixed for abcd itself. CORRECTION to this line's earlier claim that the health pass "spawns no subprocess": it always did — it now spawns four (one `rev-parse`, one batched `check-ignore` for both paths, one `config --local`, and one `check-attr` for the hooks' EOL), down from six or seven, and `ahoy.Detect` is on the session-hook path, so that count is a cost paid per prompt. One earlier test was vacuous and is fixed rather than deleted: the local-tier half of the symlink containment test passed with containment reverted, because the escaping store is classified unreadable before any write is attempted, so it now asserts that protecting state by name. FIX PASS 3 (converge). MARKER IDENTITY: `classifyGuardHook` matched the marker as a substring of the whole blob, so a foreign hook that merely MENTIONED it — a comment, a grep for it — classified as abcd's own, the board claimed coverage, and the merge shim was written beside it; identity is now a whole LINE matching `^# abcd-name-guard: v<digits>$`, with the version a group so a v2 template cannot reclassify v1 hooks. The same line-wise fix went to the `.gitattributes` pin, where a commented-out attribute had read as pinned. REACH ACCURACY: a fast-forward `git pull` creates no commit and so runs no hook — the commonest uncovered path of all, now named — and the stash claim was probed FALSE (`git stash pop` then commit runs pre-commit normally) and removed: a bypass list naming a covered path teaches a reader to distrust the rest of it. Minors: the merge-inert gap no longer asserts a delegating shim that may not exist; `createContained` pinned modes on directories it did NOT create, widening a maintainer's deliberate 0700 to 0755, and now pins only what it made; `repoShaped` walked no ancestors, so in a repo SUBDIRECTORY with git unavailable the stub would have been written into a tracked tree; the stale-scratch sweep and the tier creation are conditional on the tier being the scratch home; and the foreign-hook gap's `Required: false` is documented as deliberate (a required gap nothing can resolve is a repo permanently reported incomplete for a state its maintainer chose). Also re-classified: `stepBanlist` now asks disk what occupies the hook paths rather than trusting the detection snapshot taken before the earlier steps ran. FIX PASS 4 (prose currency plus one-liners). The two CHANGELOG bullets, internal/README.md, and both brief surfaces described THREE scaffolded artefacts and a stale reach; there are FIVE (both guard hooks, the .gitattributes LF pin, the public family, the stub), and the reach now names a fast-forward `git pull` — the commonest uncovered path of all — and no longer names a reapplied stash, which probing showed IS covered and which reach_test.go now pins out. Security minors: an inherited shell FUNCTION shadowing `grep` defeated the PATH pin entirely (probed — the guard reported a clean check under `BASH_FUNC_grep%%=() { return 1; }`), so all four hooks now `unset -f` the commands they run as their first statement after the xtrace guard; `abcd.guardPath` refuses a value with an empty PATH element, which every consumer reads as the current directory; the scratch-directory trap is installed before the three inner mktemps that could exit past it; and `mktemp -d` takes an explicit portable template. The `.gitattributes` classification stops pattern-matching abcd's own line and asks `git check-attr eol` instead — git is already this package's authority for the ignore question, and a later `* text eol=crlf` overrides a line a regex still calls pinned; the line-anchored regex survives only to keep an append from duplicating. The marker regex tolerates a trailing CR, so a CRLF-mangled abcd hook classifies as OURS-but-unpinned and install can heal it rather than reporting a foreign hook for ever. Three residuals are widened rather than fixed, in the brief and the spec: the environment pin narrows common accidental and repo-scoped routes and closes nothing (anyone controlling the committer's environment wins); the example escape keeps content scanning so it cannot smuggle a plaintext banned name, but it protects no copy of a STORE — escaped patterns never match their own text — and a live store carrying the marker exempts every copy of itself; and the marker asserts identity, never integrity, so any file quoting it is treated as abcd's guard. Lifecycle: itd-74 sits in shipped/ with its fidelity review OWED (receipt rcp-3ceed52bdb99). That is the designed follow-up, not an omission — `abcd spec close` stamps the receipt and the review is a separate verb (`abcd intent review`), which record-lint and audit accept; no record file was hand-edited to produce or to clear it.
- dcn-26081600000000072026-08-16 — itd-76 planned at the live interview (spc-31 minted, `intent ready` exit 0). The itd-84 hand-run rendered SPLIT, confirmed: itd-76 narrows to the personal core (source add / ledger / sync-banlist / cite-check + guards); team share/ingest files as itd-126 and paper reconstruction as itd-127 (both `refines` itd-76, seeded criteria marked unconfirmed); the trust rule extracts to adr-41 (proposed) + brief invariant 9 — documents and ledgers never leave the user tier, a public citation requires both gates — which the intents cite rather than declare; the stance files as the consult-freely-cite-deliberately principle. Interview rulings: the share surface is the existing `.abcd/development/research/references.csl.json` research store, not a second `.abcd/work/references.json` exchange file (one committed bibliography — resolves the store clash the sources-plan adversarial review flagged); the draft's "Dogfood (already running)" claim was stale and is rewritten as target (no corpus on the development machine); multi-machine ledger ownership explicitly deferred until a second machine exists; persona Maya corrected to Alice. Seven Given-When-Then criteria authored and accepted in full. Rejected: planning the monolith (both remaining open questions gated only the share/ingest part), and holding for itd-77 (the corpus default path works without relocation). Hand-run graded into the decomposition-calibration note.
- dcn-26092500000000272026-09-25 — The pre-commit guard's sources refresh (itd-76) finds its binary two ways until the product thinker rules on iss-2609250834251447, and the ruling may widen the second (finding 1 of the sources-lane review, adopted by the orchestrator as the safe shape). This repository's copy builds `./cmd/abcd` from the checkout, as `.githooks/commit-msg` does, and never runs an installed abcd; a tree that does not build is one warning line and the commit proceeds. The copy `abcd ahoy` scaffolds refreshes only when the clone opts in with `git config --local abcd.sourcesBinary <absolute path>`, a path to an executable regular file with no PATH search and never an environment variable, and otherwise prints one line and proceeds. That answers none of the three questions the ruling holds (how a scaffolded hook finds abcd, fail open or closed, default or opt-in): it is the shape that leaves them open, so AC3 holds in this repository and in a managed repository that opted in. In both copies the refresh updates an existing keyed private store only, relays the binary's own line, and names a legacy store's migration once per working tree (implementer of the sources fix round).
- dcn-26092500000000282026-09-25 — The build's open-question check reads the record's own settled convention and stays fail-closed (lane implementer, autonomous run A, fix round 1 of the loop lane, on iss-2609252053576085; the lane's first ruling, that every list item under `## Open Questions` is a question whatever it says, lived only in its report and refused intents the record had settled). `intent.OpenQuestions` recognises exactly two markers, learned from the 12 planned intents that carry list items there. A section whose first non-blank line is an italic `_All resolved …_`, with at most one count word (`_All four resolved …_`), is settled whole (itd-111, itd-93); `_All but one resolved_` is not. An item explicitly marked resolved or deferred is not a question: a bold span opening with the word (`**Resolved — …**`, `**Deferred**`, `**Deferred to a follow-up intent**`, `**explicitly deferred**`, `**explicit deferral**`) or the word as a label (`resolved:`, `RESOLVED:`, `Deferred:`), anywhere in the item including its continuation lines. Everything else stays a question: an item led `**Open` whatever it goes on to say (itd-60's "Open, and not gating scope"), an item that only points to another record (itd-76's "travel with itd-126"), `**Out of scope …**`, and a question that merely mentions deferral. With the rule, itd-111 and itd-93 pass the check; itd-7, itd-60, itd-65, itd-66, itd-76, itd-82, itd-117, itd-131, itd-2609170822093401 and itd-2609211116005482 still refuse, each on an item the record does not mark settled. Relabelling those items is the product thinker's call through the planning interview, not the loop's.
- dcn-26092900000000012026-09-29 — Correction to the 2026-09-25 entry on the pre-commit guard's sources refresh (itd-76, iss-2609250834251447): that entry says the scaffolded copy's opt-in shape "answers none of the three questions the ruling holds", and half of that is not so. The template `abcd ahoy` scaffolds (`internal/core/ahoy/defaults/pre-commit`) takes a PROVISIONAL stance on two of the three questions: default or opt-in — opt-in, since nothing runs until the clone sets `abcd.sourcesBinary`; fail open or closed — open, since an unusable opt-in (not an absolute path to an executable regular file) prints one line and the commit proceeds. Only the first question, how a scaffolded hook finds abcd, stays unanswered in the template's own terms, which say it "deliberately does not take" that decision. Both provisional answers stand until the product thinker rules on iss-2609250834251447, and the ruling may replace either. The earlier entry stands as written, since the ledger is append-only (review2-sources finding 5; recorded by lane drainDrift2 of autonomous run A, iss-2609252055533837).
- dcn-26092900000000112026-09-29 — The product thinker answered the twenty-six Group J rulings (plan or close), J1 to J26 (recorded by lane recGroupJ of autonomous run A, for orchestrator abcd-fc). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, section "Group J", dated 2026-09-29, and are restated here because that file is not committed. PLAN: the installer script becomes a minimal starter with an ownership-checked hand-over, planned as a new intent with its planning interview owed (J2, iss-377, draft itd-2609292106557115); token metering and size classes are planned as one intent (J3, iss-2608301744251874 and iss-2608301856299268), and session token accounting in the history store joins that metering intent (J4, iss-2608220150157508; all three in draft itd-2609292107351737); the sources tooling moves into abcd's core with ingest and consult as abcd verbs (J5, iss-27, whose corpus half itd-76 already delivered), and the backfill of the placeholder source entries is part of that intent (J6, iss-55; both in draft itd-2609292108089653); the grill hands off to an installed external interview skill and falls back to its own (J11, iss-165, draft itd-2609292108373494); an opt-in local-model prompt sanitiser is planned (J14, iss-2608261543489261, draft itd-2609292109005937); decision-to-transcript links are planned, with the anchor kind and the link's home settled in the planning interview owed for draft itd-171 (J16, iss-2608290819228175); the dredge synthesis, its write-ups as their own memory source class, is planned together with draft itd-25, whose planning interview is owed (J19, iss-2609211905340006); one provenance register across ingest and vendoring is planned together with draft itd-26, whose planning interview is owed (J20, iss-2609211905346507); release retention, keeping the newest release per version line, is automated by planning draft itd-70 (J21, iss-282); a rules-backend seam with an opt-in CARL adapter is planned, the native loader staying the default (J22, iss-64, draft itd-2609292109214516); core-owned managed pre-commit gates are planned with draft itd-62 (J23, iss-84); a behavioural end-to-end scenario suite per verb family is planned (J24, iss-48, draft itd-2609292109475690). Every draft stays in drafts/ until a person plans it, and each source issue links its draft and names that planning interview in its deferral. BUILD: the teaching plane, the rules-loader safety domain generated from the hazard registry, is built as a lane, the registry being the single source (J10, iss-151 and itd-103; a code lane owns it). RULED DETAILS: `source_kind` carries both, as two separate labels, one for the tool and one for the route (J13, iss-2608230752354928); the prompt router's removal signal is the full list of active domains every time, a domain's absence meaning it has stopped (J15, iss-2608261550580260); setup offers an outside AI service (for example OpenRouter) at install, skippable, once the API adapter ships (J18, iss-2609081951416843, which waits on the API adapter); the salvage-hook timeout is 120 seconds, the wait must show a counter or a message while it runs and never stall silently, and every per-event timeout is pinned in a test (J25, iss-323). J13, J15 and J25 are carried by code lanes. CLOSE: the quick-tunnel preview protocol is closed unless the need recurs (J26, iss-2608230617385431, wontfix with that reason). PARKED ON A TRIGGER: agent payment protocols are kept under watch, not now, revisited as the protocols mature (J8, iss-138); the findings-only skill waits for the skill format to be versioned and stewarded (J9, iss-139); the disposition worksheet as an intent and a site page waits until after the first study (J17, iss-2609021815529020). DECIDE LATER: the Homebrew tap stays parked (J1, iss-380); pluggable search back ends (J7, iss-26); forge-backed record numbering (J12, iss-2608210737264758).
- dcn-26092900000000202026-09-29 — The product thinker answered the twenty-six Group J rulings (plan or close), J1 to J26 (recorded by lane recGroupJ of autonomous run A, for orchestrator abcd-fc). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, section "Group J", dated 2026-09-29, and are restated here because that file is not committed. PLAN: the installer script becomes a minimal starter with an ownership-checked hand-over, planned as a new intent with its planning interview owed (J2, iss-377, draft itd-2609292106557115); token metering and size classes are planned as one intent (J3, iss-2608301744251874 and iss-2608301856299268), and session token accounting in the history store joins that metering intent (J4, iss-2608220150157508; all three in draft itd-2609292107351737); the sources tooling moves into abcd's core with ingest and consult as abcd verbs (J5, iss-27, whose corpus half itd-76 already delivered), and the backfill of the placeholder source entries is part of that intent (J6, iss-55; both in draft itd-2609292108089653); the grill hands off to an installed external interview skill and falls back to its own (J11, iss-165, draft itd-2609292108373494); an opt-in local-model prompt sanitiser is planned (J14, iss-2608261543489261, draft itd-2609292109005937); decision-to-transcript links are planned, with the anchor kind and the link's home settled in the planning interview owed for draft itd-171 (J16, iss-2608290819228175); the dredge synthesis, its write-ups as their own memory source class, is planned together with draft itd-25, whose planning interview is owed (J19, iss-2609211905340006); one provenance register across ingest and vendoring is planned together with draft itd-26, whose planning interview is owed (J20, iss-2609211905346507); release retention, keeping the newest release per version line, is automated by planning draft itd-70 (J21, iss-282); a rules-backend seam with an opt-in CARL adapter is planned, the native loader staying the default (J22, iss-64, draft itd-2609292109214516); core-owned managed pre-commit gates are planned with draft itd-62 (J23, iss-84); a behavioural end-to-end scenario suite per verb family is planned (J24, iss-48, draft itd-2609292109475690). Every draft stays in drafts/ until a person plans it, and each source issue links its draft and names that planning interview in its deferral. BUILD: the teaching plane, the rules-loader safety domain generated from the hazard registry, is built as a lane, the registry being the single source (J10, iss-151 and itd-103; a code lane owns it). RULED DETAILS: `source_kind` carries both, as two separate labels, one for the tool and one for the route (J13, iss-2608230752354928); the prompt router's removal signal is the full list of active domains every time, a domain's absence meaning it has stopped (J15, iss-2608261550580260); setup offers an outside AI service (for example OpenRouter) at install, skippable, once the API adapter ships (J18, iss-2609081951416843, which waits on the API adapter); the salvage-hook timeout is 120 seconds, the wait must show a counter or a message while it runs and never stall silently, and every per-event timeout is pinned in a test (J25, iss-323). J13, J15 and J25 are carried by code lanes. CLOSE: the quick-tunnel preview protocol is closed unless the need recurs (J26, iss-2608230617385431, wontfix with that reason). PARKED ON A TRIGGER: agent payment protocols are kept under watch, not now, revisited as the protocols mature (J8, iss-138); the findings-only skill waits for the skill format to be versioned and stewarded (J9, iss-139); the disposition worksheet as an intent and a site page waits until after the first study (J17, iss-2609021815529020). DECIDE LATER: the Homebrew tap stays parked (J1, iss-380); pluggable search back ends (J7, iss-26); forge-backed record numbering (J12, iss-2608210737264758).
- iss-41lifecycle cannot represent delivered work: v0.1.0 shipped capability from a drafts-stage intent while shipped/ sits empty, so the record goes quiet exactly where it matters; the canonical later-phase list in 03-out-of-scope has drifted from the filesystem it claims lockstep with (superseded itd-47 still listed; drafts itd-76/77/78 missing). Detector (per reality-is-filable): define the interim delivery-state rule (how shipped capability is represented before Phase 4), then a lifecycle lint — no CHANGELOG delivery entry whose intent still sits in drafts/, and out-of-scope lists derived from the filesystem. Acceptance corpus: the v0.1.0 drafts-stage shipment and the two out-of-scope list drifts.
- iss-2609250834251447A managed repository still has no local gate on a commit message carrying a live agent-session URL or a tool attribution footer, and nothing guards the text handed to the forge CLI for a pull request, an issue or a comment. abcd's own repository refuses both shapes in a commit message through its committed commit-msg hook, which runs go run ./cmd/abcd lint outbound from the source checkout; a managed repository has no source checkout, so the scaffolded form of that hook needs three product decisions first: how it finds an abcd binary (a git hook has no plugin root, so only the PATH rung survives), whether it fails closed or open when none is found, and whether abcd ahoy installs it by default or on opt-in. Split out of iss-2609061438431625 when its local half landed for this repository.
- iss-2609252007419997The scaffolded pre-commit template refreshes the sources banlist block by default with a binary found on PATH or in ~/.local/bin, which answers by fiat the three questions iss-2609250834251447 holds as a ruling owed to the product thinker (how a scaffolded hook finds a binary, fail open or closed, default or opt-in). Until that ruling, the template should refresh only on repo-local opt-in, git config --local abcd.sourcesBinary naming an absolute path to a regular file, with no PATH search and never an environment variable, and otherwise print one line and proceed.
- iss-2609252053576085The build's open_questions check (intent.OpenQuestions, read by internal/core/implement/loop/check.go) counts every list item under '## Open Questions' as a question whatever it says, so it refuses records that follow the record's own settled convention: 12 of 50 planned intents carry list items there, and some are settled by their own words, itd-111's section opening '_All resolved or explicitly deferred at planning_' and itd-93's '_All four resolved_', itd-76's item marked '**explicitly deferred**', itd-60's items marked '**Resolved', itd-117's '**Deferred**' items. 'abcd build next' (itd-2609211116005482) would meet refusals nobody planned, and the ruling lives only in a lane report. Keep the check fail-closed, but read the markers the record actually uses.
- iss-261026203129354702-context gives /abcd:library six corpus sub-verbs beside ingest, ask and lint; 07-memory says the shipped library surface carries ingest, ask and lint and nothing else.
- iss-2610262031350187itd-126 drives the sources corpus through a source verb, while the brief keeps the corpus under /abcd:library
- iss-2610262031355278itd-2609292108089653 refuses stubs at source add, while the brief's add for the sources corpus is a library sub-verb
- iss-2610262040442493itd-127 calls the source-to-decision trail 'the provenance ledger'; the ledger glossary calls that trail 'the sources ledger'.
- itd-16`/abcd:audit` Is the Umbrella; Conversation/Edit Chain Is Its Default Application
- itd-78abcd Knows What to Build First — Even When It's Something Small