Development record
Record id allocators (itd-N, spc-N, iss-N) are branch-local: parallel agents on separate branches each scan for max+1 and mint the SAME id. Two intents both claimed itd-82 and both merged to main (PRs #46, #47). The iss-N allocator hit the same class before (iss-77 collision, manually renumbered to iss-79; class recorded as iss-74). Resolving a collision forces a renumber, which breaks the record's stated 'ids are capture-stable, never renumbered' invariant -- so the minting scheme, not the renumber, is the defect. This capture is the MINTING half: a collision-free allocation scheme is needed (forge-minted / random-suffix / timestamp / reserve-registry -- SOTA under research). The DETECTION half is armed separately: intent_lifecycle now blocks duplicate intent ids.
Record id allocators (itd-N, spc-N, iss-N) are branch-local: parallel agents on separate branches each scan for max+1 and mint the SAME id. Two intents both claimed itd-82 and both merged to main (PRs #46, #47). The iss-N allocator hit the same class before (iss-77 collision, manually renumbered to iss-79; class recorded as iss-74). Resolving a collision forces a renumber, which breaks the record's stated 'ids are capture-stable, never renumbered' invariant -- so the minting scheme, not the renumber, is the defect. This capture is the MINTING half: a collision-free allocation scheme is needed (forge-minted / random-suffix / timestamp / reserve-registry -- SOTA under research). The DETECTION half is armed separately: intent_lifecycle now blocks duplicate intent ids.
2026-07-27 instance (itd-101 arc): intent plan itd-101 on a branch off main minted spc-16 while unmerged PR #156 already carried spc-16 (itd-103); the same session's abcd capture then minted iss-146 while #156 already carried iss-146. Both renumbered by hand (spc-17; the duplicate capture folded into this issue). Corpus: two collisions from one planning step, spc-N and iss-N allocators both live.
Frontmatter
| id | iss-80 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-80-record-id-allocators-itd-n-spc-n-iss-n.md
open on GitHub ↗ · commit history ↗
mentions18
- dcn-26072700000000062026-07-27: itd-101 spec minted as spc-16 collided with unmerged #156 (iss-80 class, both spc-N and iss-N allocators); renumbered to spc-17 by hand, duplicate capture folded into iss-80 — ids on this branch deliberately skip 16.
- dcn-26072800000000042026-07-28: itd-104 spec minted as spc-16 (third live iss-80 instance this run: 16 collided with #156, 17 with the itd-101 branch); renumbered to spc-18 by hand.
- dcn-26072800000000052026-07-28: itd-102 spec minted as spc-16 (fourth live iss-80 instance this run); renumbered to spc-19 by hand.
- dcn-26072900000000022026-07-29 — v0.5.0 scoped as "security & consistency" (plan: plans/2026-07-29-v0.5.0-security-and-consistency.md): the security half closes the NEXT.md leak class end-to-end (itd-74/spc-20; the atomic iss-154+157+125+153 detector item; iss-155/156; guard batch iss-159+144+148; boundary fixes iss-30/34), the consistency half retires the record-currency majors (iss-37..44, iss-80) — maintainer added the latter rather than deferring them. Version is derived by launch ship, not declared; 0.5.0 is the prediction given itd-74 additive. Deferred majors listed explicitly in the plan (largest: iss-124); the 2026-07-24 queue's pick-up role is superseded.
- dcn-26080500000000032026-08-05 — iss-80 (v0.5.0): resolved as already-fixed, with the residual verification gap closed rather than the defect re-fixed. The branch-local id-collision class this item tracked — `itd-N`/`spc-N`/`iss-N` allocators scanning only the working tree, so two agents on branches cut from one base each mint the same id and collide at merge — was already closed by iss-115 and iss-120, which introduced `recordid.MaxAcrossRefs` (scan every git ref for the family's highest committed id) and folded it into all three minting paths: `internal/core/spec/store.go`, `internal/core/intent/create.go`, and `internal/core/capture/workflow.go`. What survived was uneven evidence, not an uneven fix: only the `spc-N` family had an end-to-end regression test standing up a real two-branch history (`internal/core/spec/refunion_test.go`), while none of capture's or intent's existing tests stood up a real two-branch history through `Capture`/`CreateFromText` and so never drove the ref scan through their public entry points at all — `MaxAcrossRefs` was exercised only as a primitive in its own package test, never as wiring. That is the shape in which a fix silently un-wires: a refactor that dropped the `MaxAcrossRefs` call from `Capture` or `CreateFromText` would have left every existing test in both packages green. So this round adds `internal/core/capture/refunion_test.go` (`TestCaptureMintsPastACommittedBranch`) and `internal/core/intent/refunion_test.go` (`TestCreateFromTextMintsPastACommittedBranch`), each mirroring the spec test's exact fixture via `internal/gittest.NewRepo` — branch A mints and commits record 1, branch B is cut from before that commit so its working tree carries no record, and B must mint record 2. Both were mutation-checked before being trusted: with `MaxAcrossRefs` stubbed to return an empty `RefScan`, each fails with precisely the collision it names (`iss-1`/`itd-1` re-minted on branch B), and all three families fail together — so the tests are pinned to the ref-union behaviour rather than passing incidentally. No production code was touched, which is the point: the minting paths were verified correct by reading before the coverage was written, and the change is test-and-record only. Impact recorded as `internal` and no CHANGELOG entry, matching the iss-34 precedent for a test-only closure with no user-facing behaviour change.
- dcn-26080500000000042026-08-05 — CORRECTION to the entry immediately above, from a merge-gate record-accuracy review on PR #195. That entry, and iss-80's own `resolution:` field, said the branch-local id-collision class "was closed" by iss-115/iss-120. It was not: `internal/core/recordid/recordid.go`'s own package doc documents an ACCEPTED, undocumented-in-that-entry residual window — two branches that BOTH mint before either commits still collide — left to the already-armed record-lint uniqueness detectors (`issue_id_unique`, `intent_lifecycle`, `spec_id_unique`) as backstop, the same trade-off iss-120's own resolution names verbatim ("the armed record-lint detectors as the residual-window backstop"). iss-80's body asked for a fully collision-free minting scheme (forge-minted / random-suffix / timestamp / reserve-registry); that was never built, that trade-off was made when iss-115/iss-120 landed rather than here, and no open item currently tracks the residual window separately. Both iss-80's `resolution:` field and this log now say "already addressed, not closed to zero" and name the trade-off explicitly, rather than implying the class was eliminated. The test-coverage work in the entry above (the two new refunion tests) is unaffected and stands as described.
- dcn-26080500000000082026-08-05 — iss-118: DECISIONS.md and ACKNOWLEDGEMENTS.md gain `merge=union` in `.gitattributes`, the same remedy already in place for CHANGELOG.md. Both are the identical shape — append-only ledgers of anonymous, dated, order-independent entries that never need identity — so a concurrent-append conflict has nothing to actually disagree on; the union driver keeps both sides instead of stopping the merge. Prompted directly by the bug-hunt loop's `bugfix/iss-184-…` branch hitting exactly this conflict against `.abcd/work/DECISIONS.md` on PR #199, which iss-118 had already diagnosed (filed after an earlier merge hit the same conflict) but left unresolved pending this design call. Full atomicisation (per-decision records, a new id family, an armed uniqueness detector) was the other option iss-118 named; not pursued here as disproportionate to a minor, low-traffic hotspot. Impact recorded as `internal`, no CHANGELOG entry — matching the iss-80 precedent for a process/tooling change with no user-facing behaviour.
- iss-74Duplicate iss-56 id: two open issues share id iss-56 — iss-56-iss-36-lists-abcd-logbook-as-a-retired.md and iss-56-managed-pre-commit-gates.md. The allocator scans max N across open/resolved/wontfix under flock+O_EXCL, so a collision implies a manual add that bypassed it or an allocator gap. Ledger-integrity bug: derived priority, blocked_by edges, and resolve/wontfix by id are all ambiguous while two files answer to iss-56. Detector (per unrecognized-input-never-writes / ledger integrity): a capture/record-lint check that ids are unique across the three status dirs; acceptance corpus = the two iss-56 files. Fix: renumber the later-created one to the next free id and repoint any inbound blocked_by.
- iss-77launch payload completeness: .abcd/config/launch-payload.json includes only [.claude-plugin, commands, scripts, docs, README, LICENSE, .gitignore] and omits agents/ and hooks/, both of which exist and are auto-discovered Claude-Code plugin surfaces (agents are referenced by the harness; hooks are the prompt-router injection transport). The iss-31 'omits skills/' instance is now STALE (no skills/ dir — reclassified to commands/abcd/ on 2026-07-11, already shipped via the commands include). Deciding the exact public includes set (do agents/ and hooks/ ship as-is? do hooks reference a binary path that must resolve post-install?) is design-shaped — maintainer call. Detector: a bundle-completeness test asserting every auto-discovered plugin surface dir is either included or explicitly excluded-with-reason.
- iss-79fifth divergent atomic write: internal/core/memory/ingest.go storeOriginal (~:806) is an inline temp(.memtmp,O_EXCL)+fsync+rename durable write that iss-32's consolidation left untouched — it is inline, not a named func, so TestNoNonCanonicalAtomicWritePrimitives (name-based) cannot catch it, and it was outside iss-32's named 4-copy corpus. It differs from canonical fsutil.WriteFileAtomic: no parent-dir fsync, no explicit chmod. Consolidate it onto fsutil.WriteFileAtomic(target, material.rawBytes, 0644) (keeping the pre-existing sources-dir symlink guard), OR extend the canonical-primitive detector to catch inline temp+rename sequences. Safe today (has its own symlink guard); this is a completeness/consistency follow-up.
- iss-146guard-bash false positive class: a compound command chaining a gh write verb with a LOCAL command taking an absolute path argument (gh pr merge ... ; git worktree remove /abs/path) is blocked by the line-level privacy regex, which cannot see that the path never reaches GitHub. Second guard incident of 2026-07-27 (the first, blocking --no-verify, was a true positive). Both are fixture material for itd-103's calibration corpus: this one is a known-good case the TNR floor must protect; the workaround (split the compound) is recorded here so the friction is not silent.
- iss-2608210737265820intent audit ingest appends the INGESTED verdict block at end-of-file, so when a shipped intent carries link-reference definitions after its Audit Notes heading (itd-114: the iss-80 link ref) the block lands below them — rendering fine but visually detached from the Audit Notes section it belongs to. The writer should insert under the Audit Notes heading (or above trailing link-ref definitions) rather than appending at EOF
- itd-82`abcd drain` fixes the issues that need no decision, and hands the rest back by kind
- itd-101**Every citation abcd publishes is provably alive and honestly labelled — and the gate that enforces it never flakes.** A cited reference page is only as trustworthy as its links, and links rot silently: pages retitle, URLs redirect, whole platforms announce their own shutdown. abcd splits citation validation across a deterministic gate and an explicit refresh. An offline lint family checks structure and source policy on every commit — footnote markers and definitions in bijection, URLs and DOIs well-formed, aggregator domains refused — with zero network in the gate. `abcd docs cite refresh` does the live fetching on demand and writes a committed baseline: per URL, the final resolved address, when it was last checked, and whether verification was automatic or manual — never how. Sources that block automated fetchers join a manual queue the maintainer clears link by link: printed as a checklist first, later as a generated, disposable checklist page that hands back a receipt file the verb ingests. The lint then enforces the baseline offline — no broken entries, no stale entries, no cited URL without a receipt, no citation whose recorded final address has drifted. The engine is native and dependency-free first; a specialist link checker can slot in later behind the same seam. "My release gate stays deterministic and my citations stay alive," said Kira, an open-source maintainer. "When a source needs human eyes, abcd hands me the exact list to click through and records that I did — not how I did it."
- itd-103**abcd teaches repo agents the shell commands they must never run — and blocks them when they try.** An agent that runs `cd scratch && rm -rf *` is one failed `cd` away from deleting the working tree, and the facilitator watching the session usually has no way to know that. abcd ships a hazard registry: each entry a dangerous command pattern, its severity, the safe successor, and a plain-language why. One registry, two planes. The rules loader injects the matched safety rules before shell-heavy work, so the agent is taught the safe form up front; a deterministic guard — a core verb any harness hook can call — refuses a matching command at execution time and replies with the safe successor, so the block itself is the lesson. Hosts without hook support still get the teaching plane; hosts with hooks get both, wired in at install. The registry grows from reality: a facilitator who sees something scary needs to know exactly one move — capture it — and recurring captures promote patterns into the bundled defaults, each entry shipping with a fixture that proves its guard fires. "I couldn't have told you why that command was dangerous," said Nia, a facilitator. "I didn't have to. The guard refused it, told the agent what to run instead, and told me why in words I understood. The one time something new scared me, I captured it and moved on."
- itd-114Two Agents Can Mint At The Same Instant And Never Collide
- spc-16abcd-teaches-repo-agents-the-shell-commands-they-must-never
- spc-17every-citation-abcd-publishes-is-provably-alive-and-honestly