Require explicit delivery attribution before closeout
Closeout treated a bare canonical item link as delivery whenever the item was in progress, then ascending PR order gave the earliest PR first choice. That attributed pm-ado-5w3m to gap-reporting PR #16 instead of delivery PR #19, as documented in pm-ado#33.
Closeout now requires explicit delivery evidence regardless of status: a closing keyword before the canonical tracker link, a whole-line closing item-id declaration or pm-github: trailer, or an exact item PR URL field (github_pr, pr_url, pull_request_url). It plans the complete selected PR set before SDK writes. Competing merged delivery claims are reported individually as ambiguous_delivery and none closes the item. Existing head-check, epic, child-item, terminal-item and explicit-skip guards remain. Whole-line declarations accept spaces and tabs, so keywords and IDs cannot be joined across a newline. Ambiguity applies only to the PR numbers that explicitly claim delivery; mention-only rows retain not_delivery.
Before: an in-progress item's incidental link in #16 could win over #19. After: both real historical bare-link bodies are refused. When the item's explicit PR URL names #19, its verified delivery and actual 2026-10-08T01:19:51Z merge chronology are used. Two explicit claims remain unresolved for the operator. The proposed records in pm-ado PR #32 are untouched and need regeneration with explicit #19 evidence.
Minimal fixtures retain the real PR titles, relevant body text and commit messages fetched with the requested gh commands. Tests use actual SDK trackers and only substitute the HTTP transport. The six original attribution cases cover real #16/#19 provenance, valid same-line declarations, multiline rejection, and two claims plus a mention-only PR. Independently restoring the declaration regex or removing claimant membership makes its new behavioral regression fail with ERR_ASSERTION. The original in-progress bypass revert proof and compact single-delivery control remain covered.
The performance follow-up scans each PR body once for whole-line declarations and collects IDs in a Set, replacing regex construction and body scans for each item. Lookup keys preserve the original non-Unicode regex case rules, including rejection of a Kelvin-sign lookalike for ASCII k. The existing six attribution tests are byte-for-byte unchanged. A new real-SDK case uses 40 items and 12 PRs and checks 24 delivery choices, two ambiguous claimant rows, twelve mention-only rows and unchanged plan-mode item statuses. The focused suite passes 7/7, and the same bulk-result case passes 1/1 against the pre-refactor function as an equivalence control. It contains no timing assertion. Reverting only the lookup-key function to ordinary lowercasing makes the bulk result assertion fail with ERR_ASSERTION; restoring the key passes. PM-linked tests pass both commands (7/7 attribution, 1/1 compact dry-run); earlier correctness revert proofs remain recorded in the tracker.
Main's merged PR #149 at 68e7b89 is incorporated by local merge c0aba84a9a57651a3e5cbeee0273916f8798153c. Only CHANGELOG.md conflicted: main's version was selected and canonically regenerated after tracker writes. PM files merged cleanly. The clone-local receipt directory was absent before and after the merge, and strict health reported no merge_receipts_pending, so reconciliation was unnecessary. The refactor code head is 23495cf5469f0d0b6f5795b147a852dde4ca41fb.
Both npm run release:check and bun run release:check exited 0 at code head 23495cf5469f0d0b6f5795b147a852dde4ca41fb. Each passed 910/910 tests with zero failures/skips/cancellations and exact 100/100/100/100 over all 15 authored modules: 10518/10518 lines and statements, 276/276 functions and 3216/3216 branches. Every stage passed at unchanged thresholds, source scope, helper/test budgets and pins. A concurrent attempt at the earlier ed63b0e head hit the existing 500 ms startup-watchdog flake tracked by pm-github-sah1 and was stopped; its later cancellation diagnostics are stop consequences. The unchanged isolated watchdog passed 2/2; the complete corrected Bun gate ran after npm and passed that original probe plus all 910 tests. Interrupted and superseded logs are not acceptance receipts.
The final receipt head is c2cd38e0708d7ef423efd24e74bf8355aa2d4fda; only tracker/history and validation documentation differ from the tested code head. An independent fresh --no-local clone passed strict health with required merge drivers, tracker validation and privacy (4899 reachable Git objects; all identities approved, no secret or host-path matches), with a clean checkout. Final-head CI passed Node 22 and Node 26: each passed 910/910 Node tests and 910/910 native Bun tests across 42 files, zero failures/skips/cancellations, and exact 100/100/100/100 over all 15 authored modules. CodeQL and Semgrep passed. Checks were polled every 60 seconds and completed within the 40-minute limit.
Attribution evidence and validation. Release refresh and real-public-data import proof are now on main through #149.
The PM claim is released and the item remains unclosed. PR #150 remains open for the orchestrator.
PM: pm-github-uqjf.