Development record
A new capture or draft is matched against the record before it is written
Press Release
A new issue or draft is matched against the record at filing; a likely double is linked, named, and never dropped.
"I filed the same finding twice a month apart and nobody noticed until a consistency pass," said a technical facilitator. "Now the capture tells me at filing that it looks like iss-N, writes the link, and leaves it to me to confirm. Nothing is refused: a wrong match is a link I remove, not a finding I lost."
Why This Matters
Three places look for a match today, none at filing: the pre-pass at planning (itd-42), the drain before it captures, and itd-87's recurrence rule after closure. The itd-84 discipline names a capture-time validator as its next rung. The research of 2026-09-21 on unattended triage is plain about the failure to avoid: a matcher that refuses is the one that loses findings silently. Ruled: file it, link it, say so.
Mechanism
We expect a typed link written at filing to make doubles visible where a later triage never finds them, because the moment of filing is the only moment both records are in one hand; shown wrong if the next consistency pass still finds unlinked doubles filed after this ships.
Scope Conditions
- Holds for text long enough to compare; a one-line capture below the declared minimum is filed without matching and says so.
What's In Scope
- The match:
captureand the quoted-textintentcreate compare the new text with every open and resolved issue and every intent's title and press release, by the term-overlap heuristic the embark ranking uses, declared a heuristic. - The link: a likely match is written onto the new record as
duplicates:(near-identical) orrefines:(narrower), naming the candidate; the verb prints the match and the link; nothing is refused or dropped. - The confirmation: a person or a later pass confirms or removes the link; a record whose link is removed is ordinary.
- The configuration: threshold and compared fields declared with a bundled default; below the threshold nothing is written and
--jsonlists the near misses. - The rung: the itd-84 discipline's capture-time validator is marked delivered by this record.
What's Out of Scope
- Refusing or merging records.
- Matching across repositories.
- Semantic matching by a model (the heuristic is lexical; a host pass is a later rung).
Decisions
Ruled by the product thinker on 2026-09-21, in the interview that filed and planned this intent (adr-2609212115255771 records the vocabulary rulings it rests on):
- File it, link it, say so; never refuse, never drop (ruled 2026-09-21).
- A lexical heuristic, declared as one; the threshold is configuration.
Open Questions
None open.
Acceptance Criteria
- Given a new capture whose text overlaps an existing issue above the threshold, when it is filed, then the record is written with a
duplicates:orrefines:link naming the candidate, and the verb prints the match. - Given a new draft intent whose text overlaps an existing intent's title or press release, when it is created, then the same link is written and printed.
- Given any match, when filing completes, then no record was refused or dropped; removing the link leaves an ordinary record.
- Given overlap below the threshold, when filing completes, then nothing is written for it and
--jsonlists the near misses with their scores. - Given the itd-84 discipline record, when it is read after this ships, then the capture-time validator rung reads delivered by this record.
Audit Notes
Fidelity review — receipt rcp-1887e5f574ef (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:55cacd764222cf6126e4455ac627a86ccad5b272fd5e58b31e6d99d69a11c824 Input attestations: tree:ceb4b6dbb97622bddf2401c91f9f808ed05060a0 (origin/main; internal/core/record/match, internal/core/capture/match.go, internal/core/intent/match.go, internal/surface/cli/match.go)@-;
Acceptance rollup: MET 5 · MET_WITH_CONCERNS 0 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: the capture workflow runs matchAndLink under the ledger lock before the write, writes duplicates:/refines: into the record's frontmatter, and the CLI prints each link written; TestCaptureLinksAPlantedDouble and TestCaptureVerbLinksAndPrintsTheMatch exercise both ends and pass at BASE evidence: internal/core/capture/workflow.go:267 — "content, matched = matchAndLink(repoRoot, issuesRoot, *req.Match, req.Text, content, fm)" evidence: internal/core/capture/match.go:78 — "func matchAndLink(repoRoot, issuesRoot string, cfg match.Config, text, content string, fm map[string]any) (string, *match.Outcome)" evidence: internal/surface/cli/match.go:36 — "func renderMatch(w io.Writer, o *match.Outcome)" evidence: internal/core/capture/match_test.go:66 — "func TestCaptureLinksAPlantedDouble" evidence: internal/surface/cli/match_cli_test.go:67 — "func TestCaptureVerbLinksAndPrintsTheMatch"
- ac-2 — MET: the quoted-text intent create matches title plus press release under the mint lock against every intent's H1 and press release (and the ledger), seeds the typed links into the draft, and the CLI prints the outcome; TestCreateFromTextMatchedLinksADouble and TestIntentCreateVerbLinksAndPrintsTheMatch pass at BASE evidence: internal/core/intent/create.go:324 — "outcome = runMatch(opts.Match, opts.Title+"\n"+opts.PressRelease)" evidence: internal/core/intent/create.go:468 — "for _, rel := range []match.Relation{match.Duplicates, match.Refines} {" evidence: internal/core/intent/match.go:35 — "func MatchTexts(repoRoot string) ([]MatchText, error)" evidence: internal/surface/cli/cli.go:2686 — "it, err := intent.CreateFromTextMatched(repoRoot, text, opts, m)" evidence: internal/surface/cli/match_cli_test.go:120 — "func TestIntentCreateVerbLinksAndPrintsTheMatch"
- ac-3 — MET: an unreadable candidate set, a refused configuration or a link the schema refuses all come back as an outcome with the record filed unlinked, never an error; a record whose link line is removed is listed back as ordinary; TestCaptureIsNeverRefusedByTheMatch, TestCreateFromTextMatchedNeverRefuses, TestCaptureVerbFilesThroughARefusedConfiguration and TestRemovingTheLinkLeavesAnOrdinaryRecord pass at BASE evidence: internal/core/capture/match.go:113 — "o.Skipped = fmt.Sprintf("the links could not be written (%v), so the record is filed unlinked", err)" evidence: internal/surface/cli/match.go:26 — "so the record is filed without matching; fix or remove the key" evidence: internal/core/capture/match_test.go:176 — "func TestRemovingTheLinkLeavesAnOrdinaryRecord" evidence: internal/core/intent/match_test.go:71 — "func TestCreateFromTextMatchedNeverRefuses"
- ac-4 — MET: a score below the threshold takes no Relation and is never linked, up to five near misses travel on the outcome with score and reverse, the threshold and compared fields come from match.threshold / match.fields through the layered reader with bundled defaults, and --json carries near_misses; TestCaptureBelowTheThresholdWritesNothingAndListsNearMisses, TestCaptureVerbJSONListsNearMisses and TestTheThresholdIsHonoured pass at BASE evidence: internal/core/record/match/match.go:317 — "if len(o.NearMisses) < NearMissLimit {" evidence: internal/core/record/match/config.go:67 — "th, err := layered.Get(s, "match.threshold", DefaultThreshold," evidence: internal/core/capture/match_test.go:120 — "func TestCaptureBelowTheThresholdWritesNothingAndListsNearMisses" evidence: internal/surface/cli/match_cli_test.go:89 — "func TestCaptureVerbJSONListsNearMisses"
- ac-5 — MET: the itd-84 discipline record carries a Delivered rung paragraph naming this intent as what delivers the capture-time candidate pass, and says what it does not deliver evidence: .abcd/development/intents/disciplines/itd-84-intent-decomposition.md:63 — "Delivered rung: the capture-time candidate pass. The lexical shortlist of rule 2 runs at filing, delivered by" evidence: .abcd/development/intents/disciplines/itd-84-intent-decomposition.md:65 — "[itd-2609212137116617] (../shipped/itd-2609212137116617-a-new-capture-or-draft-is-matched.md)"
Gap audit:
- honoured:
- file it, link it, say so; never refuse, never drop (decision 1) evidence: internal/core/intent/match.go:105 — "The match never refuses the create" evidence: internal/core/capture/match.go:76 — "It never fails the capture"
- a lexical heuristic declared as one on every outcome, threshold as configuration (decision 2) evidence: internal/core/record/match/match.go:31 — "const Heuristic = "lexical term overlap, weighted by how rare each term is across the candidates; a heuristic, not a judgement"" evidence: internal/core/record/match/config.go:54 — "LoadConfig resolves match.threshold and match.fields through the one layered"
- candidates are every open and resolved issue and every intent's title and press release; wontfix is excluded evidence: internal/core/capture/match.go:43 — "for _, st := range []State{StateOpen, StateResolved} {"
- only duplicates and refines are ever proposed; reverses and supersedes stay human (itd-84 rule 3) evidence: internal/core/record/match/match.go:34 — "a lexical // score can only ever propose the two below"
- the plugin pages document the links and near_misses
evidence: commands/capture.md:117 — "
duplicates: [<id>]when the two hold each other's terms" evidence: commands/intent.md:80 — "duplicates:orrefines:link for each likely double (itd-2609212137116617)"
- diverged:
- a new issue is matched at filing (press release): the ledger's other filing paths file without matching — inbox promote builds a CaptureRequest with no Match, and the consistency and reading ingests write issue records outside matchAndLink; only the capture verb and the quoted-text intent create match evidence: internal/core/report/inbox.go:683 — "return capture.CaptureRequest{" evidence: internal/core/capture/workflow.go:267 — "content, matched = matchAndLink(repoRoot, issuesRoot, *req.Match" evidence: internal/core/capture/consistency.go:22 — "func IngestConsistency(repoRoot string, payload []byte, date string)"
- the overlap function is the one the embark ranking uses, moved to the record package (spec Scope 1 / Approach): no such function existed, the primitive shipped new as internal/core/record/match; recorded by the spec amendment and iss-2609261631134401 evidence: .abcd/development/specs/closed/spc-2609212141417782-a-new-capture-or-draft-is-matched.md:51 — "The Scope, Approach and Footprint above name a delivery that did not happen"
- missing: (none)
Scope-condition dispositions:
- cond-2609212141415326 — survived: a text with fewer than MinTerms (8) distinct terms is filed with no candidate gathered and the outcome's Skipped names the declared minimum, which the CLI prints as not matched; TestAShortTextIsFiledWithoutMatchingAndSaysSo passes at BASE evidence: internal/core/record/match/match.go:283 — "o.Skipped = "the text carries fewer distinct terms than the declared minimum, so it is filed without matching"" evidence: internal/core/record/match/match.go:263 — "func Short(text string) bool { return len(Terms(text)) < MinTerms }" evidence: internal/core/record/match/match_test.go:131 — "func TestAShortTextIsFiledWithoutMatchingAndSaysSo"
Grounds
- pursued: the ledger holds 480 open issues and the run and the drain will file more unattended; we expect a link at filing to make doubles visible; shown wrong if the next consistency pass still finds unlinked doubles filed after this ships
Frontmatter
| id | itd-2609212137116617 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | minor |
.abcd/development/intents/shipped/itd-2609212137116617-a-new-capture-or-draft-is-matched.md
open on GitHub ↗ · commit history ↗
References5
- adr-2609212115255771Phases and milestones are retired: sequencing is dependencies rendered as status, the checkpoint is the derived releaserelated
- itd-42Grill Reads an Intent Against the Brief and Its Siblings, Not Just the Glossarybuilds on
- itd-48`intent-fidelity-reviewer` Gains Its Cross-Doc And Kind-Classification Rolesrelated
- itd-84An Intent Is Decomposed Before It Is Filed, And A Reversal Is Only Ever Flaggedbuilds on
- itd-87A Finding That Comes Back After You Closed It Tells You Something, And abcd Stops Throwing It Awayrelated
Referenced by4
- adr-2609300821558671A reading finding is matched against the record when it is stored, reversing itd-180's warm-work-only rulerelated
- iss-2610262044221992The recurrence draft says the ledger folds a match into the closed entry, but the shipped matcher always writes a new record and links it as a duplicaterelated
- itd-2609221009495079abcd's typed judgements can run through a decision adapter, measured in a lab before it decides anythingrelated
- spc-2609212141417782a-new-capture-or-draft-is-matched-against-the-record-beforeimplemented by
mentions7
- dcn-26092100000000012026-09-21 — Phases and milestones are retired, and the word roadmap with them (product thinker, at an interactive interview after an independent research pass; adr-2609212115255771 supersedes adr-9). Sequencing is dependencies plus the lifecycle shelves, rendered as a Now / Next / Later status block on the `abcd` board and the site (itd-2609212103568351), never stored; the checkpoint is the derived release plus each intent's acceptance criteria, with an optional `target_release` the cut reports and moves forward (itd-2609212103572513); the unit below a spec is the step, a section not a family (itd-2609212103565953); an issue carries no spec by design; the batch is the run's internal order; one page maps the families (itd-2609211913453478). itd-24 becomes release retrospectives; itd-34 drops its phase rule. Same interview: `build` is the verb a person types and `implement` the loop (itd-2609201916151817 decision 8), the loop takes an issue key (decision 10), only the loop writes a verdict (decision 9); the person's command list is grouped with an agents block behind `--agent` (itd-146), every verb opens with a does/writes/refuses sentence (itd-2609212113220149), modes become flags and five checks one lint (itd-2609212130136102, breaking); the status-line badge is guarded and reset (itd-2609212130146198); a new capture or draft is matched and linked at filing, never refused (itd-2609212137116617); abcd's text names the product thinker or the technical facilitator and never the maintainer (itd-2609212137129937, captured for the run, no sweep today); the lab conventions become `abcd lab` (itd-2609212137128014); a managed repository's site goes live by one verb behind a provider seam (itd-2609061543533170).
- dcn-26092500000000052026-09-25 — The layered configuration resolver has one home, `internal/core/layered`, and two file families; every later consumer reads through it rather than opening a file of its own (lane implementer, autonomous run A, on spc-2609180535002478 part 1). Precedence is flag, then repository, then machine, then bundled, and every value comes back with the layer and the origin that supplied it. The families are `layered.Config`, which is `.abcd/config.json` in the checkout the session resolved and `~/.abcd/config.json` on the machine, and `layered.OracleRouting`, which is `.abcd/config/oracle-routing.json` and `~/.abcd/oracle-routing.json`, the files itd-2609170822093401 names. Scalar keys go in `Config` under a namespace: `pace.*` (itd-2609201925079472), `oracle.review` (itd-6), `roles.<role>.runner` (itd-2609201916056194) and `match.threshold` (itd-2609212137116617). The per-agent routing table keeps its own file, as its intent names, and no new file family is added without an entry here. Loudness: an absent file is an absent layer; a present file that is malformed, symlinked, carries a key twice, trails content, declares the wrong `schema_version`, or (machine layer) is not the caller's own owner-only-writable file is an error naming it; a consumer claims its namespace and every key in it (`Stack.Claim`, with `*` for an open segment such as the role name), and an unknown key under a claimed namespace is refused. Because ahoy writes `oracle.backend` into the shared file, whoever claims `oracle` lists `backend` too. A winning value that does not decode or fails its check is refused, and nothing falls through to a lower layer or to the default.
- iss-2609091256264547The decomposition discipline requires a cross-record link to be typed with one of four relations, supersedes or reverses or duplicates or refines, and never a vague related. Only the first of those four exists. supersedes is schema-known through recordHandleFields and is carried by fifty-eight decision records and three others, while reverses, duplicates and refines appear in no schema and in no committed record anywhere in the tree: the field list a record may carry is related_adrs, related_intents, builds_on and blocked_by, and a record carrying a key outside the known set is dropped by the reader rather than reported, so writing one of the three missing words would make the record invisible to every surface that reads it. The consequence is that an author following the rule literally either writes a link the tooling silently discards or writes the relation in prose and calls it typed, and the corpus shows the second: records assert a typed link in a heading and carry no frontmatter edge, which reads as done to a human and is invisible to every graph walker. That is the shape enforcement-claims-are-facts refuses, a convention naming a mechanism that does not exist, and it is load-bearing here because the decomposition protocol is the documented gate until its automated rung ships. Fix candidates, none chosen: implement the three missing relations as known fields so the rule can be followed; or narrow the rule to the vocabulary the schema implements and say what an author does with a relation the four words cannot express; or record that prose is the sanctioned form for the three and stop calling them typed. Detector: a record asserting a typed relation in its body carries a frontmatter edge naming the same target, and a relation the schema cannot express is refused at write time rather than dropped in silence.
- iss-2609261631134401The closed spec spc-2609212141417782 says the filing-time overlap function is 'the one the embark ranking uses, moved to the record package' at internal/core/record/match.go. Neither happened: no embark overlap function existed, and the primitive shipped new, at internal/core/record/match (a sub-package, because record imports capture and intent). The closed record states a delivery that did not occur.
- iss-2609281911024185itd-2609212137116617's press release says a new issue is matched against the record at filing, but only the capture verb and the quoted-text intent create run the filing-time match. The ledger's other writers file without it: inbox promote builds a capture.CaptureRequest with no Match (internal/core/report/inbox.go captureRequest), and IngestConsistency and IngestReading (internal/core/capture/consistency.go, reading.go) write issue records outside matchAndLink (internal/core/capture/workflow.go). The unattended paths the intent's Grounds name as the reason for the match are exactly the ones that skip it, so a double promoted from a peer report or filed by a consistency pass is never linked at filing. Either these writers pass the layered match config through CaptureRequest.Match, or the record narrows its claim to the two verbs.
- itd-25Patterns Surface Themselves
- spc-2610021456200324The consistency pass at commit time: a stable finding key, cross-document lint codes, chunked review, and a pre-commit gate