Development record
A Resolved Issue Points At What Fixed It
Press Release
A resolved issue now points at what fixed it. abcd capture resolve <iss-N> "<note>" --impact fix --intent itd-N --spec spc-N --commit <sha> stamps the
structured provenance the schema has modelled all along — the resolved_by
pointer that was parsed on read and written by nothing. "When I close an issue
I name the intent, spec, or commit that fixed it, and six months later the
trail is still there," says Nia, facilitator.
Why This Matters
Step 12 of the twelve-step record walk closes the issue but loses the trail:
ResolvedBy{Intent, Spec, Commit} is parsed on read while Resolve writes
only resolution and impact, so a resolved issue asserts it was fixed in
prose but cannot point at what fixed it — while the intent side of the same
record store binds its verdicts to a SHA-256 receipt. One record store, two
evidence standards. This intent closes the resolved_by half of iss-245 (the
promoted_to (historical) half is itd-119); when both ship, iss-245 itself resolves with
provenance — the first entry in the ledger to carry the trail.
Scope Conditions
None stated.
Acceptance Criteria
BDD format, per the itd-1 discipline. Walked and confirmed by the maintainer, 2026-08-16.
- Given an open issue, when resolve runs with any of
--intent itd-N,--spec spc-N,--commit <sha>, thenresolved_byis written with exactly the supplied members, alongsideresolutionandimpact, in the same atomic transition toresolved/. - Given
--intentor--spec, then the id must exist in its record store (any bucket, open or closed);--commitis shape-checked only (7–64 hex characters). An unknown id or malformed value refuses the whole transition — nothing written, the issue stays open. - Given no provenance flags, then resolve behaves exactly as today: no
resolved_bykey is written at all — provenance is optional, never defaulted, never guessed. - Given
--json, then the result reports the transition and the writtenresolved_bymembers. - Given the change, then
wontfixis untouched — a non-action ships nothing and points at nothing. - Given the sweep, then
commands/capture.mdand the issues README document the provenance flags.
SOTA
Close-with-reference is standard tracker practice (GitHub "fixes #N" commit
links, GitLab closing patterns, Jira issue links); nothing importable — the
provenance lands in abcd's native frontmatter schema, already modelled.
Chosen path: bespoke, extending the existing Resolve transition and its
atomic-write machinery. No new dependency.
Open Questions
None gating. Backfilling resolved_by onto already-resolved issues was
ruled out of scope at the grill, 2026-08-16 — the verb closes the gap going
forward.
Audit Notes
Fidelity review — receipt rcp-b741cdb3204d (verifier abcd:intent-auditor claude-fable-5-1).
Provenance: abcd:intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:1b78d748d8fbc6654013fa3563875ad19302a5986ba546eba3bf3b6e8bc3ee6c Input attestations: diff:tree at da7b7cf409b41758b3502d3873687dc8b817d8c4 (worktree HEAD; the host supplied no commit range, so the whole tree at that commit is the delivered reality)@-;
Acceptance rollup: MET 5 · MET_WITH_CONCERNS 1 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: Resolve appends only the supplied members as a nested resolved_by object to the extras that ride the single transition call beside impact and the resolution note; the core test writes all three members and the partial test writes a subset. evidence: internal/core/capture/workflow.go:266 — "if rb != nil {" evidence: internal/core/capture/workflow.go:279 — "extras = append(extras, kv{"resolved_by", members})" evidence: internal/core/capture/workflow.go:281 — "res, err := transition(req.RepoRoot, req.IssuesRoot, req.ID, "resolve", "resolution", req.Resolution," evidence: internal/core/capture/resolve_provenance_test.go:33 — "func TestResolveWritesResolvedBy(t *testing.T) {" evidence: internal/core/capture/resolve_provenance_test.go:78 — "func TestResolvePartialProvenance(t *testing.T) {"
- ac-2 — MET_WITH_CONCERNS: resolveProvenance runs before the move: intent and spec ids are shape-checked then probed for existence in any bucket, the commit is shape-checked only, and the refusal test proves the issue stays open with its bytes unchanged for every bad value; concern: the sha regex admits lowercase hex only, narrower than the criterion's 7-64 hex characters (git itself only emits lowercase, so the gap is theoretical).
evidence: internal/core/capture/workflow.go:298 — "func resolveProvenance(req ResolveRequest) (*ResolvedBy, error) {"
evidence: internal/core/capture/workflow.go:308 — "if _, ok := findRecordFile(repoRoot, intentStoreRelDirs(), req.ByIntent); !ok {"
evidence: internal/core/capture/capture.go:327 — "reCommitSha = regexp.MustCompile(
^[0-9a-f]{7,64}$)" evidence: internal/core/capture/resolve_provenance_test.go:128 — "t.Fatalf("%s: issue must stay open, got status=%q err=%v", name, status, ferr)" evidence: internal/core/capture/resolve_provenance_test.go:135 — "t.Fatalf("%s: refused resolve mutated the issue", name)" - ac-3 — MET: With no member supplied resolveProvenance returns nil and the extras never gain a resolved_by key; the flagless regression test guards it. evidence: internal/core/capture/workflow.go:299 — "if req.ByIntent == "" && req.BySpec == "" && req.ByCommit == "" {" evidence: internal/core/capture/resolve_provenance_test.go:143 — "func TestResolveFlaglessWritesNoResolvedBy(t *testing.T) {"
- ac-4 — MET: TransitionResult carries the written members under a resolved_by json tag and the CLI surface test decodes id, to_status and the exact members from the --json envelope.
evidence: internal/core/capture/capture.go:245 — "ResolvedBy *ResolvedBy
json:"resolved_by,omitempty"" evidence: internal/core/capture/workflow.go:286 — "res.ResolvedBy = rb" evidence: internal/surface/cli/capture_surface_test.go:312 — "func TestCaptureResolveProvenanceJSON(t *testing.T) {" evidence: internal/surface/cli/capture_surface_test.go:355 — "t.Fatalf("resolved_by members wrong: %+v", r.ResolvedBy)" - ac-5 — MET: WontfixRequest has no provenance members, so nothing on that path can write resolved_by, and the plugin page states it.
evidence: internal/core/capture/capture.go:220 — "type WontfixRequest struct {"
evidence: commands/capture.md:311 — "back in the JSON as
resolved_by.wontfixtakes no provenance — a non-action" - ac-6 — MET: The plugin page documents the three flags with the existence and shape rules, and the issues README documents the field and the flags that write it.
evidence: commands/capture.md:289 — "
resolvealso takes optional provenance — the structuredresolved_bypointer" evidence: .abcd/work/issues/README.md:127 — "-resolved_by— optional pointer object (intent,spec,commit) naming"
Gap audit:
- honoured:
- a resolved issue points at what fixed it through a structured pointer written in the same transition evidence: internal/core/capture/workflow.go:222 — "// resolved_by object (spc-25) in the same atomic transition. Ids are validated"
- provenance is optional, never defaulted, never guessed evidence: internal/core/capture/workflow.go:226 — "// resolved_by key at all: provenance is optional, never defaulted."
- diverged:
- the commit sha accepts 7-64 hex characters
evidence: internal/core/capture/capture.go:327 — "reCommitSha = regexp.MustCompile(
^[0-9a-f]{7,64}$)"
- the commit sha accepts 7-64 hex characters
evidence: internal/core/capture/capture.go:327 — "reCommitSha = regexp.MustCompile(
- missing: (none)
Frontmatter
| id | itd-120 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | major |
.abcd/development/intents/shipped/itd-120-a-resolved-issue-points-at-what-fixed-it.md
open on GitHub ↗ · commit history ↗
References1
- itd-4Nothing You Notice Gets Lostbuilds on
Referenced by2
- iss-2610262043143346The facilitator persona is Carol in itd-115 and Nia in itd-120.related
- spc-25a-resolved-issue-points-at-what-fixed-it-abcd-capture-resolvimplemented by
mentions5
- dcn-26092300000000232026-09-23 — The payload example in spc-2609231435545473 (closed) is stale on one point: its itd-121 quote opens `"... what I'd do next,"`, a truncation the release-page ingest refuses by design, since a quote is a whole quoted sentence as the press release has it. The example's verb is right: the ingest accepts an attribution after `says` as well as after `said`, because six intents of that release (itd-119, itd-120, itd-121, itd-123, itd-124, itd-125) quote as `says <Name>, <role>.`. The closed spec stays as written; `TestQuoteSaysFormFromARealRecord` carries a working `says` quote, taken from itd-121 as its record has it (lane implementer, on review 2 of the release-page build).
- 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.
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-119An Issue Graduates Into An Intent Without Retyping
- itd-2609231013154443Every release arrives with a press release composed from what shipped