Development record
`abcd capture resolve` exists and works. Nothing anywhere can fail when it is
abcd capture resolve exists and works. Nothing anywhere can fail when it is
not run, so an issue that has in fact been fixed stays in open/ until a human
remembers, and a forgotten one leaves no marker to find it by. The measurement:
328 issues in resolved/, 78 carrying resolved_by — the provenance
convention holds about 24% of the time, and the resolution convention itself
has no denominator, because an unresolved-but-fixed issue is indistinguishable
from an open one.
The specific mechanic that forces the forgetting is resolved_by.commit. Every
other field in a resolution is knowable while the record is being edited; the
fixing commit's sha is not, because the record and the fix are in the same
change. That makes stamping a second step deferred past merge, and a second
step is what gets dropped.
Two working patterns exist in the tree, neither enforced:
- The bughunt routine resolves in the SAME pull request, citing the preceding commit on its own branch (b783352f cites 52988d9). This works and is the right shape: no post-merge step exists to forget.
- The
--commitflag is shape-checked only,^[0-9a-f]{7,64}$(internal/surface/cli), so a stamp naming a commit that no longer exists — or never did — is accepted and reads exactly like a good one.
Audited 2026-08-24: all 76 stamped shas currently in the ledger ARE reachable
from origin/main, so the practice is correct wherever it has been applied.
The exposure is latent rather than realised, and it is not what the standing
practice note assumes. That note reasons that branch commits land unchanged
because the repository merges with merge commits; the repository in fact
allows all three methods (merge_commit=true squash=true rebase=true), the
method is a per-pull-request choice, and under squash or rebase the cited
branch sha is rewritten out of existence with nothing detecting it.
The deeper observation is that resolved_by.commit is denormalised: the
provenance is already in git and is derivable under every merge method,
because it is read from main's own history rather than from a branch.
Verified on iss-97, which stores no resolved_by at all:
git log --diff-filter=A -- .abcd/work/issues/resolved/iss-97-*.md
-> 51566371 fix: guard ahoy.Detect marker/config reads against FIFO and
huge files (iss-97), merged by 94fac3ef (#105)
Storing the sha is what makes the step unschedulable; deriving it removes the ordering problem rather than automating around it.
Direction, not a decision: a Resolves: iss-N commit trailer plus a gate in
the scripts/check-*.sh family — the shape check-attribution.sh already
uses to walk base..head — asserting that a declared trailer is accompanied
by the ledger move in the same diff, and that any resolved_by.commit present
names a commit reachable from main. That family runs in make preflight and
in CI, so it fails at push time rather than after a merge. The routing, the
trailer spelling, and whether resolved_by.commit should be written at all or
computed on demand are a maintainer's call.
Supersedes the live half of iss-245, which observed that the schema modelled
provenance the verbs could not write. Both halves of that observation have
since shipped: capture resolve takes --intent/--spec/--commit, and
capture promote mints and stamps promoted_to (3 issues now carry it). What
remains is one layer down — the verbs exist, and nothing compels or verifies
their use.
Frontmatter
| id | iss-2608241347321757 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-2608241347321757-resolving-an-issue-is-a-convention.md
open on GitHub ↗ · commit history ↗
mentions5
- iss-97ahoy.Detect reads CLAUDE.md/AGENTS.md/config via plain os.ReadFile with no size cap or O_NONBLOCK, unlike the hardened session-end transcript read; a FIFO or multi-GB file at cwd could hang or slurp any hook that calls Detect (session-start, session-end, ahoy verbs). Harden Detect's marker/config reads to match maxTranscriptBytes plus a non-blocking open.
- iss-245The capture store's schema models resolution provenance it has no verb to write. ResolvedBy{Intent,Spec,Commit} (capture.go:72) is parsed on read (validate.go:275) but Resolve (workflow.go:162) writes only resolution and impact and exposes no flag to supply it, so a resolved issue can assert it was fixed in prose but cannot point at the intent, spec or commit that fixed it. PromotedTo (capture.go:92) is validated against ^itd-[0-9]+$ (validate.go:121) and documented in the issues README as 'the itd-N this issue graduated into', yet no verb writes it and no issue in the ledger carries it — so the capture-to-intent promotion the schema anticipates is unreachable, which is why the 'Which ledger?' note in commands/intent.md forces the intent-vs-issue call at the moment of lowest information with no way to revise it. Contrast the intent side, where the fidelity review binds its verdict to a sha256 receipt over the acceptance criteria: one record store, two very different evidence standards.
- iss-2608241612007530the issue-resolution gate is triggered by the trailer, so a fix that lands without one is invisible to it. RS001 fires only on a commit carrying a Resolves trailer, and RS002/RS003 only on stamps that already exist, so a merged fix whose commit names no issue passes all three rules and leaves its record in open/ — the exact backlog iss-2608241347321757 was built to stop, reached from the other direction. Measured 2026-08-24: 1315 commits name an iss- id somewhere in the message and 3 carry the trailer. iss-202 is the standing instance: its fix merged on 2026-08-24 in a commit ending with a bare 'iss-202' line rather than the trailer form, and its record sits in open/ with the fix shipped. The missing detector is the inverse direction — a change touching code an open record describes, declaring no resolution.
- iss-2608241845109280abcd site build refuses a committed record, so the site render fails on main and nothing catches it before CI. .abcd/work/issues/resolved/iss-2608241347321757-...md:52 carries a four-space indented code block, and the renderer supports a fixed subset only: 'unsupported markdown construct: indented code block: fence code with backticks so it renders as a copyable command — the site renders a fixed subset and never passes text through unrendered'. The binary is behaving as designed; the record is non-conforming. It reached main because the site-screenshots workflow is path-filtered and the ci changes classifier stands jobs down for a pull request confined to .abcd/, so PR 489 never ran a site build, and the failure only surfaced on the first later PR touching a watched path (the v0.6.4 release PR 497). Two gaps, not one: the record's markdown is wrong, and record-lint does not check records against the renderer's supported subset even though every record under .abcd/work/issues/ is site-rendered. The second is the detector; the first is its acceptance corpus. Note screenshots is not a required status check, so this never blocked a merge.
- the-record-lands-with-the-actThe record lands with the act