Development record
source-provenance-ledger
Summary
Delivers the personal core of the sources corpus as native abcd verbs:
abcd source add, abcd source ledger, abcd source sync-banlist, and
abcd source cite-check, operating on the user-level home
(~/.abcd/sources/ by default; relocation is itd-77's concern). Team
share/ingest and paper reconstruction are out of scope here — they are
itd-126 and itd-127, both refines itd-76. The trust boundary the verbs
enforce is adr-41 / brief invariant 9, cited, not restated.
Scope
- Store layout (per the 2026-07-08 scaffold plan and SOTA note):
sources.json— a CSL-JSON array whosecustomblock carriesconfidential,permission_status,keywords,aliases,ban_authors; per-source foldersconfidential/<key>/andpublic/<key>/holding the original, extracted text, and derived artefacts — folder location is the classification;ledger/<repo>.jsonl— one append-only file per consuming repo. The corpus directory is itself a no-remote git repository; its history is the tamper-evidence layer (itd-16 is a possible later backend; nothing depends on it). - Verbs (transport-agnostic core + CLI/plugin front doors, per the
wired-or-it-isn't-done rule):
add— registers a document: writes the CSL-JSON entry, stores the files under the class folder, commits the corpus repo. Declassification isgit mv confidential/<key> public/<key>plus the entry update — a visible move, never a silent flag edit.ledger— appends{ts, repo, decision_ref, claim, source_key, locator, influence, cited_publicly}and commits; corrections are new lines. Thecited_publicly: trueflip is a distinct invocation that checks the source'spermission_statusfirst and refuses, naming the failing gate, when permission is absent (adr-41 gate 2 exercising gate 1).sync-banlist— projects confidential entries' titles and aliases (authors only underban_authors: true) into the repo's untracked itd-74 private banlist block, between generated-block markers it owns.cite-check— scans stdin or a file; reports offending entries by key only so its own output is shareable.
- Guard wiring: the pre-commit guard calls
sync-banlist --refreshwhen a corpus is present; when absent, every corpus-dependent step prints a one-line "corpus absent — skipped" notice (loud staging) and exits 0.
Approach
Go core package (internal/core/source) owning store I/O, classification
moves, ledger append, and the two-gate check; front doors stay thin. All
matching for banlist and cite-check reuses the itd-74 machinery rather than
growing a second matcher (one canonical primitive) — note the recorded
RE2/ASCII word-boundary hazard there: matching must use Unicode-aware
predicates, not \b. No network anywhere (brief invariant 7); no new
dependencies — CSL-JSON parses with encoding/json.
Acceptance criteria → design
- Add & classify —
addwrites entry + files under the declared class folder; the folder path is derived from, and only from, the declared class at ingestion. - Ledger append-only —
ledgeropens the per-repo JSONL in append mode; there is no edit path; the corpus commit history proves it. - Banlist projection —
sync-banlistregenerates its owned block; the itd-74 pre-commit guard, already shipped, refuses banned strings; the refresh hook makes the block at most one commit stale. - Safe scan —
cite-checkoutput contains keys and byte offsets only, never the matched strings themselves. - Two-gate flip — the flip subcommand hard-fails on
permission_status≠ permitted, and a successful flip is itself an appended line. - Loud absence — every verb and the guard hook detect a missing corpus and say so on one line, exit 0 (guard) / a distinct no-corpus exit (verbs).
- Visible declassification — the move updates class by location; the next
sync-banlistrun drops the moved key's strings; flip eligibility followspermission_status, which declassification sets.
First validation
Re-establish this repository's own corpus through the shipped verbs (the dogfood-target section of the intent): ingest the acknowledgements baseline's public sources, wire the guard, and run one full consult→ledger→flip cycle.
Frontmatter
| id | spc-31 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-31-source-provenance-ledger.md
open on GitHub ↗ · commit history ↗
References1
- itd-76abcd Lets You Consult Sources You Cannot Cite — and Remembers Every Debtimplements
mentions9
- adr-41Documents and ledgers never leave the user tier; a public citation requires both gates
- 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.
- 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.
- itd-16`/abcd:audit` Is the Umbrella; Conversation/Edit Chain Is Its Default Application
- itd-50The Audit Loop Drives An Intent To Acceptance — Or Calls For A Replan
- itd-74abcd Keeps the Names You Ban Out of Everything You Publish
- itd-77abcd's User-Level Home Lives Where You Want It
- itd-126A Team Shares One Bibliography Without Sharing Anyone's Corpus
- itd-127A Paper Is Reconstructed From the Provenance Ledger