From 69ab7dec54bd1493e632d98dab5aa3b7f09a4fee Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:39:42 +0200 Subject: [PATCH 1/8] Require explicit merged-PR delivery attribution --- .agents/pm/history/pm-github-uqjf.jsonl | 10 +++ .agents/pm/issues/pm-github-uqjf.toon | 30 ++++++++ CHANGELOG.md | 1 + README.md | 10 ++- closeout.ts | 35 ++++++++- docs/explicit-closeout-attribution.md | 16 ++++ test/closeout-attribution.test.ts | 74 +++++++++++++++++++ test/closeout-workflow.test.ts | 2 +- test/closeout.test.ts | 45 +++++------ .../closeout-attribution/pm-ado-16.json | 15 ++++ .../closeout-attribution/pm-ado-19.json | 11 +++ 11 files changed, 219 insertions(+), 30 deletions(-) create mode 100644 .agents/pm/history/pm-github-uqjf.jsonl create mode 100644 .agents/pm/issues/pm-github-uqjf.toon create mode 100644 docs/explicit-closeout-attribution.md create mode 100644 test/closeout-attribution.test.ts create mode 100644 test/fixtures/closeout-attribution/pm-ado-16.json create mode 100644 test/fixtures/closeout-attribution/pm-ado-19.json diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl new file mode 100644 index 00000000..fa7fa855 --- /dev/null +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -0,0 +1,10 @@ +{"hash_algorithm":"sha256","ts":"2026-10-18T18:19:06.214Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-uqjf"},{"op":"add","path":"/metadata/title","value":"Require explicit delivery attribution before merged-PR closeout"},{"op":"add","path":"/metadata/description","value":"pm-ado issue 33 shows that PR 16 only reported the statement-coverage gap, yet closeout selected it before delivery PR 19 because the item was in progress and its canonical link appeared in the body. Require explicit delivery evidence and report ambiguous candidates without closing."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":1},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-10-18T18:19:06.214Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-18T18:19:06.214Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"87da0cd055dec310bb6bf4923976ad4a0a7fc0b97a761f3262eb0913c68ebd28","item_hash_version":3,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"68a7daef958cef97c57eebdde10fd1aefd157101e1a61a3faaf5d49944bc101c"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:21:30.561Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:21:30.561Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"87da0cd055dec310bb6bf4923976ad4a0a7fc0b97a761f3262eb0913c68ebd28","after_hash":"ad40250f8141b3c734b31f67742f81ed1ae0d265925a1b08100deec5132c25ba","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"adb42ad7632d0ec84f175e3f3d17404a4a12595423f8111803be7b3e35f5e3c6"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:21:33.159Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:21:33.159Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"ad40250f8141b3c734b31f67742f81ed1ae0d265925a1b08100deec5132c25ba","after_hash":"e6eccf5804babb9e05b460226433fd1cb2f797b4ec0e38cf052296054b5c5d5f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"4eeb70a124b74e1b00e566ada6ed540f12374f7ffea9abf94f313577662d0fc6"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:21:36.252Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:21:36.252Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-18T18:21:36.252Z","author":"codex-agent","text":"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history."}]}],"before_hash":"e6eccf5804babb9e05b460226433fd1cb2f797b4ec0e38cf052296054b5c5d5f","after_hash":"b6197213ecf77a422c53acf28876c7f308714bf0180d9fb6d7bb6b1dd4eaa2b5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a3279fdcbc007a5de3f9baa319bbc1e67ea4cc1a73f5a303bf72979a92b580f4"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:26:54.269Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:26:54.269Z"},{"op":"add","path":"/metadata/tags/0","value":"changelog-unreleased"}],"before_hash":"b6197213ecf77a422c53acf28876c7f308714bf0180d9fb6d7bb6b1dd4eaa2b5","after_hash":"7afdb99afad32aa44e8854672932d93a552d9349e7084ff64506196ca5c6a315","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"87ef53277bda7ee5e218c1af6cb4066ab3dcd9f99528daf7299ccc6337351e4e"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:27:09.008Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:27:09.008Z"},{"op":"add","path":"/metadata/files","value":[{"path":"closeout.ts","scope":"project"},{"path":"README.md","scope":"project"},{"path":"test/closeout-attribution.test.ts","scope":"project"},{"path":"test/closeout-workflow.test.ts","scope":"project"},{"path":"test/closeout.test.ts","scope":"project"},{"path":"test/fixtures/closeout-attribution/pm-ado-16.json","scope":"project"},{"path":"test/fixtures/closeout-attribution/pm-ado-19.json","scope":"project"}]}],"before_hash":"7afdb99afad32aa44e8854672932d93a552d9349e7084ff64506196ca5c6a315","after_hash":"66da8bd908b6e633596d247288d0f3b33101871f9784a59c4c427afa3d229128","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"882cbc5ae2b691625fdce7b1c2fc43fac8e07c25bda5947cc743ca5ad8843dc2"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:27:23.840Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:27:23.840Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/closeout-attribution.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-18T18:27:23.663Z","source_kind":"local_mutation","source_ref":"fix/closeout-explicit-delivery"}}]}],"before_hash":"66da8bd908b6e633596d247288d0f3b33101871f9784a59c4c427afa3d229128","after_hash":"3a3e03516eb2df9143029f7f3444de7b12bb24a2bebf614cda6a511f4f3f7aee","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"18416d30044f588c93e12438e23269d76fed48700aebb2fd59ec62a25e258c12"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:27:43.386Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-18T18:27:43.386Z","author":"codex-agent","text":"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:27:43.386Z"}],"before_hash":"3a3e03516eb2df9143029f7f3444de7b12bb24a2bebf614cda6a511f4f3f7aee","after_hash":"1a2b05376ad4f87e0b7e29378cadb0e9d4a1f13f7474d668f1ec6141d2b3d0c9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"02dea5e365cf5f219f1c094268a04228fb8acd16cb4f175adb84fe5fcf9b028c"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:30:07.355Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-18T18:30:07.355Z","author":"codex-agent","text":"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:30:07.355Z"}],"before_hash":"1a2b05376ad4f87e0b7e29378cadb0e9d4a1f13f7474d668f1ec6141d2b3d0c9","after_hash":"3b7df670257527cf5f07329aa0ef7612a11c2b30365230bde50653e6f44028d5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"d17f228cbc8aa1e7875e122f197970f104c5d54cc41ac1d346395f35cf9b8b3e"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:32:41.098Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:32:41.098Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/explicit-closeout-attribution.md","scope":"project"},{"path":"README.md","scope":"project"}]}],"before_hash":"3b7df670257527cf5f07329aa0ef7612a11c2b30365230bde50653e6f44028d5","after_hash":"fcc5d63a191500b5888297857a1b9ba117d0f6344ca3a0894381084c81857dd7","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"24ba4e2d3f024bf88921c67628158e9d9bc90f2367c2133a31003a64ed978617"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon new file mode 100644 index 00000000..59f6259b --- /dev/null +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -0,0 +1,30 @@ +id: pm-github-uqjf +title: Require explicit delivery attribution before merged-PR closeout +description: "pm-ado issue 33 shows that PR 16 only reported the statement-coverage gap, yet closeout selected it before delivery PR 19 because the item was in progress and its canonical link appeared in the body. Require explicit delivery evidence and report ambiguous candidates without closing." +type: Issue +status: in_progress +priority: 1 +tags[1]: changelog-unreleased +created_at: "2026-10-18T18:19:06.214Z" +updated_at: "2026-10-18T18:32:41.098Z" +assignee: codex-agent +claim_principal: codex-agent +author: codex-agent +comments[3]{created_at,author,text}: + "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." + "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." + "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." +files[7]{path,scope}: + closeout.ts,project + README.md,project + test/closeout-attribution.test.ts,project + test/closeout-workflow.test.ts,project + test/closeout.test.ts,project + test/fixtures/closeout-attribution/pm-ado-16.json,project + test/fixtures/closeout-attribution/pm-ado-19.json,project +tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}: + node --test test/closeout-attribution.test.ts,project,codex-agent,"2026-10-18T18:27:23.663Z",local_mutation,fix/closeout-explicit-delivery +docs[2]{path,scope}: + docs/explicit-closeout-attribution.md,project + README.md,project +body: "" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e0dc4bf..7168fd80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- Require explicit delivery attribution before merged-PR closeout ([pm-github-uqjf](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-uqjf.toon)) - Release timeout must report current workflow approval state ([pm-github-b145](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-b145.toon)) - Preserve legacy GitHub ownership across copied provider examples ([pm-github-b143](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-b143.toon)) - Reject malformed effective-rule discriminators before closeout ([pm-github-b141](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-b141.toon)) diff --git a/README.md b/README.md index 94671138..085cbfcd 100644 --- a/README.md +++ b/README.md @@ -39,15 +39,17 @@ Only `https://github.com/owner/repo/blob/main/.agents/pm//.toon` links in PR bodies are recognized. Percent-encoded names and nested SDK tracker folders are supported; malformed encodings and traversal segments are ignored. Repository comparisons ignore case. Links to other repositories, unknown ids, closed/canceled items and `--skip` ids produce -explicit skip rows. Bare ids and `pm:` trailers are not supported. If several -selected PRs reference one item, the lowest PR number supplies its evidence and -later references are skipped. +explicit skip rows. Bare mentions and `pm:` trailers are not delivery evidence. Use a whole-line +`Closes ` or `pm-github: ` declaration, a closing keyword before +the canonical item link, or an item field `github_pr`, `pr_url` or +`pull_request_url` containing the exact PR URL. Multiple merged PRs explicitly +claiming one item are reported as `ambiguous_delivery`; none closes it. **A mention is not delivery.** PR bodies routinely link follow-ups, epics and related work for context, so a link alone never closes an item. An item is closed only when: -- it is `in_progress` (claimed work), **or** its link is preceded on the same +- its delivery is explicitly declared; a canonical link may be preceded on the same line by a GitHub-style closing keyword: `Closes`, `Fixes`, `Resolves` or `Delivers` (and their tense forms), for example `- Closes [pm-x](…/pm-x.toon)`; - it is not an `Epic`; epics close when their children are done; diff --git a/closeout.ts b/closeout.ts index baa3bd7e..066abbf2 100644 --- a/closeout.ts +++ b/closeout.ts @@ -20,7 +20,7 @@ export interface CloseoutRow { /** Stable decisions used by automation and compact skip counts. */ export type CloseoutCode = "already_terminal" | "other_repository" | "duplicate_selection" | "explicit_skip" | "unknown_item" | "epic" | "active_children" | "not_delivery" | "close_failed" - | "head_checks_passed" | "head_checks_failed" | "head_evidence_unavailable" | "required_checks_unverified" | "incomplete_merge"; + | "ambiguous_delivery" | "head_checks_passed" | "head_checks_failed" | "head_evidence_unavailable" | "required_checks_unverified" | "incomplete_merge"; /** Which head-check policy was evaluated for one selected pull request. */ interface CheckGate { @@ -392,6 +392,7 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti if (typeof parent === "string" && !TERMINAL_STATUSES.includes(item.status)) activeChildren.set(parent, (activeChildren.get(parent) ?? 0) + 1); } const selected = new Set(); + const candidates = new Map>(); const plan: CloseoutRow[] = []; const checks: CheckGate[] = []; const closures: { row: CloseoutRow; reason: string; at: string; resolution: string; expectedResult: string; actualResult: string }[] = []; @@ -400,7 +401,24 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti let gate: CheckGate = { pr: number, code: "incomplete_merge", rule: "unavailable", ok: false, passed: [], reason: "Pull request is not merged or merge evidence is incomplete." }; if (mergeEvidence(pr)) gate = await checkCloseoutHead(api, repo, pr, declared); checks.push(gate); - for (const ref of extractCloseoutItems(pr.body ?? "")) { + const refs = extractCloseoutItems(pr.body ?? ""); + for (const item of items.values()) { + // Only canonical delivery metadata or a whole-line trailer/closing declaration is authoritative. + const linked = [item.github_pr, item.pr_url, item.pull_request_url].includes(pr.html_url); + const id = item.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const declaration = new RegExp(`^(?:[-*]\\s+)?(?:${[...CLOSING_KEYWORDS].join("|")}|pm-github:)\\s+${id}[.!]?\\s*$`, "im").test(pr.body ?? ""); + if (linked || declaration) { + const ref = refs.find(ref => ref.item === item.id && ref.repo.toLowerCase() === repo.toLowerCase()); + if (ref) ref.declared = true; + else refs.push({ item: item.id, repo, declared: true }); + } + } + for (const ref of refs) { + if (ref.declared && pr.merged && ref.repo.toLowerCase() === repo.toLowerCase()) { + const choices = candidates.get(ref.item) ?? new Set(); + choices.add(number); + candidates.set(ref.item, choices); + } let reason: string; let code: CloseoutCode; if (ref.repo.toLowerCase() !== repo.toLowerCase()) { code = "other_repository"; reason = "Link points to another repository."; } @@ -410,9 +428,9 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti else if (!gate.ok) { code = gate.code; reason = gate.reason; } else if (items.get(ref.item)!.type === "Epic") { code = "epic"; reason = "Epics close when their children are done, not by a pull request."; } else if (activeChildren.has(ref.item)) { code = "active_children"; reason = `Item has ${activeChildren.get(ref.item)} non-terminal child item(s).`; } - else if (items.get(ref.item)!.status !== "in_progress" && !ref.declared) { + else if (!ref.declared) { code = "not_delivery"; - reason = `Item is ${items.get(ref.item)!.status}, not in progress; a mention is not delivery. Prefix its link with Closes, Fixes, Resolves or Delivers to close it.`; + reason = "A mention is not delivery, regardless of item status. Declare Closes, Fixes, Resolves or Delivers, link the item to this PR, or use a pm-github trailer."; } else if (selected.has(ref.item)) { code = "duplicate_selection"; reason = "Item already selected by another pull request."; } else { @@ -432,10 +450,19 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti plan.push({ item: ref.item, pr: number, action: "skip", code, reason }); } } + // Resolve attribution across the complete selection before the first SDK write. + for (const row of plan) { + if ((candidates.get(row.item)?.size ?? 0) > 1 && !["already_terminal", "other_repository", "unknown_item", "explicit_skip"].includes(row.code)) { + row.action = "skip"; + row.code = "ambiguous_delivery"; + row.reason = "Multiple merged PRs explicitly claim delivery; select the intended PR before closing."; + } + } let closed = 0; let failed = 0; if (apply) { for (const entry of closures) { + if (entry.row.action !== "close") continue; try { await close(entry.row.item, entry.reason, { author: "pm-github", completedAt: entry.at, resolution: entry.resolution, expectedResult: entry.expectedResult, actualResult: entry.actualResult, diff --git a/docs/explicit-closeout-attribution.md b/docs/explicit-closeout-attribution.md new file mode 100644 index 00000000..6d432eac --- /dev/null +++ b/docs/explicit-closeout-attribution.md @@ -0,0 +1,16 @@ +# Explicit closeout delivery attribution + +pm-ado issue [33](https://github.com/unbraind/pm-ado/issues/33) identified a durable provenance error: merged PR 16 reported the statement-enforcement gap, while PR 19 delivered enforcement. Both bodies link the same tracker item. The previous rule let any in-progress item close on a bare body link, then ascending PR order gave PR 16 first choice. + +Closeout now requires explicit evidence regardless of the item's status: + +- A closing keyword immediately before a canonical tracker link. +- A whole-line closing declaration such as `Closes pm-ado-5w3m`. +- A whole-line `pm-github: pm-ado-5w3m` trailer. +- The item's `github_pr`, `pr_url` or `pull_request_url` set to the exact PR URL. + +The entire selected PR set is planned before any SDK close. If multiple merged PRs explicitly claim the same item, every candidate is reported as `ambiguous_delivery` and no candidate closes it. Existing required-check, child-item, epic, skip and terminal-item safeguards still apply. Titles and incidental mentions never establish delivery. + +The minimal fixtures retain the real titles, relevant body paragraphs and commit messages fetched with `gh pr view -R unbraind/pm-ado 16 --json title,body,commits` and the corresponding PR 19 command. Neither historical body explicitly declares delivery. Both are therefore refused until an explicit link or declaration is supplied; with the item's PR URL set to 19, closeout uses its October 8 merge chronology. Existing proposed pm-ado PR 32 tracker history is not rewritten here. + +`node --test test/closeout-attribution.test.ts` passes all four real-tracker cases. Restoring only the old in-progress bypass in runCloseout makes both real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Both package release gates remain mandatory at unchanged thresholds; final receipts are in the PM item and PR. diff --git a/test/closeout-attribution.test.ts b/test/closeout-attribution.test.ts new file mode 100644 index 00000000..65304b49 --- /dev/null +++ b/test/closeout-attribution.test.ts @@ -0,0 +1,74 @@ +/** Real PR shapes distinguish a reported gap from explicit delivery attribution. */ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { create, get, update, commitImportedItem, getItemPath, readSettings } from "@unbrained/pm-cli/sdk"; +import { runCloseout, type CloseoutApi } from "../closeout.ts"; + +const repo = "unbraind/pm-ado"; +const url = (n: number) => `https://github.com/${repo}/pull/${n}`; +const head = "a".repeat(40); +/** Minimal public text fetched with gh pr view; no reviewer summaries or identities retained. */ +const recorded = [16, 19].map(n => JSON.parse(readFileSync(new URL(`./fixtures/closeout-attribution/pm-ado-${n}.json`, import.meta.url), "utf8")) as { number: number; body: string }); + +/** Real SDK tracker plus complete HTTP-boundary evidence; neither SDK nor mutation is mocked. */ +async function scenario(linked: boolean, bodies?: string[]) { + const root = mkdtempSync(join(tmpdir(), "closeout-attribution-")); + try { + const init = spawnSync(join(import.meta.dirname, "../node_modules/.bin/pm"), ["--pm-path", root, "init", "fixture"], { cwd: root, encoding: "utf8" }); + assert.equal(init.status, 0, init.stderr); + const sdk = { pmRoot: root, noExtensions: true }; + const item = (await create({ title: "Enforce statement coverage", type: "Issue", author: "fixture" }, sdk)).item; + await update(item.id, { status: "in_progress" }, sdk); + if (linked) { + await commitImportedItem({ pmRoot: root, id: item.id, itemPath: getItemPath(root, "Issue", item.id), settings: await readSettings(root), + document: { metadata: { ...item, status: "in_progress", pr_url: url(19) }, body: "" }, author: "fixture", message: "Link delivery PR", conflictWarningPrefix: "fixture" }); + } + const api: CloseoutApi = { base: "https://api.github.com", next: () => undefined, get: async endpoint => { + const n = Number(endpoint.split("/").at(-1)); + const value = endpoint.includes("/pulls/") ? { number: n, body: (bodies?.[n === 16 ? 0 : 1] ?? recorded.find(p => p.number === n)!.body).replaceAll("pm-ado-5w3m", item.id), + merged: true, merged_at: n === 16 ? "2026-10-04T12:58:08Z" : "2026-10-08T01:19:51Z", merge_commit_sha: "b".repeat(40), + html_url: url(n), head: { sha: head }, base: { ref: "main", repo: { full_name: repo } } } + : endpoint.includes("/protection/") ? { contexts: ["CI"], checks: [] } + : endpoint.includes("/check-runs") ? { total_count: 1, check_runs: [{ name: "CI", head_sha: head, status: "completed", conclusion: "success", app: { id: 7 } }] } : []; + return { body: JSON.stringify(value) }; + } }; + const report = await runCloseout(root, repo, { pr: ["16", "19"], apply: true }, api); + return { report, item: (await get(item.id, {}, sdk)).item }; + } finally { rmSync(root, { recursive: true, force: true }); } +} + +test("real gap-reporting #16 and delivery #19 bare links never infer delivery from in-progress status", async () => { + const { report, item } = await scenario(false); + assert.equal(report.closed, 0); + assert.deepEqual(report.plan.filter(row => row.code !== "unknown_item").map(row => [row.pr, row.code]), [[16, "not_delivery"], [19, "not_delivery"]]); + assert.equal(item.status, "in_progress"); +}); + +test("the item's explicit PR link selects real delivery #19 and its merge chronology", async () => { + const { report, item } = await scenario(true); + assert.equal(report.closed, 1); + assert.ok(report.plan.some(row => row.pr === 16 && row.code === "not_delivery")); + assert.ok(report.plan.some(row => row.pr === 19 && row.action === "close")); + assert.equal(item.completed_at, "2026-10-08T01:19:51.000Z"); + assert.match(item.resolution!, /pull\/19/); +}); + +test("closing item ids and pm-github trailers explicitly deliver without a canonical body link", async () => { + for (const declaration of ["Closes pm-ado-5w3m", "- Fixes pm-ado-5w3m.", "pm-github: pm-ado-5w3m", + "See https://github.com/unbraind/pm-ado/blob/main/.agents/pm/issues/unrelated.toon\nSee https://github.com/other/repo/blob/main/.agents/pm/issues/pm-ado-5w3m.toon\nCloses pm-ado-5w3m"]) { + const { report } = await scenario(false, ["No delivery claim", declaration]); + assert.equal(report.closed, 1); + assert.equal(report.plan[0]!.pr, 19); + } +}); + +test("two explicit merged delivery claims remain ambiguous before any close", async () => { + const { report, item } = await scenario(false, ["Closes pm-ado-5w3m", "pm-github: pm-ado-5w3m"]); + assert.equal(report.closed, 0); + assert.ok(report.plan.every(row => row.action === "skip" && row.code === "ambiguous_delivery")); + assert.equal(item.status, "in_progress"); +}); diff --git a/test/closeout-workflow.test.ts b/test/closeout-workflow.test.ts index b086ac92..32d0dcaa 100644 --- a/test/closeout-workflow.test.ts +++ b/test/closeout-workflow.test.ts @@ -152,7 +152,7 @@ esac if (env.READ_DENIED === "true" || (env.CHECKS_DENIED === "true" && url.includes("/check-runs"))) { jsonResponse(res, 403, { message: "Resource not accessible by integration" }); return; } const sha = "a".repeat(40); if (url.includes("/pulls?")) jsonResponse(res, 200, env.NO_DELIVERY === "true" ? [] : [{ number: 42, merged_at: "2026-10-15T00:00:00Z", updated_at: "2026-10-15T00:00:00Z" }]); - else if (url.endsWith("/pulls/42")) jsonResponse(res, 200, { number: 42, body: env.NO_LINKS === "true" ? "Routine maintenance" : `https://github.com/${env.FOREIGN_LINK === "true" ? "other/project" : "acme/widgets"}/blob/main/.agents/pm/tasks/${id}.toon${env.SUMMARY_ROWS === "true" ? `\nhttps://github.com/other/project/blob/main/.agents/pm/tasks/${id}.toon` : ""}${env.SUMMARY_ITEM ? `\nhttps://github.com/acme/widgets/blob/main/.agents/pm/tasks/${env.SUMMARY_ITEM}.toon` : ""}`, + else if (url.endsWith("/pulls/42")) jsonResponse(res, 200, { number: 42, body: env.NO_LINKS === "true" ? "Routine maintenance" : `Closes https://github.com/${env.FOREIGN_LINK === "true" ? "other/project" : "acme/widgets"}/blob/main/.agents/pm/tasks/${id}.toon${env.SUMMARY_ROWS === "true" ? `\nhttps://github.com/other/project/blob/main/.agents/pm/tasks/${id}.toon` : ""}${env.SUMMARY_ITEM ? `\nhttps://github.com/acme/widgets/blob/main/.agents/pm/tasks/${env.SUMMARY_ITEM}.toon` : ""}`, merged: true, merged_at: "2026-10-15T00:00:00Z", merge_commit_sha: env.INCOMPLETE_MERGE === "true" ? null : "b".repeat(40), html_url: "https://github.com/acme/widgets/pull/42", head: { sha }, base: { ref: "trunk", repo: { full_name: "acme/widgets" } } }); else if (url.includes("/protection/")) jsonResponse(res, 200, { contexts: ["CI"], checks: [] }); diff --git a/test/closeout.test.ts b/test/closeout.test.ts index 676f2c7d..7a4ca79a 100644 --- a/test/closeout.test.ts +++ b/test/closeout.test.ts @@ -18,7 +18,10 @@ const repo = "fixture/project"; const head = "a".repeat(40); const merge = "b".repeat(40); const mergedAt = "2026-10-13T12:30:00Z"; -const link = (id: string, repository = repo) => `https://github.com/${repository}/blob/main/.agents/pm/tasks/${id}.toon`; +const bareLink = (id: string, repository = repo) => `https://github.com/${repository}/blob/main/.agents/pm/tasks/${id}.toon`; + +/** Explicit delivery declarations used by all positive closeout fixtures. */ +const link = (id: string, repository = repo) => `Closes ${bareLink(id, repository)}`; /** Complete synthetic merged PR; prose is never trusted as closure evidence. */ function pull(body: string | null, overrides: Record = {}) { @@ -84,25 +87,25 @@ function closeout(root: string, options: Record = { pr: "42" }) } test("extraction accepts only canonical tracker links and deduplicates references", () => { - assert.deepEqual(extractCloseoutItems([link("pm-1"), link("pm%2D1"), link("pm-1", "FIXTURE/PROJECT"), link("pm-1", "other/repo"), - "pm: pm-2", link("pm-3").replace("blob/main", "blob/feature"), link("pm-4") + ".bak", "https://github.com/fixture/project/issues/1"].join("\n")), + assert.deepEqual(extractCloseoutItems([bareLink("pm-1"), bareLink("pm%2D1"), bareLink("pm-1", "FIXTURE/PROJECT"), bareLink("pm-1", "other/repo"), + "pm: pm-2", bareLink("pm-3").replace("blob/main", "blob/feature"), bareLink("pm-4") + ".bak", "https://github.com/fixture/project/issues/1"].join("\n")), [{ repo, item: "pm-1", declared: false }, { repo: "other/repo", item: "pm-1", declared: false }]); // Only a closing keyword before the link on the same line declares delivery; a later declaring line upgrades a mention. - assert.deepEqual(extractCloseoutItems([`- Closes ${link("pm-5")}`, `- [pm-6](${link("pm-6")}): closed later`, `See ${link("pm-7")}`, `Fixes [pm-7](${link("pm-7")})`, - `Prefix ${link("pm-8")}`].join("\n")).map(ref => [ref.item, ref.declared]), + assert.deepEqual(extractCloseoutItems([`- Closes ${bareLink("pm-5")}`, `- [pm-6](${bareLink("pm-6")}): closed later`, `See ${bareLink("pm-7")}`, `Fixes [pm-7](${bareLink("pm-7")})`, + `Prefix ${bareLink("pm-8")}`].join("\n")).map(ref => [ref.item, ref.declared]), [["pm-5", true], ["pm-6", false], ["pm-7", true], ["pm-8", false]]); // The keyword must directly precede its link: prose in between, as in these review-reported lines, declares nothing. for (const line of ["Unrelated to the fix in ", "Not fixed here; see ", "Previously closed work: ", "Closes (see notes) ", "Closes [a](not a url) and ", "text]("]) { assert.equal(declaresDelivery(line), false, line); } - for (const line of ["Closes ", "- Closes: ", "**Fixes** [pm-x](", "Delivers [a](https://x/a.toon) and [b](", `Resolves ${link("pm-1")}, `, "_Delivered_ "]) { + for (const line of ["Closes ", "- Closes: ", "**Fixes** [pm-x](", "Delivers [a](https://x/a.toon) and [b](", `Resolves ${bareLink("pm-1")}, `, "_Delivered_ "]) { assert.equal(declaresDelivery(line), true, line); } for (const folder of [".", "..", "%2e%2e", "%2Fescape", "%5Cescape", "%00", "bad%ZZ"]) { assert.deepEqual(extractCloseoutItems(`https://github.com/${repo}/blob/main/.agents/pm/${folder}/pm-1.toon`), []); } - assert.deepEqual(extractCloseoutItems(link("pm-%2Fescape")), []); - assert.deepEqual(extractCloseoutItems(link("pm-%ZZ")), []); + assert.deepEqual(extractCloseoutItems(bareLink("pm-%2Fescape")), []); + assert.deepEqual(extractCloseoutItems(bareLink("pm-%ZZ")), []); }); test("dry-run plans same-repo, foreign, unknown, terminal and excluded items without writes", async () => tracker(async (root, ids) => { @@ -237,12 +240,12 @@ test("public apply reports partial results as CommandError exit 1 after remainin } finally { await unlock(); } })); -test("one item referenced by multiple selected PRs is closed once", async () => tracker(async (root, ids) => { +test("multiple explicit delivery claims are refused before writes", async () => tracker(async (root, ids) => { await withMockGithub(fixture(link(ids[0]!)), async () => { const report = await closeout(root, { pr: ["43", "42"], apply: true }); - assert.equal(report.closed, 1); + assert.equal(report.closed, 0); assert.deepEqual(report.plan.map(row => row.pr), [42, 43]); - assert.match(report.plan[1]!.reason, /already selected/); + assert.ok(report.plan.every(row => row.code === "ambiguous_delivery" && row.action === "skip")); }); })); @@ -456,8 +459,8 @@ test("public command registration reuses repo resolution and shared transport", assert.equal((result.result as { plan: { action: string }[] }).plan[0]!.action, "close"); const compact = await harness.runCommand({ command: "github closeout", pmRoot: root, global: { json: false }, options: { pr: ["42", "43", "44"] } }); - assert.equal((compact.result as { plan: object[] }).plan.length, 1); - assert.deepEqual((compact.result as { skipped: object }).skipped, { duplicate_selection: 2 }); + assert.equal((compact.result as { plan: object[] }).plan.length, 3); + assert.deepEqual((compact.result as { skipped: object }).skipped, {}); const full = await harness.runCommand({ command: "github closeout", pmRoot: root, global: { json: false }, options: { pr: ["42", "43", "44"], full: true } }); assert.equal((full.result as { plan: object[] }).plan.length, 3); @@ -510,19 +513,19 @@ test("packed CLI preserves repeatable selectors, TOON/JSON shape and no-op apply assert.equal(preview.code, 0, preview.stderr); const report = decode(preview.stdout) as { dry_run: boolean; plan: { action: string }[] }; assert.equal(report.dry_run, true); - assert.equal(report.plan.length, 2); - assert.deepEqual((report as unknown as { skipped: object }).skipped, { explicit_skip: 4, duplicate_selection: 2 }); + assert.equal(report.plan.length, 4); + assert.deepEqual((report as unknown as { skipped: object }).skipped, { explicit_skip: 4 }); const full = await call([...flags, "--full"]); assert.equal(full.code, 0, full.stderr); assert.equal((decode(full.stdout) as { plan: object[] }).plan.length, 8); const json = await call([...flags, "--json"]); assert.equal(json.code, 0, json.stderr); assert.equal((JSON.parse(json.stdout) as { plan: object[] }).plan.length, 8); - assert.equal(report.plan.filter(row => row.action === "close").length, 2); + assert.equal(report.plan.filter(row => row.action === "close").length, 0); assert.equal(snapshot(root), before); const applied = await call([...flags, "--apply", "--json"]); assert.equal(applied.code, 0, applied.stderr); - assert.equal((JSON.parse(applied.stdout) as { closed: number }).closed, 2); + assert.equal((JSON.parse(applied.stdout) as { closed: number }).closed, 0); const after = snapshot(root); const repeat = await call([...flags, "--apply", "--json"]); assert.equal(repeat.code, 0, repeat.stderr); @@ -598,7 +601,7 @@ test("custom SDK folders with dots, encoded spaces and nested paths close throug const created = await create({ type: type!, title: `Custom delivery ${type}`, author: "fixture", acceptanceCriteria: "Verified delivery" }, { pmRoot: root, noExtensions: true }); await update(created.item.id, { status: "in_progress" }, { pmRoot: root, noExtensions: true }); const url = `https://github.com/${repo}/blob/main/.agents/pm/${folder!.split("/").map(encodeURIComponent).join("/")}/${encodeURIComponent(created.item.id).replaceAll("-", "%2D")}.toon`; - await withMockGithub(fixture(url), async () => { + await withMockGithub(fixture(`Closes ${url}`), async () => { assert.equal((await closeout(root, { pr: "42", apply: true })).closed, 1); assert.equal((await get(created.item.id, {}, { pmRoot: root, noExtensions: true })).item.status, "closed"); }); @@ -664,8 +667,8 @@ test("a mention is not delivery: open follow-ups, epics and parents with active const epic = (await create({ title: "Umbrella", type: "Epic", author: "fixture" }, sdk)).item.id; await update(epic, { status: "in_progress" }, sdk); await update(ids[1]!, { parent: ids[0]! }, sdk); - const body = [`Delivers [parent](${link(ids[0]!)}) and [child](${link(ids[1]!)})`, `Follow-up: ${link(followUp)} (not closed by this PR)`, - `Closes ${link(declared)}`, `Part of ${link(epic)}`].join("\n"); + const body = [`Delivers [parent](${bareLink(ids[0]!)}) and [child](${bareLink(ids[1]!)})`, `Follow-up: ${bareLink(followUp)} (not closed by this PR)`, + `Closes ${bareLink(declared)}`, `Part of ${bareLink(epic)}`].join("\n"); await withMockGithub(fixture(body), async () => { const report = await closeout(root, { pr: "42" }); const rows = new Map(report.plan.map(row => [row.item, row])); @@ -673,7 +676,7 @@ test("a mention is not delivery: open follow-ups, epics and parents with active assert.match(rows.get(ids[0]!)!.reason, /1 non-terminal child/); assert.equal(rows.get(ids[1]!)!.action, "close"); assert.equal(rows.get(followUp)!.action, "skip"); - assert.match(rows.get(followUp)!.reason, /open, not in progress; a mention is not delivery/); + assert.match(rows.get(followUp)!.reason, /A mention is not delivery/); assert.equal(rows.get(declared)!.action, "close"); assert.equal(rows.get(epic)!.action, "skip"); assert.match(rows.get(epic)!.reason, /Epics close when their children are done/); diff --git a/test/fixtures/closeout-attribution/pm-ado-16.json b/test/fixtures/closeout-attribution/pm-ado-16.json new file mode 100644 index 00000000..09402a9c --- /dev/null +++ b/test/fixtures/closeout-attribution/pm-ado-16.json @@ -0,0 +1,15 @@ +{ + "number": 16, + "title": "Certify pm-ado on PM CLI 2026.10.4 and consolidate pending dependency updates", + "body": "**NOT READY for 100/100/100/100:** the pre-existing statement threshold is declared but not independently measured or enforced. README and gate comments now describe the measured scope accurately, and the threshold is preserved. Follow-up pm-ado-5w3m records actual enforcement work. Offline validation is the only shipped command; no live Azure sync/import/export, reverse relations or revision-reconciliation acceptance is claimed. Final-head CI and substantive reviews remain separate.\nExact commands and outputs: `docs/certification-2026.10.4.md`. PM: [certification pm-ado-qq1p](https://github.com/unbraind/pm-ado/blob/main/.agents/pm/chores/pm-ado-qq1p.toon), [statement enforcement pm-ado-5w3m](https://github.com/unbraind/pm-ado/blob/main/.agents/pm/issues/pm-ado-5w3m.toon). Items stay open; orchestrator merges and closes after verification.", + "commits": [ + { + "messageHeadline": "Certify pm-ado on PM CLI 2026.10.4 with exact dependency pins", + "messageBody": "" + }, + { + "messageHeadline": "Keep managed preview artifacts clone-local and document review scope", + "messageBody": "" + } + ] +} diff --git a/test/fixtures/closeout-attribution/pm-ado-19.json b/test/fixtures/closeout-attribution/pm-ado-19.json new file mode 100644 index 00000000..47724dc7 --- /dev/null +++ b/test/fixtures/closeout-attribution/pm-ado-19.json @@ -0,0 +1,11 @@ +{ + "number": 19, + "title": "Enforce independently measured statement coverage", + "body": "PM item: https://github.com/unbraind/pm-ado/blob/main/.agents/pm/issues/pm-ado-5w3m.toon", + "commits": [ + { + "messageHeadline": "Enforce independently measured statement coverage", + "messageBody": "" + } + ] +} From b21ff5bdbdcfdab6c97b1204d73ff528b0f65745 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:57:26 +0200 Subject: [PATCH 2/8] Cover compact output for an explicit single delivery --- .agents/pm/history/pm-github-uqjf.jsonl | 3 +++ .agents/pm/issues/pm-github-uqjf.toon | 7 +++++-- test/closeout.test.ts | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl index fa7fa855..bbadf31a 100644 --- a/.agents/pm/history/pm-github-uqjf.jsonl +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -8,3 +8,6 @@ {"hash_algorithm":"sha256","ts":"2026-10-18T18:27:43.386Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-18T18:27:43.386Z","author":"codex-agent","text":"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:27:43.386Z"}],"before_hash":"3a3e03516eb2df9143029f7f3444de7b12bb24a2bebf614cda6a511f4f3f7aee","after_hash":"1a2b05376ad4f87e0b7e29378cadb0e9d4a1f13f7474d668f1ec6141d2b3d0c9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"02dea5e365cf5f219f1c094268a04228fb8acd16cb4f175adb84fe5fcf9b028c"} {"hash_algorithm":"sha256","ts":"2026-10-18T18:30:07.355Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-18T18:30:07.355Z","author":"codex-agent","text":"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:30:07.355Z"}],"before_hash":"1a2b05376ad4f87e0b7e29378cadb0e9d4a1f13f7474d668f1ec6141d2b3d0c9","after_hash":"3b7df670257527cf5f07329aa0ef7612a11c2b30365230bde50653e6f44028d5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"d17f228cbc8aa1e7875e122f197970f104c5d54cc41ac1d346395f35cf9b8b3e"} {"hash_algorithm":"sha256","ts":"2026-10-18T18:32:41.098Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:32:41.098Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/explicit-closeout-attribution.md","scope":"project"},{"path":"README.md","scope":"project"}]}],"before_hash":"3b7df670257527cf5f07329aa0ef7612a11c2b30365230bde50653e6f44028d5","after_hash":"fcc5d63a191500b5888297857a1b9ba117d0f6344ca3a0894381084c81857dd7","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"24ba4e2d3f024bf88921c67628158e9d9bc90f2367c2133a31003a64ed978617"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:48:06.831Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-18T18:48:06.831Z","author":"codex-agent","text":"PR https://github.com/unbraind/pm-github/pull/150 is open at 69ab7dec54bd1493e632d98dab5aa3b7f09a4fee. Fresh --no-local clone strict health passes with required merge drivers in an isolated home. The current full npm gate passes all 4 new real-shape attribution cases, including the additional foreign/unknown canonical-link boundary. Current-head function-only revert copies are being rechecked without mutating any running gate checkout. CodeQL and Semgrep pass; full runtime gates and Node 22/26 CI are still running."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:48:06.831Z"}],"before_hash":"fcc5d63a191500b5888297857a1b9ba117d0f6344ca3a0894381084c81857dd7","after_hash":"cecc285308c8127e64a6eb5f68d733e37462adce29e2d516fc52c802a6ece1b5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b5063d5bf9d0255dadd327005708feb0de1629fb8117bb4f5190a84362701b8f"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:54:50.389Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-10-18T18:54:50.388Z","author":"codex-agent","text":"CI Node 26 passed 897/897 tests with zero failures, skips or cancellations, then the unchanged exact branch gate correctly refused 99.96 percent. closeout.ts line 490 lacked the compact-report positive-close arm after former multi-PR success fixtures became ambiguity refusals. Added an assertion that a real single-delivery dry-run remains close in compact output; no production code or threshold change. Superseded local closeout gates were stopped to rerun the complete corrected test corpus."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:54:50.389Z"}],"before_hash":"cecc285308c8127e64a6eb5f68d733e37462adce29e2d516fc52c802a6ece1b5","after_hash":"22b87573fdb4a16f7c0abc64ab28811b65cb8e59a4bc4fc2aafd8751f679f02d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a8c0ff4c8c568b8624836155ca24baa912905dc651288529bd8da72bbcc52890"} +{"hash_algorithm":"sha256","ts":"2026-10-18T18:57:14.608Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-10-18T18:57:14.608Z","author":"codex-agent","text":"Focused compact projection verification passed: node --test --test-name-pattern=\"dry-run plans same-repo\" test/closeout.test.ts, 1/1 with zero failures/skips. This preserves a valid single-delivery close action alongside individual refusals and routine skip counts; the exact branch gate will rerun without weakening its threshold. Current-head real-shape function-only revert copies failed 2/2, with zero test-load failures."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:57:14.608Z"}],"before_hash":"22b87573fdb4a16f7c0abc64ab28811b65cb8e59a4bc4fc2aafd8751f679f02d","after_hash":"e6304d51bb6114471718e0f17cc01b56e4991a363be89ba4dfaabcbf92389bb4","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"9e577e6f2037d2598f459616fbcbe9e5f6dfddbd42669e91ee121edbb5dc53e9"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon index 59f6259b..9080c5e3 100644 --- a/.agents/pm/issues/pm-github-uqjf.toon +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -6,14 +6,17 @@ status: in_progress priority: 1 tags[1]: changelog-unreleased created_at: "2026-10-18T18:19:06.214Z" -updated_at: "2026-10-18T18:32:41.098Z" +updated_at: "2026-10-18T18:57:14.608Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent -comments[3]{created_at,author,text}: +comments[6]{created_at,author,text}: "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." + "2026-10-18T18:48:06.831Z",codex-agent,"PR https://github.com/unbraind/pm-github/pull/150 is open at 69ab7dec54bd1493e632d98dab5aa3b7f09a4fee. Fresh --no-local clone strict health passes with required merge drivers in an isolated home. The current full npm gate passes all 4 new real-shape attribution cases, including the additional foreign/unknown canonical-link boundary. Current-head function-only revert copies are being rechecked without mutating any running gate checkout. CodeQL and Semgrep pass; full runtime gates and Node 22/26 CI are still running." + "2026-10-18T18:54:50.388Z",codex-agent,"CI Node 26 passed 897/897 tests with zero failures, skips or cancellations, then the unchanged exact branch gate correctly refused 99.96 percent. closeout.ts line 490 lacked the compact-report positive-close arm after former multi-PR success fixtures became ambiguity refusals. Added an assertion that a real single-delivery dry-run remains close in compact output; no production code or threshold change. Superseded local closeout gates were stopped to rerun the complete corrected test corpus." + "2026-10-18T18:57:14.608Z",codex-agent,"Focused compact projection verification passed: node --test --test-name-pattern=\"dry-run plans same-repo\" test/closeout.test.ts, 1/1 with zero failures/skips. This preserves a valid single-delivery close action alongside individual refusals and routine skip counts; the exact branch gate will rerun without weakening its threshold. Current-head real-shape function-only revert copies failed 2/2, with zero test-load failures." files[7]{path,scope}: closeout.ts,project README.md,project diff --git a/test/closeout.test.ts b/test/closeout.test.ts index 7a4ca79a..cab10cea 100644 --- a/test/closeout.test.ts +++ b/test/closeout.test.ts @@ -116,6 +116,7 @@ test("dry-run plans same-repo, foreign, unknown, terminal and excluded items wit const report = await closeout(root, { pr: ["42", "42"], skip: [ids[3], ids[3]] }); assert.equal(report.dry_run, true); assert.equal(report.closed, 0); + assert.equal(compactCloseoutReport(report).plan[0]!.action, "close"); assert.deepEqual(report.plan.map(row => row.action), ["close", "skip", "skip", "skip", "skip", "skip"]); assert.match(report.plan[1]!.reason, /another repository/); assert.match(report.plan[2]!.reason, /does not exist/); From e95ecc2dddb084fc0111038f3f29f59683ea922d Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:17:11 +0200 Subject: [PATCH 3/8] Record explicit attribution validation receipts --- .agents/pm/history/pm-github-uqjf.jsonl | 4 ++++ .agents/pm/issues/pm-github-uqjf.toon | 11 ++++++----- docs/explicit-closeout-attribution.md | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl index bbadf31a..e436e173 100644 --- a/.agents/pm/history/pm-github-uqjf.jsonl +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -11,3 +11,7 @@ {"hash_algorithm":"sha256","ts":"2026-10-18T18:48:06.831Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-18T18:48:06.831Z","author":"codex-agent","text":"PR https://github.com/unbraind/pm-github/pull/150 is open at 69ab7dec54bd1493e632d98dab5aa3b7f09a4fee. Fresh --no-local clone strict health passes with required merge drivers in an isolated home. The current full npm gate passes all 4 new real-shape attribution cases, including the additional foreign/unknown canonical-link boundary. Current-head function-only revert copies are being rechecked without mutating any running gate checkout. CodeQL and Semgrep pass; full runtime gates and Node 22/26 CI are still running."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:48:06.831Z"}],"before_hash":"fcc5d63a191500b5888297857a1b9ba117d0f6344ca3a0894381084c81857dd7","after_hash":"cecc285308c8127e64a6eb5f68d733e37462adce29e2d516fc52c802a6ece1b5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b5063d5bf9d0255dadd327005708feb0de1629fb8117bb4f5190a84362701b8f"} {"hash_algorithm":"sha256","ts":"2026-10-18T18:54:50.389Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-10-18T18:54:50.388Z","author":"codex-agent","text":"CI Node 26 passed 897/897 tests with zero failures, skips or cancellations, then the unchanged exact branch gate correctly refused 99.96 percent. closeout.ts line 490 lacked the compact-report positive-close arm after former multi-PR success fixtures became ambiguity refusals. Added an assertion that a real single-delivery dry-run remains close in compact output; no production code or threshold change. Superseded local closeout gates were stopped to rerun the complete corrected test corpus."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:54:50.389Z"}],"before_hash":"cecc285308c8127e64a6eb5f68d733e37462adce29e2d516fc52c802a6ece1b5","after_hash":"22b87573fdb4a16f7c0abc64ab28811b65cb8e59a4bc4fc2aafd8751f679f02d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a8c0ff4c8c568b8624836155ca24baa912905dc651288529bd8da72bbcc52890"} {"hash_algorithm":"sha256","ts":"2026-10-18T18:57:14.608Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-10-18T18:57:14.608Z","author":"codex-agent","text":"Focused compact projection verification passed: node --test --test-name-pattern=\"dry-run plans same-repo\" test/closeout.test.ts, 1/1 with zero failures/skips. This preserves a valid single-delivery close action alongside individual refusals and routine skip counts; the exact branch gate will rerun without weakening its threshold. Current-head real-shape function-only revert copies failed 2/2, with zero test-load failures."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T18:57:14.608Z"}],"before_hash":"22b87573fdb4a16f7c0abc64ab28811b65cb8e59a4bc4fc2aafd8751f679f02d","after_hash":"e6304d51bb6114471718e0f17cc01b56e4991a363be89ba4dfaabcbf92389bb4","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"9e577e6f2037d2598f459616fbcbe9e5f6dfddbd42669e91ee121edbb5dc53e9"} +{"hash_algorithm":"sha256","ts":"2026-10-18T19:11:07.629Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"node --test --test-name-pattern='dry-run plans same-repo' test/closeout.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-18T19:11:07.119Z","source_kind":"local_mutation","source_ref":"fix/closeout-explicit-delivery"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T19:11:07.629Z"}],"before_hash":"e6304d51bb6114471718e0f17cc01b56e4991a363be89ba4dfaabcbf92389bb4","after_hash":"f9bd2958f09544242895ea6968bfc9e6c4e6f07222b9f30f6b0285b547ccd369","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"4ab149e73ef2750375bb5c6c678f5347316a68e98b23e7ed5273ad62dd04b542"} +{"hash_algorithm":"sha256","ts":"2026-10-18T21:13:13.689Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-10-18T21:13:13.683Z","author":"codex-agent","text":"Code-head CI is green at b21ff5bdbdcfdab6c97b1204d73ff528b0f65745: Node 22 and 26 passed with exact 100/100/100/100 coverage over 14 authored modules; Node 22 reports 897/897 tests with zero skips or failures. CodeQL and Semgrep passed. Full local npm and Bun release checks are still running; no local completion is claimed yet. Automated reviewer approval is missing: Sourcery quota, CodeRabbit skipped review, and Greptile ended trial. CI: https://github.com/unbraind/pm-github/actions/runs/39156538670."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T21:13:13.689Z"}],"before_hash":"f9bd2958f09544242895ea6968bfc9e6c4e6f07222b9f30f6b0285b547ccd369","after_hash":"a8b6519af91fbc1a8daca2a548ea8ee78758d6c0871af0bebe6e1d0877bb20c9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"01860b5fbe6c8caabc2844a1383d60932b8c2c5f1357a8efddb79e12126d30af"} +{"hash_algorithm":"sha256","ts":"2026-10-18T22:13:39.500Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/7","value":{"created_at":"2026-10-18T22:13:39.500Z","author":"codex-agent","text":"Full local npm run release:check and bun run release:check both exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each passed 897/897 tests with zero failures, skips or cancellations and exact 100/100/100/100 over all 14 authored modules: 10471 lines and statements, 274 functions and 3187 branches, all covered. Typecheck, build, docstrings, lint, duplication, privacy, production audit, pack dry run, changelog check and publication-attestation checks passed at unchanged pins and thresholds. The linked real-shape suite passed 4/4 and the compact single-delivery case passed 1/1; reverting only the old in-progress bypass makes both real-shape regressions fail. GitHub independently confirms delivery PR 19 merged at 2026-10-08T01:19:51Z. PR 150 is separate from release PR 149; existing pm-ado PR 32 records remain untouched and need regeneration with explicit PR 19 evidence. Final receipt checks and final-head CI follow. No merge, release dispatch, tag or publication was performed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:13:39.500Z"}],"before_hash":"a8b6519af91fbc1a8daca2a548ea8ee78758d6c0871af0bebe6e1d0877bb20c9","after_hash":"0c1b5706486110804136d52a6e9e3453c3bd0b5a58559597e6702fbc7643d225","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"edeeb52e333179859f64f6c8f89fdda4406342920994b573a99ac3a646cc236e"} +{"hash_algorithm":"sha256","ts":"2026-10-18T22:13:45.196Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:13:45.196Z"}],"before_hash":"0c1b5706486110804136d52a6e9e3453c3bd0b5a58559597e6702fbc7643d225","after_hash":"1b6f802810ae14d99890a83cee252bf00194b9667df3f54e252368ef2702344a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"ed9b75047d51c2f76ffdc374743b367c2efc7e18eba60d7ad6f129da10d56458"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon index 9080c5e3..eb5791f7 100644 --- a/.agents/pm/issues/pm-github-uqjf.toon +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -6,17 +6,17 @@ status: in_progress priority: 1 tags[1]: changelog-unreleased created_at: "2026-10-18T18:19:06.214Z" -updated_at: "2026-10-18T18:57:14.608Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-10-18T22:13:45.196Z" author: codex-agent -comments[6]{created_at,author,text}: +comments[8]{created_at,author,text}: "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." "2026-10-18T18:48:06.831Z",codex-agent,"PR https://github.com/unbraind/pm-github/pull/150 is open at 69ab7dec54bd1493e632d98dab5aa3b7f09a4fee. Fresh --no-local clone strict health passes with required merge drivers in an isolated home. The current full npm gate passes all 4 new real-shape attribution cases, including the additional foreign/unknown canonical-link boundary. Current-head function-only revert copies are being rechecked without mutating any running gate checkout. CodeQL and Semgrep pass; full runtime gates and Node 22/26 CI are still running." "2026-10-18T18:54:50.388Z",codex-agent,"CI Node 26 passed 897/897 tests with zero failures, skips or cancellations, then the unchanged exact branch gate correctly refused 99.96 percent. closeout.ts line 490 lacked the compact-report positive-close arm after former multi-PR success fixtures became ambiguity refusals. Added an assertion that a real single-delivery dry-run remains close in compact output; no production code or threshold change. Superseded local closeout gates were stopped to rerun the complete corrected test corpus." "2026-10-18T18:57:14.608Z",codex-agent,"Focused compact projection verification passed: node --test --test-name-pattern=\"dry-run plans same-repo\" test/closeout.test.ts, 1/1 with zero failures/skips. This preserves a valid single-delivery close action alongside individual refusals and routine skip counts; the exact branch gate will rerun without weakening its threshold. Current-head real-shape function-only revert copies failed 2/2, with zero test-load failures." + "2026-10-18T21:13:13.683Z",codex-agent,"Code-head CI is green at b21ff5bdbdcfdab6c97b1204d73ff528b0f65745: Node 22 and 26 passed with exact 100/100/100/100 coverage over 14 authored modules; Node 22 reports 897/897 tests with zero skips or failures. CodeQL and Semgrep passed. Full local npm and Bun release checks are still running; no local completion is claimed yet. Automated reviewer approval is missing: Sourcery quota, CodeRabbit skipped review, and Greptile ended trial. CI: https://github.com/unbraind/pm-github/actions/runs/39156538670." + "2026-10-18T22:13:39.500Z",codex-agent,"Full local npm run release:check and bun run release:check both exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each passed 897/897 tests with zero failures, skips or cancellations and exact 100/100/100/100 over all 14 authored modules: 10471 lines and statements, 274 functions and 3187 branches, all covered. Typecheck, build, docstrings, lint, duplication, privacy, production audit, pack dry run, changelog check and publication-attestation checks passed at unchanged pins and thresholds. The linked real-shape suite passed 4/4 and the compact single-delivery case passed 1/1; reverting only the old in-progress bypass makes both real-shape regressions fail. GitHub independently confirms delivery PR 19 merged at 2026-10-08T01:19:51Z. PR 150 is separate from release PR 149; existing pm-ado PR 32 records remain untouched and need regeneration with explicit PR 19 evidence. Final receipt checks and final-head CI follow. No merge, release dispatch, tag or publication was performed." files[7]{path,scope}: closeout.ts,project README.md,project @@ -25,8 +25,9 @@ files[7]{path,scope}: test/closeout.test.ts,project test/fixtures/closeout-attribution/pm-ado-16.json,project test/fixtures/closeout-attribution/pm-ado-19.json,project -tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}: +tests[2]{command,scope,provenance{author,created_at,source_kind,source_ref}}: node --test test/closeout-attribution.test.ts,project,codex-agent,"2026-10-18T18:27:23.663Z",local_mutation,fix/closeout-explicit-delivery + node --test --test-name-pattern='dry-run plans same-repo' test/closeout.test.ts,project,codex-agent,"2026-10-18T19:11:07.119Z",local_mutation,fix/closeout-explicit-delivery docs[2]{path,scope}: docs/explicit-closeout-attribution.md,project README.md,project diff --git a/docs/explicit-closeout-attribution.md b/docs/explicit-closeout-attribution.md index 6d432eac..86f12f7e 100644 --- a/docs/explicit-closeout-attribution.md +++ b/docs/explicit-closeout-attribution.md @@ -14,3 +14,5 @@ The entire selected PR set is planned before any SDK close. If multiple merged P The minimal fixtures retain the real titles, relevant body paragraphs and commit messages fetched with `gh pr view -R unbraind/pm-ado 16 --json title,body,commits` and the corresponding PR 19 command. Neither historical body explicitly declares delivery. Both are therefore refused until an explicit link or declaration is supplied; with the item's PR URL set to 19, closeout uses its October 8 merge chronology. Existing proposed pm-ado PR 32 tracker history is not rewritten here. `node --test test/closeout-attribution.test.ts` passes all four real-tracker cases. Restoring only the old in-progress bypass in runCloseout makes both real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Both package release gates remain mandatory at unchanged thresholds; final receipts are in the PM item and PR. + +Both npm run release:check and bun run release:check exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each ran 897/897 tests with zero failures, cancellations or skips. Both measured exact 100/100/100/100 over all 14 authored modules: 10471/10471 lines and statements, 274/274 functions and 3187/3187 branches. Dependency pins, source exemptions and thresholds are unchanged. The focused compact-output single-delivery case also passed. Final tracker/documentation receipts are regenerated and checked separately; final-head CI and fresh-clone strict health are reported in the PR. From c9a29d199ac9982e842e28cfdf6b75fb014572c3 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:55:38 +0200 Subject: [PATCH 4/8] Keep closeout declarations on one line and ambiguity on claimants --- .agents/pm/history/pm-github-uqjf.jsonl | 3 +++ .agents/pm/issues/pm-github-uqjf.toon | 8 ++++++-- closeout.ts | 5 +++-- docs/explicit-closeout-attribution.md | 8 ++++---- test/closeout-attribution.test.ts | 23 ++++++++++++++++++++--- 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl index e436e173..cdfa19ff 100644 --- a/.agents/pm/history/pm-github-uqjf.jsonl +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -15,3 +15,6 @@ {"hash_algorithm":"sha256","ts":"2026-10-18T21:13:13.689Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-10-18T21:13:13.683Z","author":"codex-agent","text":"Code-head CI is green at b21ff5bdbdcfdab6c97b1204d73ff528b0f65745: Node 22 and 26 passed with exact 100/100/100/100 coverage over 14 authored modules; Node 22 reports 897/897 tests with zero skips or failures. CodeQL and Semgrep passed. Full local npm and Bun release checks are still running; no local completion is claimed yet. Automated reviewer approval is missing: Sourcery quota, CodeRabbit skipped review, and Greptile ended trial. CI: https://github.com/unbraind/pm-github/actions/runs/39156538670."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T21:13:13.689Z"}],"before_hash":"f9bd2958f09544242895ea6968bfc9e6c4e6f07222b9f30f6b0285b547ccd369","after_hash":"a8b6519af91fbc1a8daca2a548ea8ee78758d6c0871af0bebe6e1d0877bb20c9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"01860b5fbe6c8caabc2844a1383d60932b8c2c5f1357a8efddb79e12126d30af"} {"hash_algorithm":"sha256","ts":"2026-10-18T22:13:39.500Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/7","value":{"created_at":"2026-10-18T22:13:39.500Z","author":"codex-agent","text":"Full local npm run release:check and bun run release:check both exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each passed 897/897 tests with zero failures, skips or cancellations and exact 100/100/100/100 over all 14 authored modules: 10471 lines and statements, 274 functions and 3187 branches, all covered. Typecheck, build, docstrings, lint, duplication, privacy, production audit, pack dry run, changelog check and publication-attestation checks passed at unchanged pins and thresholds. The linked real-shape suite passed 4/4 and the compact single-delivery case passed 1/1; reverting only the old in-progress bypass makes both real-shape regressions fail. GitHub independently confirms delivery PR 19 merged at 2026-10-08T01:19:51Z. PR 150 is separate from release PR 149; existing pm-ado PR 32 records remain untouched and need regeneration with explicit PR 19 evidence. Final receipt checks and final-head CI follow. No merge, release dispatch, tag or publication was performed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:13:39.500Z"}],"before_hash":"a8b6519af91fbc1a8daca2a548ea8ee78758d6c0871af0bebe6e1d0877bb20c9","after_hash":"0c1b5706486110804136d52a6e9e3453c3bd0b5a58559597e6702fbc7643d225","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"edeeb52e333179859f64f6c8f89fdda4406342920994b573a99ac3a646cc236e"} {"hash_algorithm":"sha256","ts":"2026-10-18T22:13:45.196Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:13:45.196Z"}],"before_hash":"0c1b5706486110804136d52a6e9e3453c3bd0b5a58559597e6702fbc7643d225","after_hash":"1b6f802810ae14d99890a83cee252bf00194b9667df3f54e252368ef2702344a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"ed9b75047d51c2f76ffdc374743b367c2efc7e18eba60d7ad6f129da10d56458"} +{"hash_algorithm":"sha256","ts":"2026-10-18T22:53:53.595Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:53:53.595Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"1b6f802810ae14d99890a83cee252bf00194b9667df3f54e252368ef2702344a","after_hash":"7649b3ffec1bc9b181d2ea17c7beb5fc75d4e54ac210a53b217caab1b6dea3fb","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"eaae7c264bbaae8b7446f1c5e255ca4d6f56d848b12d873f8b2b72d3c781b956"} +{"hash_algorithm":"sha256","ts":"2026-10-18T22:54:00.823Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/8","value":{"created_at":"2026-10-18T22:54:00.823Z","author":"codex-agent","text":"Review round 1: new PR150 threads PRRT_kwDOTbN4pM6tvYM7 and PRRT_kwDOTbN4pM6tvYM- are confirmed. Whole-line delivery declarations currently accept newline whitespace between keyword and item id; ambiguity rewriting also changes mention-only rows when other PRs claim delivery. Restrict declarations to horizontal whitespace and rewrite only rows whose PR is in the existing claimant set. Extend real-SDK attribution scenarios with multiline negatives and two claimants plus a mention-only PR. Full npm/Bun gates and function-only revert proofs follow; threads remain unanswered and unresolved."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:54:00.823Z"}],"before_hash":"7649b3ffec1bc9b181d2ea17c7beb5fc75d4e54ac210a53b217caab1b6dea3fb","after_hash":"2310a5ecdb77b00692aa513fdb0443cb2a3f330a440990b6c0427d3e1017874a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"940fb9d9d1e296310a55ff380d788285cbc336e1fe41719b1422d1fafb1cf64c"} +{"hash_algorithm":"sha256","ts":"2026-10-18T22:55:26.292Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-10-18T22:55:26.292Z","author":"codex-agent","text":"Review regressions pass 6/6 against real temporary SDK trackers. Function-only behavioral revert proofs: restoring only the old declaration regex in runCloseout makes the multiline-keyword/trailer scenario fail with ERR_ASSERTION (exit 1); removing only claimant PR membership from the ambiguity condition makes the two-claims-plus-mention scenario fail with ERR_ASSERTION (exit 1). Both retain current tests and other fixes. Same-line spaces/tabs, real linked PR19 chronology and single-delivery compact output remain covered. Typecheck, lint and docstrings pass. Full npm/Bun gates remain pending."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:55:26.292Z"}],"before_hash":"2310a5ecdb77b00692aa513fdb0443cb2a3f330a440990b6c0427d3e1017874a","after_hash":"b45433d31a882407b49dbd2a77c1a45cae0f7d9ba7ac443d0eacc4f9b141bde9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"377c5e92e045969ea5f1d0197c4007eef9cc65401d895e6a0cc8c236e24c9e8e"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon index eb5791f7..360eb05c 100644 --- a/.agents/pm/issues/pm-github-uqjf.toon +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -6,9 +6,11 @@ status: in_progress priority: 1 tags[1]: changelog-unreleased created_at: "2026-10-18T18:19:06.214Z" -updated_at: "2026-10-18T22:13:45.196Z" +updated_at: "2026-10-18T22:55:26.292Z" +assignee: codex-agent +claim_principal: codex-agent author: codex-agent -comments[8]{created_at,author,text}: +comments[10]{created_at,author,text}: "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." @@ -17,6 +19,8 @@ comments[8]{created_at,author,text}: "2026-10-18T18:57:14.608Z",codex-agent,"Focused compact projection verification passed: node --test --test-name-pattern=\"dry-run plans same-repo\" test/closeout.test.ts, 1/1 with zero failures/skips. This preserves a valid single-delivery close action alongside individual refusals and routine skip counts; the exact branch gate will rerun without weakening its threshold. Current-head real-shape function-only revert copies failed 2/2, with zero test-load failures." "2026-10-18T21:13:13.683Z",codex-agent,"Code-head CI is green at b21ff5bdbdcfdab6c97b1204d73ff528b0f65745: Node 22 and 26 passed with exact 100/100/100/100 coverage over 14 authored modules; Node 22 reports 897/897 tests with zero skips or failures. CodeQL and Semgrep passed. Full local npm and Bun release checks are still running; no local completion is claimed yet. Automated reviewer approval is missing: Sourcery quota, CodeRabbit skipped review, and Greptile ended trial. CI: https://github.com/unbraind/pm-github/actions/runs/39156538670." "2026-10-18T22:13:39.500Z",codex-agent,"Full local npm run release:check and bun run release:check both exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each passed 897/897 tests with zero failures, skips or cancellations and exact 100/100/100/100 over all 14 authored modules: 10471 lines and statements, 274 functions and 3187 branches, all covered. Typecheck, build, docstrings, lint, duplication, privacy, production audit, pack dry run, changelog check and publication-attestation checks passed at unchanged pins and thresholds. The linked real-shape suite passed 4/4 and the compact single-delivery case passed 1/1; reverting only the old in-progress bypass makes both real-shape regressions fail. GitHub independently confirms delivery PR 19 merged at 2026-10-08T01:19:51Z. PR 150 is separate from release PR 149; existing pm-ado PR 32 records remain untouched and need regeneration with explicit PR 19 evidence. Final receipt checks and final-head CI follow. No merge, release dispatch, tag or publication was performed." + "2026-10-18T22:54:00.823Z",codex-agent,"Review round 1: new PR150 threads PRRT_kwDOTbN4pM6tvYM7 and PRRT_kwDOTbN4pM6tvYM- are confirmed. Whole-line delivery declarations currently accept newline whitespace between keyword and item id; ambiguity rewriting also changes mention-only rows when other PRs claim delivery. Restrict declarations to horizontal whitespace and rewrite only rows whose PR is in the existing claimant set. Extend real-SDK attribution scenarios with multiline negatives and two claimants plus a mention-only PR. Full npm/Bun gates and function-only revert proofs follow; threads remain unanswered and unresolved." + "2026-10-18T22:55:26.292Z",codex-agent,"Review regressions pass 6/6 against real temporary SDK trackers. Function-only behavioral revert proofs: restoring only the old declaration regex in runCloseout makes the multiline-keyword/trailer scenario fail with ERR_ASSERTION (exit 1); removing only claimant PR membership from the ambiguity condition makes the two-claims-plus-mention scenario fail with ERR_ASSERTION (exit 1). Both retain current tests and other fixes. Same-line spaces/tabs, real linked PR19 chronology and single-delivery compact output remain covered. Typecheck, lint and docstrings pass. Full npm/Bun gates remain pending." files[7]{path,scope}: closeout.ts,project README.md,project diff --git a/closeout.ts b/closeout.ts index 066abbf2..e15fd349 100644 --- a/closeout.ts +++ b/closeout.ts @@ -406,7 +406,7 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti // Only canonical delivery metadata or a whole-line trailer/closing declaration is authoritative. const linked = [item.github_pr, item.pr_url, item.pull_request_url].includes(pr.html_url); const id = item.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const declaration = new RegExp(`^(?:[-*]\\s+)?(?:${[...CLOSING_KEYWORDS].join("|")}|pm-github:)\\s+${id}[.!]?\\s*$`, "im").test(pr.body ?? ""); + const declaration = new RegExp(`^(?:[-*][ \\t]+)?(?:${[...CLOSING_KEYWORDS].join("|")}|pm-github:)[ \\t]+${id}[.!]?[ \\t]*$`, "im").test(pr.body ?? ""); if (linked || declaration) { const ref = refs.find(ref => ref.item === item.id && ref.repo.toLowerCase() === repo.toLowerCase()); if (ref) ref.declared = true; @@ -452,7 +452,8 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti } // Resolve attribution across the complete selection before the first SDK write. for (const row of plan) { - if ((candidates.get(row.item)?.size ?? 0) > 1 && !["already_terminal", "other_repository", "unknown_item", "explicit_skip"].includes(row.code)) { + const claimants = candidates.get(row.item); + if (claimants && claimants.size > 1 && claimants.has(row.pr) && !["already_terminal", "other_repository", "unknown_item", "explicit_skip"].includes(row.code)) { row.action = "skip"; row.code = "ambiguous_delivery"; row.reason = "Multiple merged PRs explicitly claim delivery; select the intended PR before closing."; diff --git a/docs/explicit-closeout-attribution.md b/docs/explicit-closeout-attribution.md index 86f12f7e..011f6c87 100644 --- a/docs/explicit-closeout-attribution.md +++ b/docs/explicit-closeout-attribution.md @@ -5,14 +5,14 @@ pm-ado issue [33](https://github.com/unbraind/pm-ado/issues/33) identified a dur Closeout now requires explicit evidence regardless of the item's status: - A closing keyword immediately before a canonical tracker link. -- A whole-line closing declaration such as `Closes pm-ado-5w3m`. -- A whole-line `pm-github: pm-ado-5w3m` trailer. +- A whole-line closing declaration such as `Closes pm-ado-5w3m`, with spaces or tabs between the keyword and ID. +- A whole-line `pm-github: pm-ado-5w3m` trailer, with spaces or tabs between the trailer and ID. A newline cannot join a declaration. - The item's `github_pr`, `pr_url` or `pull_request_url` set to the exact PR URL. -The entire selected PR set is planned before any SDK close. If multiple merged PRs explicitly claim the same item, every candidate is reported as `ambiguous_delivery` and no candidate closes it. Existing required-check, child-item, epic, skip and terminal-item safeguards still apply. Titles and incidental mentions never establish delivery. +The entire selected PR set is planned before any SDK close. If multiple merged PRs explicitly claim the same item, every claimant is reported as `ambiguous_delivery` and no candidate closes it. A PR that only mentions the item retains `not_delivery`. Existing required-check, child-item, epic, skip and terminal-item safeguards still apply. Titles and incidental mentions never establish delivery. The minimal fixtures retain the real titles, relevant body paragraphs and commit messages fetched with `gh pr view -R unbraind/pm-ado 16 --json title,body,commits` and the corresponding PR 19 command. Neither historical body explicitly declares delivery. Both are therefore refused until an explicit link or declaration is supplied; with the item's PR URL set to 19, closeout uses its October 8 merge chronology. Existing proposed pm-ado PR 32 tracker history is not rewritten here. -`node --test test/closeout-attribution.test.ts` passes all four real-tracker cases. Restoring only the old in-progress bypass in runCloseout makes both real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Both package release gates remain mandatory at unchanged thresholds; final receipts are in the PM item and PR. +`node --test test/closeout-attribution.test.ts` covers six real-tracker cases, including multiline declarations and two claimants plus a mention-only PR. Restoring only the old in-progress bypass in runCloseout makes both original real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Review-round revert proofs and full gate receipts are recorded in the PM item and PR when complete; both package release gates remain mandatory at unchanged thresholds. Both npm run release:check and bun run release:check exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each ran 897/897 tests with zero failures, cancellations or skips. Both measured exact 100/100/100/100 over all 14 authored modules: 10471/10471 lines and statements, 274/274 functions and 3187/3187 branches. Dependency pins, source exemptions and thresholds are unchanged. The focused compact-output single-delivery case also passed. Final tracker/documentation receipts are regenerated and checked separately; final-head CI and fresh-clone strict health are reported in the PR. diff --git a/test/closeout-attribution.test.ts b/test/closeout-attribution.test.ts index 65304b49..b9ac9049 100644 --- a/test/closeout-attribution.test.ts +++ b/test/closeout-attribution.test.ts @@ -29,14 +29,14 @@ async function scenario(linked: boolean, bodies?: string[]) { } const api: CloseoutApi = { base: "https://api.github.com", next: () => undefined, get: async endpoint => { const n = Number(endpoint.split("/").at(-1)); - const value = endpoint.includes("/pulls/") ? { number: n, body: (bodies?.[n === 16 ? 0 : 1] ?? recorded.find(p => p.number === n)!.body).replaceAll("pm-ado-5w3m", item.id), + const value = endpoint.includes("/pulls/") ? { number: n, body: (bodies?.[[16, 19, 20].indexOf(n)] ?? recorded.find(p => p.number === n)!.body).replaceAll("pm-ado-5w3m", item.id), merged: true, merged_at: n === 16 ? "2026-10-04T12:58:08Z" : "2026-10-08T01:19:51Z", merge_commit_sha: "b".repeat(40), html_url: url(n), head: { sha: head }, base: { ref: "main", repo: { full_name: repo } } } : endpoint.includes("/protection/") ? { contexts: ["CI"], checks: [] } : endpoint.includes("/check-runs") ? { total_count: 1, check_runs: [{ name: "CI", head_sha: head, status: "completed", conclusion: "success", app: { id: 7 } }] } : []; return { body: JSON.stringify(value) }; } }; - const report = await runCloseout(root, repo, { pr: ["16", "19"], apply: true }, api); + const report = await runCloseout(root, repo, { pr: bodies?.length === 3 ? ["16", "19", "20"] : ["16", "19"], apply: true }, api); return { report, item: (await get(item.id, {}, sdk)).item }; } finally { rmSync(root, { recursive: true, force: true }); } } @@ -58,7 +58,7 @@ test("the item's explicit PR link selects real delivery #19 and its merge chrono }); test("closing item ids and pm-github trailers explicitly deliver without a canonical body link", async () => { - for (const declaration of ["Closes pm-ado-5w3m", "- Fixes pm-ado-5w3m.", "pm-github: pm-ado-5w3m", + for (const declaration of ["Closes pm-ado-5w3m", "- Fixes pm-ado-5w3m.", "pm-github: pm-ado-5w3m", "*\tFixes\tpm-ado-5w3m!\t", "See https://github.com/unbraind/pm-ado/blob/main/.agents/pm/issues/unrelated.toon\nSee https://github.com/other/repo/blob/main/.agents/pm/issues/pm-ado-5w3m.toon\nCloses pm-ado-5w3m"]) { const { report } = await scenario(false, ["No delivery claim", declaration]); assert.equal(report.closed, 1); @@ -66,6 +66,23 @@ test("closing item ids and pm-github trailers explicitly deliver without a canon } }); +test("multiline keywords and trailers never declare delivery", async () => { + for (const declaration of ["Closes\npm-ado-5w3m", "pm-github:\npm-ado-5w3m", "Closes\r\npm-ado-5w3m", "pm-github:\t\npm-ado-5w3m"]) { + const { report, item } = await scenario(false, ["No delivery claim", declaration]); + assert.equal(report.closed, 0, declaration); + assert.equal(item.status, "in_progress"); + assert.ok(report.plan.every(row => row.action === "skip")); + } +}); + +test("ambiguity applies to two claiming PRs while a third mention remains not_delivery", async () => { + const mention = "See https://github.com/unbraind/pm-ado/blob/main/.agents/pm/issues/pm-ado-5w3m.toon"; + const { report, item } = await scenario(false, ["Closes pm-ado-5w3m", "pm-github: pm-ado-5w3m", mention]); + assert.equal(report.closed, 0); + assert.deepEqual(report.plan.map(row => [row.pr, row.code]), [[16, "ambiguous_delivery"], [19, "ambiguous_delivery"], [20, "not_delivery"]]); + assert.equal(item.status, "in_progress"); +}); + test("two explicit merged delivery claims remain ambiguous before any close", async () => { const { report, item } = await scenario(false, ["Closes pm-ado-5w3m", "pm-github: pm-ado-5w3m"]); assert.equal(report.closed, 0); From 31cc826b2e2f8c76c0220b3852b88b8844071ec6 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 19 Oct 2026 03:21:10 +0200 Subject: [PATCH 5/8] Record closeout review and gate receipts --- .agents/pm/history/pm-github-uqjf.jsonl | 6 ++++++ .agents/pm/issues/pm-github-uqjf.toon | 11 +++++++---- docs/explicit-closeout-attribution.md | 8 ++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl index cdfa19ff..42edeaa1 100644 --- a/.agents/pm/history/pm-github-uqjf.jsonl +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -18,3 +18,9 @@ {"hash_algorithm":"sha256","ts":"2026-10-18T22:53:53.595Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:53:53.595Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"1b6f802810ae14d99890a83cee252bf00194b9667df3f54e252368ef2702344a","after_hash":"7649b3ffec1bc9b181d2ea17c7beb5fc75d4e54ac210a53b217caab1b6dea3fb","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"eaae7c264bbaae8b7446f1c5e255ca4d6f56d848b12d873f8b2b72d3c781b956"} {"hash_algorithm":"sha256","ts":"2026-10-18T22:54:00.823Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/8","value":{"created_at":"2026-10-18T22:54:00.823Z","author":"codex-agent","text":"Review round 1: new PR150 threads PRRT_kwDOTbN4pM6tvYM7 and PRRT_kwDOTbN4pM6tvYM- are confirmed. Whole-line delivery declarations currently accept newline whitespace between keyword and item id; ambiguity rewriting also changes mention-only rows when other PRs claim delivery. Restrict declarations to horizontal whitespace and rewrite only rows whose PR is in the existing claimant set. Extend real-SDK attribution scenarios with multiline negatives and two claimants plus a mention-only PR. Full npm/Bun gates and function-only revert proofs follow; threads remain unanswered and unresolved."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:54:00.823Z"}],"before_hash":"7649b3ffec1bc9b181d2ea17c7beb5fc75d4e54ac210a53b217caab1b6dea3fb","after_hash":"2310a5ecdb77b00692aa513fdb0443cb2a3f330a440990b6c0427d3e1017874a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"940fb9d9d1e296310a55ff380d788285cbc336e1fe41719b1422d1fafb1cf64c"} {"hash_algorithm":"sha256","ts":"2026-10-18T22:55:26.292Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-10-18T22:55:26.292Z","author":"codex-agent","text":"Review regressions pass 6/6 against real temporary SDK trackers. Function-only behavioral revert proofs: restoring only the old declaration regex in runCloseout makes the multiline-keyword/trailer scenario fail with ERR_ASSERTION (exit 1); removing only claimant PR membership from the ambiguity condition makes the two-claims-plus-mention scenario fail with ERR_ASSERTION (exit 1). Both retain current tests and other fixes. Same-line spaces/tabs, real linked PR19 chronology and single-delivery compact output remain covered. Typecheck, lint and docstrings pass. Full npm/Bun gates remain pending."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T22:55:26.292Z"}],"before_hash":"2310a5ecdb77b00692aa513fdb0443cb2a3f330a440990b6c0427d3e1017874a","after_hash":"b45433d31a882407b49dbd2a77c1a45cae0f7d9ba7ac443d0eacc4f9b141bde9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"377c5e92e045969ea5f1d0197c4007eef9cc65401d895e6a0cc8c236e24c9e8e"} +{"hash_algorithm":"sha256","ts":"2026-10-18T23:32:52.582Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/10","value":{"created_at":"2026-10-18T23:32:52.582Z","author":"codex-agent","text":"Review code-head CI is green at c9a29d199ac9982e842e28cfdf6b75fb014572c3: Node22 and Node26 each passed 899/899 Node tests, exact 100/100/100/100 over 14 authored modules, and 899 native Bun tests across 41 files. CodeQL and Semgrep passed; 60-second polling finished within 40 minutes. CI receipt: https://github.com/unbraind/pm-github/actions/runs/39174854340 . Both explicit local release checks remain in progress. Review threads have not been replied to or resolved."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T23:32:52.582Z"}],"before_hash":"b45433d31a882407b49dbd2a77c1a45cae0f7d9ba7ac443d0eacc4f9b141bde9","after_hash":"e77614f4d645731db1d3ec6284ffa17dc9b6bfef926f04bc048150b33a6bf5f1","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"eaa788ee793db9815472292124f0b7cca120e1c89225183816ec1f28e2abb986"} +{"hash_algorithm":"sha256","ts":"2026-10-18T23:43:08.088Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/11","value":{"created_at":"2026-10-18T23:43:08.088Z","author":"codex-agent","text":"The first concurrent Bun release-check attempt hit the existing startup-sensitive watchdog probe tracked by pm-github-sah1: test/bounded-cli.test.ts failed its /ready/ assertion because stdout was empty before the unchanged 500 ms child deadline. All six changed attribution scenarios passed in that attempt; both Node22/26 CI jobs passed all 899 tests and exact coverage. Stopped the already-failed execution (exit 143) to avoid spending resources on an invalid run; resulting cancellation diagnostics are not additional regressions. Isolated node --test test/bounded-cli.test.ts passes 2/2 unchanged. A complete Bun release-check rerun will run after the other local gates finish, with unchanged coverage thresholds, test/helper budgets and dependency pins. This reuses the existing follow-up instead of creating a duplicate."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-18T23:43:08.088Z"}],"before_hash":"e77614f4d645731db1d3ec6284ffa17dc9b6bfef926f04bc048150b33a6bf5f1","after_hash":"8d1aefa91ab32a6da0d9f2e4bc4072a13c9cf5bfd61907a3367692081dc7eafa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"c7b3c37fcd47e75eae09a099943c5764a46009e7426bcc0459e4a354f9647b9b"} +{"hash_algorithm":"sha256","ts":"2026-10-19T00:19:52.087Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/12","value":{"created_at":"2026-10-19T00:19:52.087Z","author":"codex-agent","text":"The explicit npm run release:check completed with exit 0 at code head c9a29d199ac9982e842e28cfdf6b75fb014572c3: 899/899 tests, zero failures/skips/cancellations, exact 100/100/100/100 over 14 authored modules. Lines/statements 10472/10472; functions 274/274; branches 3187/3187. All remaining release-check stages passed. The complete bun run release:check rerun has now started after the other local gates finished, with unchanged test/helper budgets, thresholds, source scope and dependency pins. Earlier interrupted concurrent Bun results remain documented separately; no final Bun completion is claimed yet."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T00:19:52.087Z"}],"before_hash":"8d1aefa91ab32a6da0d9f2e4bc4072a13c9cf5bfd61907a3367692081dc7eafa","after_hash":"78ec81f7e5fe679361379ca2b903d9c231d748fa1b2aac0a3a239ed512a0a5d1","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"c81e474079cf9621d577a00b9f507edd9ef38f4e42e86e3d4955d9521cd31119"} +{"hash_algorithm":"sha256","ts":"2026-10-19T00:50:27.912Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/13","value":{"created_at":"2026-10-19T00:50:27.912Z","author":"codex-agent","text":"The complete unchanged Bun rerun has passed the 500 ms startup-watchdog control and all six real-SDK attribution scenarios, including both new review regressions. Remaining workflow acceptance and coverage aggregation are still in progress; no full Bun completion is claimed. The companion release PR149 is now complete at 7b61575b5e1a29ed929358f188456412f6b3b769 with both 903-test local gates, final Node22/26 CI and independent fresh-clone strict health/validation/privacy: https://github.com/unbraind/pm-github/actions/runs/39179989399 . This attribution branch stays separately verified; combined merge acceptance remains with the orchestrator."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T00:50:27.912Z"}],"before_hash":"78ec81f7e5fe679361379ca2b903d9c231d748fa1b2aac0a3a239ed512a0a5d1","after_hash":"ad807dfd247c9671d95ac6bb8afef556d9ec0ad07a62cbb0e8f579594cc92331","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"2518f37af7b9ec6449bab174bf527aa574e2c353a19d254cd5876034e9c32043"} +{"hash_algorithm":"sha256","ts":"2026-10-19T01:20:55.272Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/14","value":{"created_at":"2026-10-19T01:20:55.272Z","author":"codex-agent","text":"Review round 1 full gates completed at code head c9a29d199ac9982e842e28cfdf6b75fb014572c3: npm run release:check and the complete bun run release:check rerun both exited 0. Each passed 899/899 tests, zero failures/skips/cancellations and exact 100/100/100/100 over 14 authored modules. Lines/statements 10472/10472; functions 274/274; branches npm 3187/3187 and Bun invocation 3185/3185. All stages passed with unchanged thresholds, source scope, dependency pins and helper/test budgets. The initial concurrent Bun attempt reproduced existing pm-github-sah1 /ready/ startup sensitivity and was stopped; the unchanged full rerun passed the 500 ms watchdog as well as all tests. The follow-up remains open. Focused attribution suite 6/6 and compact control 1/1 passed. Restoring only the old declaration regex makes the multiline regression fail; removing only claimant membership makes the two-claimants-plus-mention regression fail, both with ERR_ASSERTION. Same-line/tab declarations, real linked PR19 chronology and mention-only row fidelity remain intact. Completed receipts and initial-flake disclosure are in docs/explicit-closeout-attribution.md. Code-head CI: https://github.com/unbraind/pm-github/actions/runs/39174854340 . Receipt-head CI and independent fresh-clone strict health follow in the PR. No review-thread replies/resolutions or release actions occurred."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T01:20:55.272Z"}],"before_hash":"ad807dfd247c9671d95ac6bb8afef556d9ec0ad07a62cbb0e8f579594cc92331","after_hash":"f0e3ffc40bdacd90a81f6f104e3b3d7d324bb5ed740a6c76bd5797a5ca4e6f23","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"f5565090fd7e0b6331c6356f0350dd87b4c92d5227d629cad4dfa2f37b1a93ac"} +{"hash_algorithm":"sha256","ts":"2026-10-19T01:20:59.793Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T01:20:59.793Z"}],"before_hash":"f0e3ffc40bdacd90a81f6f104e3b3d7d324bb5ed740a6c76bd5797a5ca4e6f23","after_hash":"0a01986786de72f2d10f9b1cdb0ff51f7bb0429499095fdae26c642f849e7e4f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"ddad55bc83750716b4fae92d132b5ea0508f8d7419db9410f7009e085ca70571"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon index 360eb05c..5d027419 100644 --- a/.agents/pm/issues/pm-github-uqjf.toon +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -6,11 +6,9 @@ status: in_progress priority: 1 tags[1]: changelog-unreleased created_at: "2026-10-18T18:19:06.214Z" -updated_at: "2026-10-18T22:55:26.292Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-10-19T01:20:59.793Z" author: codex-agent -comments[10]{created_at,author,text}: +comments[15]{created_at,author,text}: "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." @@ -21,6 +19,11 @@ comments[10]{created_at,author,text}: "2026-10-18T22:13:39.500Z",codex-agent,"Full local npm run release:check and bun run release:check both exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each passed 897/897 tests with zero failures, skips or cancellations and exact 100/100/100/100 over all 14 authored modules: 10471 lines and statements, 274 functions and 3187 branches, all covered. Typecheck, build, docstrings, lint, duplication, privacy, production audit, pack dry run, changelog check and publication-attestation checks passed at unchanged pins and thresholds. The linked real-shape suite passed 4/4 and the compact single-delivery case passed 1/1; reverting only the old in-progress bypass makes both real-shape regressions fail. GitHub independently confirms delivery PR 19 merged at 2026-10-08T01:19:51Z. PR 150 is separate from release PR 149; existing pm-ado PR 32 records remain untouched and need regeneration with explicit PR 19 evidence. Final receipt checks and final-head CI follow. No merge, release dispatch, tag or publication was performed." "2026-10-18T22:54:00.823Z",codex-agent,"Review round 1: new PR150 threads PRRT_kwDOTbN4pM6tvYM7 and PRRT_kwDOTbN4pM6tvYM- are confirmed. Whole-line delivery declarations currently accept newline whitespace between keyword and item id; ambiguity rewriting also changes mention-only rows when other PRs claim delivery. Restrict declarations to horizontal whitespace and rewrite only rows whose PR is in the existing claimant set. Extend real-SDK attribution scenarios with multiline negatives and two claimants plus a mention-only PR. Full npm/Bun gates and function-only revert proofs follow; threads remain unanswered and unresolved." "2026-10-18T22:55:26.292Z",codex-agent,"Review regressions pass 6/6 against real temporary SDK trackers. Function-only behavioral revert proofs: restoring only the old declaration regex in runCloseout makes the multiline-keyword/trailer scenario fail with ERR_ASSERTION (exit 1); removing only claimant PR membership from the ambiguity condition makes the two-claims-plus-mention scenario fail with ERR_ASSERTION (exit 1). Both retain current tests and other fixes. Same-line spaces/tabs, real linked PR19 chronology and single-delivery compact output remain covered. Typecheck, lint and docstrings pass. Full npm/Bun gates remain pending." + "2026-10-18T23:32:52.582Z",codex-agent,"Review code-head CI is green at c9a29d199ac9982e842e28cfdf6b75fb014572c3: Node22 and Node26 each passed 899/899 Node tests, exact 100/100/100/100 over 14 authored modules, and 899 native Bun tests across 41 files. CodeQL and Semgrep passed; 60-second polling finished within 40 minutes. CI receipt: https://github.com/unbraind/pm-github/actions/runs/39174854340 . Both explicit local release checks remain in progress. Review threads have not been replied to or resolved." + "2026-10-18T23:43:08.088Z",codex-agent,"The first concurrent Bun release-check attempt hit the existing startup-sensitive watchdog probe tracked by pm-github-sah1: test/bounded-cli.test.ts failed its /ready/ assertion because stdout was empty before the unchanged 500 ms child deadline. All six changed attribution scenarios passed in that attempt; both Node22/26 CI jobs passed all 899 tests and exact coverage. Stopped the already-failed execution (exit 143) to avoid spending resources on an invalid run; resulting cancellation diagnostics are not additional regressions. Isolated node --test test/bounded-cli.test.ts passes 2/2 unchanged. A complete Bun release-check rerun will run after the other local gates finish, with unchanged coverage thresholds, test/helper budgets and dependency pins. This reuses the existing follow-up instead of creating a duplicate." + "2026-10-19T00:19:52.087Z",codex-agent,"The explicit npm run release:check completed with exit 0 at code head c9a29d199ac9982e842e28cfdf6b75fb014572c3: 899/899 tests, zero failures/skips/cancellations, exact 100/100/100/100 over 14 authored modules. Lines/statements 10472/10472; functions 274/274; branches 3187/3187. All remaining release-check stages passed. The complete bun run release:check rerun has now started after the other local gates finished, with unchanged test/helper budgets, thresholds, source scope and dependency pins. Earlier interrupted concurrent Bun results remain documented separately; no final Bun completion is claimed yet." + "2026-10-19T00:50:27.912Z",codex-agent,"The complete unchanged Bun rerun has passed the 500 ms startup-watchdog control and all six real-SDK attribution scenarios, including both new review regressions. Remaining workflow acceptance and coverage aggregation are still in progress; no full Bun completion is claimed. The companion release PR149 is now complete at 7b61575b5e1a29ed929358f188456412f6b3b769 with both 903-test local gates, final Node22/26 CI and independent fresh-clone strict health/validation/privacy: https://github.com/unbraind/pm-github/actions/runs/39179989399 . This attribution branch stays separately verified; combined merge acceptance remains with the orchestrator." + "2026-10-19T01:20:55.272Z",codex-agent,"Review round 1 full gates completed at code head c9a29d199ac9982e842e28cfdf6b75fb014572c3: npm run release:check and the complete bun run release:check rerun both exited 0. Each passed 899/899 tests, zero failures/skips/cancellations and exact 100/100/100/100 over 14 authored modules. Lines/statements 10472/10472; functions 274/274; branches npm 3187/3187 and Bun invocation 3185/3185. All stages passed with unchanged thresholds, source scope, dependency pins and helper/test budgets. The initial concurrent Bun attempt reproduced existing pm-github-sah1 /ready/ startup sensitivity and was stopped; the unchanged full rerun passed the 500 ms watchdog as well as all tests. The follow-up remains open. Focused attribution suite 6/6 and compact control 1/1 passed. Restoring only the old declaration regex makes the multiline regression fail; removing only claimant membership makes the two-claimants-plus-mention regression fail, both with ERR_ASSERTION. Same-line/tab declarations, real linked PR19 chronology and mention-only row fidelity remain intact. Completed receipts and initial-flake disclosure are in docs/explicit-closeout-attribution.md. Code-head CI: https://github.com/unbraind/pm-github/actions/runs/39174854340 . Receipt-head CI and independent fresh-clone strict health follow in the PR. No review-thread replies/resolutions or release actions occurred." files[7]{path,scope}: closeout.ts,project README.md,project diff --git a/docs/explicit-closeout-attribution.md b/docs/explicit-closeout-attribution.md index 011f6c87..021eda88 100644 --- a/docs/explicit-closeout-attribution.md +++ b/docs/explicit-closeout-attribution.md @@ -13,6 +13,10 @@ The entire selected PR set is planned before any SDK close. If multiple merged P The minimal fixtures retain the real titles, relevant body paragraphs and commit messages fetched with `gh pr view -R unbraind/pm-ado 16 --json title,body,commits` and the corresponding PR 19 command. Neither historical body explicitly declares delivery. Both are therefore refused until an explicit link or declaration is supplied; with the item's PR URL set to 19, closeout uses its October 8 merge chronology. Existing proposed pm-ado PR 32 tracker history is not rewritten here. -`node --test test/closeout-attribution.test.ts` covers six real-tracker cases, including multiline declarations and two claimants plus a mention-only PR. Restoring only the old in-progress bypass in runCloseout makes both original real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Review-round revert proofs and full gate receipts are recorded in the PM item and PR when complete; both package release gates remain mandatory at unchanged thresholds. +`node --test test/closeout-attribution.test.ts` covers six real-tracker cases, including multiline declarations and two claimants plus a mention-only PR. Restoring only the old in-progress bypass in runCloseout makes both original real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Review-round function-only proofs independently restore the old declaration regex or remove claimant membership while retaining the tests and other fix. Each corresponding regression fails with ERR_ASSERTION (exit 1). The restored attribution suite passes 6/6; the compact single-delivery control also passes. -Both npm run release:check and bun run release:check exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each ran 897/897 tests with zero failures, cancellations or skips. Both measured exact 100/100/100/100 over all 14 authored modules: 10471/10471 lines and statements, 274/274 functions and 3187/3187 branches. Dependency pins, source exemptions and thresholds are unchanged. The focused compact-output single-delivery case also passed. Final tracker/documentation receipts are regenerated and checked separately; final-head CI and fresh-clone strict health are reported in the PR. +Before review round 1, both npm run release:check and bun run release:check exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each ran 897/897 tests with zero failures, cancellations or skips. Both measured exact 100/100/100/100 over all 14 authored modules: 10471/10471 lines and statements, 274/274 functions and 3187/3187 branches. Dependency pins, source exemptions and thresholds are unchanged. The focused compact-output single-delivery case also passed. Final tracker/documentation receipts are regenerated and checked separately; final-head CI and fresh-clone strict health are reported in the PR. + +Review round 1 completed both npm run release:check and bun run release:check with exit 0 at code head c9a29d199ac9982e842e28cfdf6b75fb014572c3. Each passed 899/899 tests, zero failures/skips/cancellations and exact 100/100/100/100 over 14 authored modules. Both measured 10472/10472 lines and statements and 274/274 functions; branches were 3187/3187 (npm) and 3185/3185 (Bun invocation). All release-check stages passed with unchanged thresholds, source scope, helper/test budgets and dependency pins. The [review-round tracker receipt](../.agents/pm/history/pm-github-uqjf.jsonl) records completed results separately from earlier pending comments. [Code-head CI](https://github.com/unbraind/pm-github/actions/runs/39174854340) passed Node 22/26, each with 899 Node tests, 899 native Bun tests across 41 files and exact authored-source coverage, plus CodeQL and Semgrep. Receipt-head CI and fresh-clone health are recorded in the PR after that head is pushed. + +The first concurrent Bun attempt hit the existing 500 ms startup-watchdog probe tracked by [pm-github-sah1](../.agents/pm/issues/pm-github-sah1.toon): the child had not printed ready before its deadline. That already-failed attempt was stopped; cancellation diagnostics from stopping it are not additional regressions. The unchanged isolated watchdog suite passed 2/2. The complete Bun rerun, started after the other local gates finished, passed the original watchdog and all 899 tests. The startup-sensitive probe remains a separate open follow-up; this PR changes neither its helper nor its budgets. From ed63b0ef0fd2ea505d036144f3667beb6ad46b37 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 19 Oct 2026 04:29:37 +0200 Subject: [PATCH 6/8] Scan delivery declarations once per pull request --- .agents/pm/history/pm-github-uqjf.jsonl | 3 ++ .agents/pm/issues/pm-github-uqjf.toon | 7 +++-- closeout.ts | 13 ++++++-- docs/explicit-closeout-attribution.md | 4 +++ test/closeout-attribution.test.ts | 41 +++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl index cd00d281..e95dd472 100644 --- a/.agents/pm/history/pm-github-uqjf.jsonl +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -27,3 +27,6 @@ {"hash_algorithm":"sha256","ts":"2026-10-19T02:00:12.518Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:00:12.518Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"0a01986786de72f2d10f9b1cdb0ff51f7bb0429499095fdae26c642f849e7e4f","after_hash":"2ced9c1f0a8f230b4775d0ac6e9470fee43238e74c1d463b3dc2547fdb0a7f7f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"064ab20afbf35597dce1eb5bf9f8d009a3bc46c82d81c8606d86ca70672697cd"} {"hash_algorithm":"sha256","ts":"2026-10-19T02:00:19.487Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/15","value":{"created_at":"2026-10-19T02:00:19.487Z","author":"codex-agent","text":"Merge origin/main after PR 149 into PR 150 using the configured PM merge drivers. The only merge conflict is CHANGELOG.md; retain main's version and regenerate after tracker writes. Check clone-local receipts before reconciliation, run both unchanged release gates, then verify health from an independent clone and wait for CI."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:00:19.487Z"}],"before_hash":"2ced9c1f0a8f230b4775d0ac6e9470fee43238e74c1d463b3dc2547fdb0a7f7f","after_hash":"765ef7a9fdbdab4ecdae4687fa97fe8feb8cb627ba95e3ece7b2f3c9813967dc","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"92aa9306672ab9aa195da192fa0d6a5ced0cf38074b28233634848c440a54b66"} {"hash_algorithm":"sha256","ts":"2026-10-19T02:00:48.650Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/16","value":{"created_at":"2026-10-19T02:00:48.650Z","author":"codex-agent","text":"Merge inspection: main is 68e7b89, and only CHANGELOG.md conflicted. Main's changelog was selected before canonical regeneration. No tracker conflicts or clone-local merge receipts were produced, so reconciliation is unnecessary. Local strict health has no integrity or drift failures; it reports the existing host extension version skew (pinned CLI 2026.10.13 versus installed extension host 2026.10.18). Independent clone health will verify the committed tracker without that host state."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:00:48.650Z"}],"before_hash":"765ef7a9fdbdab4ecdae4687fa97fe8feb8cb627ba95e3ece7b2f3c9813967dc","after_hash":"af25f2c6eb3120041fe213fbd6523084b3c740717dc9a48f6d6e208189d9f150","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"0bd556a939b89469bc99f87a1db239616cbd8c4db326c6621e3a37e2839f87be"} +{"hash_algorithm":"sha256","ts":"2026-10-19T02:20:46.797Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/17","value":{"created_at":"2026-10-19T02:20:46.797Z","author":"codex-agent","text":"The orchestrator added review thread PRRT_kwDOTbN4pM6tw-Qw before any push. Merge c0aba84 is already complete and clean; do not repeat it. Stop the two unfinished pre-refactor release gates and preserve their logs as superseded runs, then scan each PR body once into a declaration-ID set, add a many-item/many-PR result regression, and run both full unchanged gates on the combined change. Existing attribution tests remain unchanged."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:20:46.797Z"}],"before_hash":"af25f2c6eb3120041fe213fbd6523084b3c740717dc9a48f6d6e208189d9f150","after_hash":"e9f6b5959ca45aa6793fae09a6bd23a737977cf1e5e5a371c2fa76d55c538764","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"4273692b64e702ee0f1c752d6b44966611ff10401b4f5ee4652cebc386269fd4"} +{"hash_algorithm":"sha256","ts":"2026-10-19T02:27:00.152Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/18","value":{"created_at":"2026-10-19T02:27:00.152Z","author":"codex-agent","text":"PRRT_kwDOTbN4pM6tw-Qw: replace per-item RegExp construction/body scans with one whole-line scan per PR and set membership per item. Keep the original six attribution tests byte-for-byte unchanged. Focused suite: 7/7, zero skipped/cancelled/failed. New real-SDK bulk case: 40 items, 12 PRs, 24 deliveries, 2 ambiguous claimant rows and 12 mention-only rows; all stored statuses remain open in plan mode. The same new result test passes 1/1 against only the pre-refactor runCloseout implementation in the scratch clone: equivalence is required here, so this optimization control intentionally passes before and after and uses no timing assertion. Earlier correctness function-only revert proofs remain recorded above. Lint and docstring checks pass. Superseded gate runs were stopped on the orchestrator's interruption and are not completed gate evidence."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:27:00.152Z"}],"before_hash":"e9f6b5959ca45aa6793fae09a6bd23a737977cf1e5e5a371c2fa76d55c538764","after_hash":"a6b6a18b2b1a6458a6ac9b3394868fe63263ac05a6b4e21a7d5356c8ce7d7363","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"daa375c66580da5f02c57bd2453d520ac2fb8e72e05b42c452fb0b504ae13afd"} +{"hash_algorithm":"sha256","ts":"2026-10-19T02:29:22.296Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/19","value":{"created_at":"2026-10-19T02:29:22.294Z","author":"codex-agent","text":"PM-linked verification completed: npx pm test pm-github-uqjf --run --progress passed both linked commands (attribution 7/7; compact same-repo dry-run control 1/1), zero timeouts/skips/failures. The project disables linked-result storage, so this comment is the durable receipt; no policy was changed. Canonical changelog regeneration and check pass after tracker writes. Commit the combined refactor and evidence before the two full release gates."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:29:22.296Z"}],"before_hash":"a6b6a18b2b1a6458a6ac9b3394868fe63263ac05a6b4e21a7d5356c8ce7d7363","after_hash":"68068e1acd8f012a0d137e726cd5552a98fba6dd98d2b53da23aad04cf06ec77","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8b73cc643205400eb7b385aa35be917606b7f24d697a1c29a9620b3d66b04cd7"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon index 269e3ba4..1af956b5 100644 --- a/.agents/pm/issues/pm-github-uqjf.toon +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -6,11 +6,11 @@ status: in_progress priority: 1 tags[1]: changelog-unreleased created_at: "2026-10-18T18:19:06.214Z" -updated_at: "2026-10-19T02:00:48.650Z" +updated_at: "2026-10-19T02:29:22.296Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent -comments[17]{created_at,author,text}: +comments[20]{created_at,author,text}: "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." @@ -28,6 +28,9 @@ comments[17]{created_at,author,text}: "2026-10-19T01:20:55.272Z",codex-agent,"Review round 1 full gates completed at code head c9a29d199ac9982e842e28cfdf6b75fb014572c3: npm run release:check and the complete bun run release:check rerun both exited 0. Each passed 899/899 tests, zero failures/skips/cancellations and exact 100/100/100/100 over 14 authored modules. Lines/statements 10472/10472; functions 274/274; branches npm 3187/3187 and Bun invocation 3185/3185. All stages passed with unchanged thresholds, source scope, dependency pins and helper/test budgets. The initial concurrent Bun attempt reproduced existing pm-github-sah1 /ready/ startup sensitivity and was stopped; the unchanged full rerun passed the 500 ms watchdog as well as all tests. The follow-up remains open. Focused attribution suite 6/6 and compact control 1/1 passed. Restoring only the old declaration regex makes the multiline regression fail; removing only claimant membership makes the two-claimants-plus-mention regression fail, both with ERR_ASSERTION. Same-line/tab declarations, real linked PR19 chronology and mention-only row fidelity remain intact. Completed receipts and initial-flake disclosure are in docs/explicit-closeout-attribution.md. Code-head CI: https://github.com/unbraind/pm-github/actions/runs/39174854340 . Receipt-head CI and independent fresh-clone strict health follow in the PR. No review-thread replies/resolutions or release actions occurred." "2026-10-19T02:00:19.487Z",codex-agent,"Merge origin/main after PR 149 into PR 150 using the configured PM merge drivers. The only merge conflict is CHANGELOG.md; retain main's version and regenerate after tracker writes. Check clone-local receipts before reconciliation, run both unchanged release gates, then verify health from an independent clone and wait for CI." "2026-10-19T02:00:48.650Z",codex-agent,"Merge inspection: main is 68e7b89, and only CHANGELOG.md conflicted. Main's changelog was selected before canonical regeneration. No tracker conflicts or clone-local merge receipts were produced, so reconciliation is unnecessary. Local strict health has no integrity or drift failures; it reports the existing host extension version skew (pinned CLI 2026.10.13 versus installed extension host 2026.10.18). Independent clone health will verify the committed tracker without that host state." + "2026-10-19T02:20:46.797Z",codex-agent,"The orchestrator added review thread PRRT_kwDOTbN4pM6tw-Qw before any push. Merge c0aba84 is already complete and clean; do not repeat it. Stop the two unfinished pre-refactor release gates and preserve their logs as superseded runs, then scan each PR body once into a declaration-ID set, add a many-item/many-PR result regression, and run both full unchanged gates on the combined change. Existing attribution tests remain unchanged." + "2026-10-19T02:27:00.152Z",codex-agent,"PRRT_kwDOTbN4pM6tw-Qw: replace per-item RegExp construction/body scans with one whole-line scan per PR and set membership per item. Keep the original six attribution tests byte-for-byte unchanged. Focused suite: 7/7, zero skipped/cancelled/failed. New real-SDK bulk case: 40 items, 12 PRs, 24 deliveries, 2 ambiguous claimant rows and 12 mention-only rows; all stored statuses remain open in plan mode. The same new result test passes 1/1 against only the pre-refactor runCloseout implementation in the scratch clone: equivalence is required here, so this optimization control intentionally passes before and after and uses no timing assertion. Earlier correctness function-only revert proofs remain recorded above. Lint and docstring checks pass. Superseded gate runs were stopped on the orchestrator's interruption and are not completed gate evidence." + "2026-10-19T02:29:22.294Z",codex-agent,"PM-linked verification completed: npx pm test pm-github-uqjf --run --progress passed both linked commands (attribution 7/7; compact same-repo dry-run control 1/1), zero timeouts/skips/failures. The project disables linked-result storage, so this comment is the durable receipt; no policy was changed. Canonical changelog regeneration and check pass after tracker writes. Commit the combined refactor and evidence before the two full release gates." files[7]{path,scope}: closeout.ts,project README.md,project diff --git a/closeout.ts b/closeout.ts index e15fd349..5da55320 100644 --- a/closeout.ts +++ b/closeout.ts @@ -402,12 +402,19 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti if (mergeEvidence(pr)) gate = await checkCloseoutHead(api, repo, pr, declared); checks.push(gate); const refs = extractCloseoutItems(pr.body ?? ""); + // Scan declarations once per PR, retaining case-insensitive IDs and optional punctuation. + const declarations = new Set(); + for (const match of (pr.body ?? "").matchAll(/^(?:[-*][ \t]+)?([a-z]+|pm-github:)[ \t]+(\S+)[ \t]*$/gim)) { + const keyword = match[1]!.toLowerCase(); + if (!CLOSING_KEYWORDS.has(keyword) && keyword !== "pm-github:") continue; + const id = match[2]!.toLowerCase(); + declarations.add(id); + if (/[.!]$/.test(id)) declarations.add(id.slice(0, -1)); + } for (const item of items.values()) { // Only canonical delivery metadata or a whole-line trailer/closing declaration is authoritative. const linked = [item.github_pr, item.pr_url, item.pull_request_url].includes(pr.html_url); - const id = item.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const declaration = new RegExp(`^(?:[-*][ \\t]+)?(?:${[...CLOSING_KEYWORDS].join("|")}|pm-github:)[ \\t]+${id}[.!]?[ \\t]*$`, "im").test(pr.body ?? ""); - if (linked || declaration) { + if (linked || declarations.has(item.id.toLowerCase())) { const ref = refs.find(ref => ref.item === item.id && ref.repo.toLowerCase() === repo.toLowerCase()); if (ref) ref.declared = true; else refs.push({ item: item.id, repo, declared: true }); diff --git a/docs/explicit-closeout-attribution.md b/docs/explicit-closeout-attribution.md index 021eda88..e5e5324c 100644 --- a/docs/explicit-closeout-attribution.md +++ b/docs/explicit-closeout-attribution.md @@ -20,3 +20,7 @@ Before review round 1, both npm run release:check and bun run release:check exit Review round 1 completed both npm run release:check and bun run release:check with exit 0 at code head c9a29d199ac9982e842e28cfdf6b75fb014572c3. Each passed 899/899 tests, zero failures/skips/cancellations and exact 100/100/100/100 over 14 authored modules. Both measured 10472/10472 lines and statements and 274/274 functions; branches were 3187/3187 (npm) and 3185/3185 (Bun invocation). All release-check stages passed with unchanged thresholds, source scope, helper/test budgets and dependency pins. The [review-round tracker receipt](../.agents/pm/history/pm-github-uqjf.jsonl) records completed results separately from earlier pending comments. [Code-head CI](https://github.com/unbraind/pm-github/actions/runs/39174854340) passed Node 22/26, each with 899 Node tests, 899 native Bun tests across 41 files and exact authored-source coverage, plus CodeQL and Semgrep. Receipt-head CI and fresh-clone health are recorded in the PR after that head is pushed. The first concurrent Bun attempt hit the existing 500 ms startup-watchdog probe tracked by [pm-github-sah1](../.agents/pm/issues/pm-github-sah1.toon): the child had not printed ready before its deadline. That already-failed attempt was stopped; cancellation diagnostics from stopping it are not additional regressions. The unchanged isolated watchdog suite passed 2/2. The complete Bun rerun, started after the other local gates finished, passed the original watchdog and all 899 tests. The startup-sensitive probe remains a separate open follow-up; this PR changes neither its helper nor its budgets. + +The follow-up merge incorporates main's PR 149 at 68e7b89 through merge commit c0aba84a9a57651a3e5cbeee0273916f8798153c. Only CHANGELOG.md conflicted: main's version was selected, then regenerated after tracker writes. PM files merged cleanly and no clone-local receipts were generated, so reconciliation was unnecessary. An independent fresh `--no-local` clone passed strict health with merge drivers required and tracker validation. + +The subsequent performance review removes per-item declaration regex construction and repeated body scans. Each PR body is scanned once into a case-insensitive declaration-ID set, preserving whole-line boundaries, spaces/tabs and optional punctuation. The six existing attribution tests remain byte-for-byte unchanged; the complete focused suite passes 7/7. Its new real-SDK case uses 40 items and 12 PRs and verifies 24 distinct delivery choices, two ambiguous claimants, twelve mention-only rows and unchanged item statuses. The same bulk-result case passes against the pre-refactor function, as expected for this behavior-preserving optimization; it makes no timing assertion. Earlier correctness regressions and their failing function-only revert proofs remain applicable. Interrupted release-gate attempts predating this refactor are superseded; completed combined-head gate receipts are recorded separately in the tracker and PR. diff --git a/test/closeout-attribution.test.ts b/test/closeout-attribution.test.ts index b9ac9049..ec0039f8 100644 --- a/test/closeout-attribution.test.ts +++ b/test/closeout-attribution.test.ts @@ -89,3 +89,44 @@ test("two explicit merged delivery claims remain ambiguous before any close", as assert.ok(report.plan.every(row => row.action === "skip" && row.code === "ambiguous_delivery")); assert.equal(item.status, "in_progress"); }); + +test("many items and PRs preserve declaration boundaries, casing, punctuation and ambiguity", async () => { + const root = mkdtempSync(join(tmpdir(), "closeout-many-declarations-")); + try { + const init = spawnSync(join(import.meta.dirname, "../node_modules/.bin/pm"), ["--pm-path", root, "init", "fixture"], { cwd: root, encoding: "utf8" }); + assert.equal(init.status, 0, init.stderr); + const sdk = { pmRoot: root, noExtensions: true }; + const ids: string[] = []; + for (let i = 0; i < 40; i++) ids.push((await create({ title: `Delivery candidate ${i}`, type: "Issue", author: "fixture" }, sdk)).item.id); + const verbs = ["close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved", "deliver", "delivers", "delivered"]; + const expected: string[] = []; + const bodies = verbs.map((verb, i) => { + const number = i + 1; + expected.push(`${number}:${ids[i * 2]}`, `${number}:${ids[i * 2 + 1]}`); + return [`- ${verb.toUpperCase()}\t${ids[i * 2]!.toUpperCase()}${i % 2 ? "." : "!"}\t`, + `pm-github:\t${ids[i * 2 + 1]}`, `Closes ${ids[i * 2]}`, + `Closes\n${ids[26]}`, `Reports ${ids[27]}`, `Closes ${ids[28]}!!`, + `See https://github.com/${repo}/blob/main/.agents/pm/issues/${ids[25]}.toon`, + ...(i < 2 ? [`Delivers ${ids[24]}`] : []), + ].join("\r\n"); + }); + const api: CloseoutApi = { base: "https://api.github.com", next: () => undefined, get: async endpoint => { + const n = Number(endpoint.split("/").at(-1)); + const value = endpoint.includes("/pulls/") ? { number: n, body: bodies[n - 1], merged: true, + merged_at: "2026-10-08T01:19:51Z", merge_commit_sha: "b".repeat(40), html_url: url(n), + head: { sha: head }, base: { ref: "main", repo: { full_name: repo } } } + : endpoint.includes("/protection/") ? { contexts: ["CI"], checks: [] } + : endpoint.includes("/check-runs") ? { total_count: 1, check_runs: [{ name: "CI", head_sha: head, status: "completed", conclusion: "success", app: { id: 7 } }] } : []; + return { body: JSON.stringify(value) }; + } }; + const report = await runCloseout(root, repo, { pr: verbs.map((_, i) => String(i + 1)), "dry-run": true }, api); + assert.equal(report.closed, 0); + assert.equal(report.failed, 0); + assert.equal(report.checks.length, 12); + assert.equal(report.plan.length, 38); + assert.deepEqual(report.plan.filter(row => row.action === "close").map(row => `${row.pr}:${row.item}`).sort(), expected.sort()); + assert.deepEqual(report.plan.filter(row => row.code === "ambiguous_delivery").map(row => [row.pr, row.item]), [[1, ids[24]], [2, ids[24]]]); + assert.deepEqual(report.plan.filter(row => row.code === "not_delivery").map(row => [row.pr, row.item]), verbs.map((_, i) => [i + 1, ids[25]])); + for (const id of ids) assert.equal((await get(id, {}, sdk)).item.status, "open"); + } finally { rmSync(root, { recursive: true, force: true }); } +}); From 23495cf5469f0d0b6f5795b147a852dde4ca41fb Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 19 Oct 2026 05:31:42 +0200 Subject: [PATCH 7/8] Preserve regex case equivalence in declaration lookup --- .agents/pm/history/pm-github-uqjf.jsonl | 4 ++++ .agents/pm/issues/pm-github-uqjf.toon | 8 ++++++-- closeout.ts | 16 ++++++++++++++-- docs/explicit-closeout-attribution.md | 4 ++-- test/closeout-attribution.test.ts | 4 +++- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl index e95dd472..bc53a729 100644 --- a/.agents/pm/history/pm-github-uqjf.jsonl +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -30,3 +30,7 @@ {"hash_algorithm":"sha256","ts":"2026-10-19T02:20:46.797Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/17","value":{"created_at":"2026-10-19T02:20:46.797Z","author":"codex-agent","text":"The orchestrator added review thread PRRT_kwDOTbN4pM6tw-Qw before any push. Merge c0aba84 is already complete and clean; do not repeat it. Stop the two unfinished pre-refactor release gates and preserve their logs as superseded runs, then scan each PR body once into a declaration-ID set, add a many-item/many-PR result regression, and run both full unchanged gates on the combined change. Existing attribution tests remain unchanged."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:20:46.797Z"}],"before_hash":"af25f2c6eb3120041fe213fbd6523084b3c740717dc9a48f6d6e208189d9f150","after_hash":"e9f6b5959ca45aa6793fae09a6bd23a737977cf1e5e5a371c2fa76d55c538764","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"4273692b64e702ee0f1c752d6b44966611ff10401b4f5ee4652cebc386269fd4"} {"hash_algorithm":"sha256","ts":"2026-10-19T02:27:00.152Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/18","value":{"created_at":"2026-10-19T02:27:00.152Z","author":"codex-agent","text":"PRRT_kwDOTbN4pM6tw-Qw: replace per-item RegExp construction/body scans with one whole-line scan per PR and set membership per item. Keep the original six attribution tests byte-for-byte unchanged. Focused suite: 7/7, zero skipped/cancelled/failed. New real-SDK bulk case: 40 items, 12 PRs, 24 deliveries, 2 ambiguous claimant rows and 12 mention-only rows; all stored statuses remain open in plan mode. The same new result test passes 1/1 against only the pre-refactor runCloseout implementation in the scratch clone: equivalence is required here, so this optimization control intentionally passes before and after and uses no timing assertion. Earlier correctness function-only revert proofs remain recorded above. Lint and docstring checks pass. Superseded gate runs were stopped on the orchestrator's interruption and are not completed gate evidence."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:27:00.152Z"}],"before_hash":"e9f6b5959ca45aa6793fae09a6bd23a737977cf1e5e5a371c2fa76d55c538764","after_hash":"a6b6a18b2b1a6458a6ac9b3394868fe63263ac05a6b4e21a7d5356c8ce7d7363","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"daa375c66580da5f02c57bd2453d520ac2fb8e72e05b42c452fb0b504ae13afd"} {"hash_algorithm":"sha256","ts":"2026-10-19T02:29:22.296Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/19","value":{"created_at":"2026-10-19T02:29:22.294Z","author":"codex-agent","text":"PM-linked verification completed: npx pm test pm-github-uqjf --run --progress passed both linked commands (attribution 7/7; compact same-repo dry-run control 1/1), zero timeouts/skips/failures. The project disables linked-result storage, so this comment is the durable receipt; no policy was changed. Canonical changelog regeneration and check pass after tracker writes. Commit the combined refactor and evidence before the two full release gates."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T02:29:22.296Z"}],"before_hash":"a6b6a18b2b1a6458a6ac9b3394868fe63263ac05a6b4e21a7d5356c8ce7d7363","after_hash":"68068e1acd8f012a0d137e726cd5552a98fba6dd98d2b53da23aad04cf06ec77","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8b73cc643205400eb7b385aa35be917606b7f24d697a1c29a9620b3d66b04cd7"} +{"hash_algorithm":"sha256","ts":"2026-10-19T03:03:25.411Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/20","value":{"created_at":"2026-10-19T03:03:25.411Z","author":"codex-agent","text":"Combined-head gate progress at code head ed63b0e: repeated orchestrator pauses stopped unfinished gate processes; those logs are superseded, not acceptance receipts. The continuing npm run has passed the original watchdog and all seven attribution cases. The concurrent Bun invocation hit existing pm-github-sah1 at bounded-cli.test.ts:20: /ready/ assertion received empty stdout before the unchanged 500 ms startup deadline. Stop that already-failed attempt; later cancellations are stop consequences. The unchanged isolated watchdog suite passes 2/2. A full Bun rerun is queued to start automatically only after npm completes with exit 0. No source, threshold, helper, budget or dependency-pin changes were made in response."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:03:25.411Z"}],"before_hash":"68068e1acd8f012a0d137e726cd5552a98fba6dd98d2b53da23aad04cf06ec77","after_hash":"c8be5221fdd3654ad25cf01d3e2c966de0c1ee87ff3c90c72ca97105e734b71c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"1adc46c8f8051dc1c4ad8323a7cc8d50f65b844ef3e914de5644bc6e6d547d1d"} +{"hash_algorithm":"sha256","ts":"2026-10-19T03:18:48.987Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/21","value":{"created_at":"2026-10-19T03:18:48.987Z","author":"codex-agent","text":"Completed npm run release:check at code head ed63b0ef0fd2ea505d036144f3667beb6ad46b37: exit 0, 910/910 tests, zero failures/cancellations/skips. Exact coverage over all 15 authored modules: 10506/10506 lines and statements, 275/275 functions, 3210/3210 branches; all four dimensions 100 percent with no source exemptions or threshold changes. Every remaining release-check stage passed. The full unchanged Bun rerun started automatically only after npm exited successfully."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:18:48.987Z"}],"before_hash":"c8be5221fdd3654ad25cf01d3e2c966de0c1ee87ff3c90c72ca97105e734b71c","after_hash":"0742fc135755574c84c716a7c915f84aee8a2784a0bd50485a97f660fea3fc8f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e63c09e5e1d33f338ea5720f28a6610d998883f00ae750f2ca4947f2deab50e9"} +{"hash_algorithm":"sha256","ts":"2026-10-19T03:30:14.703Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/22","value":{"created_at":"2026-10-19T03:30:14.703Z","author":"codex-agent","text":"Equivalence review found an edge case in the first Set implementation: JavaScript lowercasing maps the Unicode Kelvin sign to ASCII k, but the old non-Unicode /i regex rejects that lookalike. Correct the lookup key to preserve the original regex canonicalization, retaining per-PR scanning and Set membership. Extend the same 40-item/12-PR result test with a bulk-prefixed item's Kelvin-sign lookalike and non-ASCII expansion cases; the original six tests remain unchanged. The completed 910-test npm receipt belongs to ed63b0e only. Stop the unfinished Bun run as superseded by this correction; verify the old-function equivalence control and a key-function-only failing revert, then rerun both complete unchanged gates on the corrected code."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:30:14.703Z"}],"before_hash":"0742fc135755574c84c716a7c915f84aee8a2784a0bd50485a97f660fea3fc8f","after_hash":"b4893df3a6edd935c840cd367159400aeda3ee5ee9b2e1db579157a5e315f073","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"ef4ec4275ee115006add0b2a1cd117b01bbdacf78baaf64b79656ea00856431c"} +{"hash_algorithm":"sha256","ts":"2026-10-19T03:31:39.404Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/23","value":{"created_at":"2026-10-19T03:31:39.404Z","author":"codex-agent","text":"Strict case-equivalence proof completed: full attribution suite 7/7, zero failures/cancellations/skips; the six original tests remain byte-for-byte unchanged. Enhanced 40-item/12-PR bulk case passes 1/1 against the original runCloseout function. Reverting only declarationKey to ordinary lowercasing makes that same result test fail with ERR_ASSERTION (exit 1); restoring the key passes 1/1. This preserves the original non-Unicode /i behavior, including Kelvin-sign rejection, without per-item body scans or timing assertions. Lint passes; docstring gate documents all 171 declarations. Commit this correction and run fresh complete npm and Bun gates sequentially at unchanged thresholds."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:31:39.404Z"}],"before_hash":"b4893df3a6edd935c840cd367159400aeda3ee5ee9b2e1db579157a5e315f073","after_hash":"fd3e16198661ad43159140861d6c17eaf5b495b18cb8170249044a95c90047c2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"60703ddf4177a372c6a40888a6b483e35437cc03fab78dc07306831d5d4023fa"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon index 1af956b5..28d59351 100644 --- a/.agents/pm/issues/pm-github-uqjf.toon +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -6,11 +6,11 @@ status: in_progress priority: 1 tags[1]: changelog-unreleased created_at: "2026-10-18T18:19:06.214Z" -updated_at: "2026-10-19T02:29:22.296Z" +updated_at: "2026-10-19T03:31:39.404Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent -comments[20]{created_at,author,text}: +comments[24]{created_at,author,text}: "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." @@ -31,6 +31,10 @@ comments[20]{created_at,author,text}: "2026-10-19T02:20:46.797Z",codex-agent,"The orchestrator added review thread PRRT_kwDOTbN4pM6tw-Qw before any push. Merge c0aba84 is already complete and clean; do not repeat it. Stop the two unfinished pre-refactor release gates and preserve their logs as superseded runs, then scan each PR body once into a declaration-ID set, add a many-item/many-PR result regression, and run both full unchanged gates on the combined change. Existing attribution tests remain unchanged." "2026-10-19T02:27:00.152Z",codex-agent,"PRRT_kwDOTbN4pM6tw-Qw: replace per-item RegExp construction/body scans with one whole-line scan per PR and set membership per item. Keep the original six attribution tests byte-for-byte unchanged. Focused suite: 7/7, zero skipped/cancelled/failed. New real-SDK bulk case: 40 items, 12 PRs, 24 deliveries, 2 ambiguous claimant rows and 12 mention-only rows; all stored statuses remain open in plan mode. The same new result test passes 1/1 against only the pre-refactor runCloseout implementation in the scratch clone: equivalence is required here, so this optimization control intentionally passes before and after and uses no timing assertion. Earlier correctness function-only revert proofs remain recorded above. Lint and docstring checks pass. Superseded gate runs were stopped on the orchestrator's interruption and are not completed gate evidence." "2026-10-19T02:29:22.294Z",codex-agent,"PM-linked verification completed: npx pm test pm-github-uqjf --run --progress passed both linked commands (attribution 7/7; compact same-repo dry-run control 1/1), zero timeouts/skips/failures. The project disables linked-result storage, so this comment is the durable receipt; no policy was changed. Canonical changelog regeneration and check pass after tracker writes. Commit the combined refactor and evidence before the two full release gates." + "2026-10-19T03:03:25.411Z",codex-agent,"Combined-head gate progress at code head ed63b0e: repeated orchestrator pauses stopped unfinished gate processes; those logs are superseded, not acceptance receipts. The continuing npm run has passed the original watchdog and all seven attribution cases. The concurrent Bun invocation hit existing pm-github-sah1 at bounded-cli.test.ts:20: /ready/ assertion received empty stdout before the unchanged 500 ms startup deadline. Stop that already-failed attempt; later cancellations are stop consequences. The unchanged isolated watchdog suite passes 2/2. A full Bun rerun is queued to start automatically only after npm completes with exit 0. No source, threshold, helper, budget or dependency-pin changes were made in response." + "2026-10-19T03:18:48.987Z",codex-agent,"Completed npm run release:check at code head ed63b0ef0fd2ea505d036144f3667beb6ad46b37: exit 0, 910/910 tests, zero failures/cancellations/skips. Exact coverage over all 15 authored modules: 10506/10506 lines and statements, 275/275 functions, 3210/3210 branches; all four dimensions 100 percent with no source exemptions or threshold changes. Every remaining release-check stage passed. The full unchanged Bun rerun started automatically only after npm exited successfully." + "2026-10-19T03:30:14.703Z",codex-agent,"Equivalence review found an edge case in the first Set implementation: JavaScript lowercasing maps the Unicode Kelvin sign to ASCII k, but the old non-Unicode /i regex rejects that lookalike. Correct the lookup key to preserve the original regex canonicalization, retaining per-PR scanning and Set membership. Extend the same 40-item/12-PR result test with a bulk-prefixed item's Kelvin-sign lookalike and non-ASCII expansion cases; the original six tests remain unchanged. The completed 910-test npm receipt belongs to ed63b0e only. Stop the unfinished Bun run as superseded by this correction; verify the old-function equivalence control and a key-function-only failing revert, then rerun both complete unchanged gates on the corrected code." + "2026-10-19T03:31:39.404Z",codex-agent,"Strict case-equivalence proof completed: full attribution suite 7/7, zero failures/cancellations/skips; the six original tests remain byte-for-byte unchanged. Enhanced 40-item/12-PR bulk case passes 1/1 against the original runCloseout function. Reverting only declarationKey to ordinary lowercasing makes that same result test fail with ERR_ASSERTION (exit 1); restoring the key passes 1/1. This preserves the original non-Unicode /i behavior, including Kelvin-sign rejection, without per-item body scans or timing assertions. Lint passes; docstring gate documents all 171 declarations. Commit this correction and run fresh complete npm and Bun gates sequentially at unchanged thresholds." files[7]{path,scope}: closeout.ts,project README.md,project diff --git a/closeout.ts b/closeout.ts index 5da55320..61f9b1e5 100644 --- a/closeout.ts +++ b/closeout.ts @@ -260,6 +260,18 @@ const TERMINAL_STATUSES: readonly string[] = ["closed", "canceled"]; /** GitHub-style closing keywords that declare delivery of the link they directly precede. */ const CLOSING_KEYWORDS = new Set(["close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved", "deliver", "delivers", "delivered"]); +/** + * Preserve the original non-Unicode regex case rules for declaration lookup. + * @param id - Literal item ID or declaration token. + * @returns Its case-insensitive key without expanding characters or ASCII lookalikes. + */ +function declarationKey(id: string): string { + return id.replace(/[\s\S]/g, character => { + const upper = character.toUpperCase(); + return upper.length !== 1 || (character.charCodeAt(0) >= 128 && upper.charCodeAt(0) < 128) ? character : upper; + }); +} + /** * Whether a closing keyword directly precedes a link, given the line text before the link. * @@ -407,14 +419,14 @@ export async function runCloseout(pmRoot: string, repo: string | undefined, opti for (const match of (pr.body ?? "").matchAll(/^(?:[-*][ \t]+)?([a-z]+|pm-github:)[ \t]+(\S+)[ \t]*$/gim)) { const keyword = match[1]!.toLowerCase(); if (!CLOSING_KEYWORDS.has(keyword) && keyword !== "pm-github:") continue; - const id = match[2]!.toLowerCase(); + const id = declarationKey(match[2]!); declarations.add(id); if (/[.!]$/.test(id)) declarations.add(id.slice(0, -1)); } for (const item of items.values()) { // Only canonical delivery metadata or a whole-line trailer/closing declaration is authoritative. const linked = [item.github_pr, item.pr_url, item.pull_request_url].includes(pr.html_url); - if (linked || declarations.has(item.id.toLowerCase())) { + if (linked || declarations.has(declarationKey(item.id))) { const ref = refs.find(ref => ref.item === item.id && ref.repo.toLowerCase() === repo.toLowerCase()); if (ref) ref.declared = true; else refs.push({ item: item.id, repo, declared: true }); diff --git a/docs/explicit-closeout-attribution.md b/docs/explicit-closeout-attribution.md index e5e5324c..ae9f5adb 100644 --- a/docs/explicit-closeout-attribution.md +++ b/docs/explicit-closeout-attribution.md @@ -13,7 +13,7 @@ The entire selected PR set is planned before any SDK close. If multiple merged P The minimal fixtures retain the real titles, relevant body paragraphs and commit messages fetched with `gh pr view -R unbraind/pm-ado 16 --json title,body,commits` and the corresponding PR 19 command. Neither historical body explicitly declares delivery. Both are therefore refused until an explicit link or declaration is supplied; with the item's PR URL set to 19, closeout uses its October 8 merge chronology. Existing proposed pm-ado PR 32 tracker history is not rewritten here. -`node --test test/closeout-attribution.test.ts` covers six real-tracker cases, including multiline declarations and two claimants plus a mention-only PR. Restoring only the old in-progress bypass in runCloseout makes both original real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Review-round function-only proofs independently restore the old declaration regex or remove claimant membership while retaining the tests and other fix. Each corresponding regression fails with ERR_ASSERTION (exit 1). The restored attribution suite passes 6/6; the compact single-delivery control also passes. +`node --test test/closeout-attribution.test.ts` covers seven real-tracker cases, including multiline declarations, two claimants plus a mention-only PR, and a many-item/many-PR plan. Restoring only the old in-progress bypass in runCloseout makes both original real-shape provenance regressions fail: PR 16 becomes eligible again and the explicit PR 19 link cannot select the intended delivery. Review-round function-only proofs independently restore the old declaration regex or remove claimant membership while retaining the tests and other fix. Each corresponding regression fails with ERR_ASSERTION (exit 1). The six original cases and the compact single-delivery control remain covered unchanged. Before review round 1, both npm run release:check and bun run release:check exited 0 at code head b21ff5bdbdcfdab6c97b1204d73ff528b0f65745. Each ran 897/897 tests with zero failures, cancellations or skips. Both measured exact 100/100/100/100 over all 14 authored modules: 10471/10471 lines and statements, 274/274 functions and 3187/3187 branches. Dependency pins, source exemptions and thresholds are unchanged. The focused compact-output single-delivery case also passed. Final tracker/documentation receipts are regenerated and checked separately; final-head CI and fresh-clone strict health are reported in the PR. @@ -23,4 +23,4 @@ The first concurrent Bun attempt hit the existing 500 ms startup-watchdog probe The follow-up merge incorporates main's PR 149 at 68e7b89 through merge commit c0aba84a9a57651a3e5cbeee0273916f8798153c. Only CHANGELOG.md conflicted: main's version was selected, then regenerated after tracker writes. PM files merged cleanly and no clone-local receipts were generated, so reconciliation was unnecessary. An independent fresh `--no-local` clone passed strict health with merge drivers required and tracker validation. -The subsequent performance review removes per-item declaration regex construction and repeated body scans. Each PR body is scanned once into a case-insensitive declaration-ID set, preserving whole-line boundaries, spaces/tabs and optional punctuation. The six existing attribution tests remain byte-for-byte unchanged; the complete focused suite passes 7/7. Its new real-SDK case uses 40 items and 12 PRs and verifies 24 distinct delivery choices, two ambiguous claimants, twelve mention-only rows and unchanged item statuses. The same bulk-result case passes against the pre-refactor function, as expected for this behavior-preserving optimization; it makes no timing assertion. Earlier correctness regressions and their failing function-only revert proofs remain applicable. Interrupted release-gate attempts predating this refactor are superseded; completed combined-head gate receipts are recorded separately in the tracker and PR. +The subsequent performance review removes per-item declaration regex construction and repeated body scans. Each PR body is scanned once into a declaration-ID set, preserving whole-line boundaries, spaces/tabs and optional punctuation. Lookup keys retain the original non-Unicode regex case rules, including rejection of a Unicode Kelvin-sign lookalike for ASCII k. The six existing attribution tests remain byte-for-byte unchanged; the complete focused suite passes 7/7. Its new real-SDK case uses 40 items and 12 PRs and verifies 24 distinct delivery choices, two ambiguous claimants, twelve mention-only rows and unchanged item statuses, including an item whose ID is imitated by the Kelvin sign. The same bulk-result case passes against the pre-refactor function, as expected for this behavior-preserving optimization; it makes no timing assertion. Reverting only the key function to ordinary lowercasing makes the bulk result assertion fail with ERR_ASSERTION (exit 1); restoring the key passes. Earlier correctness regressions and their failing function-only revert proofs remain applicable. Completed combined-head gate receipts are recorded separately in the tracker and PR, scoped to their exact code heads. diff --git a/test/closeout-attribution.test.ts b/test/closeout-attribution.test.ts index ec0039f8..fd263e79 100644 --- a/test/closeout-attribution.test.ts +++ b/test/closeout-attribution.test.ts @@ -93,11 +93,12 @@ test("two explicit merged delivery claims remain ambiguous before any close", as test("many items and PRs preserve declaration boundaries, casing, punctuation and ambiguity", async () => { const root = mkdtempSync(join(tmpdir(), "closeout-many-declarations-")); try { - const init = spawnSync(join(import.meta.dirname, "../node_modules/.bin/pm"), ["--pm-path", root, "init", "fixture"], { cwd: root, encoding: "utf8" }); + const init = spawnSync(join(import.meta.dirname, "../node_modules/.bin/pm"), ["--pm-path", root, "init", "bulk"], { cwd: root, encoding: "utf8" }); assert.equal(init.status, 0, init.stderr); const sdk = { pmRoot: root, noExtensions: true }; const ids: string[] = []; for (let i = 0; i < 40; i++) ids.push((await create({ title: `Delivery candidate ${i}`, type: "Issue", author: "fixture" }, sdk)).item.id); + assert.ok(ids[29]!.includes("k")); const verbs = ["close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved", "deliver", "delivers", "delivered"]; const expected: string[] = []; const bodies = verbs.map((verb, i) => { @@ -106,6 +107,7 @@ test("many items and PRs preserve declaration boundaries, casing, punctuation an return [`- ${verb.toUpperCase()}\t${ids[i * 2]!.toUpperCase()}${i % 2 ? "." : "!"}\t`, `pm-github:\t${ids[i * 2 + 1]}`, `Closes ${ids[i * 2]}`, `Closes\n${ids[26]}`, `Reports ${ids[27]}`, `Closes ${ids[28]}!!`, + `Closes ${ids[29]!.replace("k", "K")}`, "Closes unmatched-ß", "Closes unmatched-ſ", "Closes unmatched-Σ", `See https://github.com/${repo}/blob/main/.agents/pm/issues/${ids[25]}.toon`, ...(i < 2 ? [`Delivers ${ids[24]}`] : []), ].join("\r\n"); From c2cd38e0708d7ef423efd24e74bf8355aa2d4fda Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 19 Oct 2026 06:26:29 +0200 Subject: [PATCH 8/8] Record merged closeout verification receipts --- .agents/pm/history/pm-github-uqjf.jsonl | 3 +++ .agents/pm/issues/pm-github-uqjf.toon | 8 ++++---- docs/explicit-closeout-attribution.md | 4 ++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.agents/pm/history/pm-github-uqjf.jsonl b/.agents/pm/history/pm-github-uqjf.jsonl index bc53a729..c28a2cf7 100644 --- a/.agents/pm/history/pm-github-uqjf.jsonl +++ b/.agents/pm/history/pm-github-uqjf.jsonl @@ -34,3 +34,6 @@ {"hash_algorithm":"sha256","ts":"2026-10-19T03:18:48.987Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/21","value":{"created_at":"2026-10-19T03:18:48.987Z","author":"codex-agent","text":"Completed npm run release:check at code head ed63b0ef0fd2ea505d036144f3667beb6ad46b37: exit 0, 910/910 tests, zero failures/cancellations/skips. Exact coverage over all 15 authored modules: 10506/10506 lines and statements, 275/275 functions, 3210/3210 branches; all four dimensions 100 percent with no source exemptions or threshold changes. Every remaining release-check stage passed. The full unchanged Bun rerun started automatically only after npm exited successfully."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:18:48.987Z"}],"before_hash":"c8be5221fdd3654ad25cf01d3e2c966de0c1ee87ff3c90c72ca97105e734b71c","after_hash":"0742fc135755574c84c716a7c915f84aee8a2784a0bd50485a97f660fea3fc8f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e63c09e5e1d33f338ea5720f28a6610d998883f00ae750f2ca4947f2deab50e9"} {"hash_algorithm":"sha256","ts":"2026-10-19T03:30:14.703Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/22","value":{"created_at":"2026-10-19T03:30:14.703Z","author":"codex-agent","text":"Equivalence review found an edge case in the first Set implementation: JavaScript lowercasing maps the Unicode Kelvin sign to ASCII k, but the old non-Unicode /i regex rejects that lookalike. Correct the lookup key to preserve the original regex canonicalization, retaining per-PR scanning and Set membership. Extend the same 40-item/12-PR result test with a bulk-prefixed item's Kelvin-sign lookalike and non-ASCII expansion cases; the original six tests remain unchanged. The completed 910-test npm receipt belongs to ed63b0e only. Stop the unfinished Bun run as superseded by this correction; verify the old-function equivalence control and a key-function-only failing revert, then rerun both complete unchanged gates on the corrected code."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:30:14.703Z"}],"before_hash":"0742fc135755574c84c716a7c915f84aee8a2784a0bd50485a97f660fea3fc8f","after_hash":"b4893df3a6edd935c840cd367159400aeda3ee5ee9b2e1db579157a5e315f073","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"ef4ec4275ee115006add0b2a1cd117b01bbdacf78baaf64b79656ea00856431c"} {"hash_algorithm":"sha256","ts":"2026-10-19T03:31:39.404Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/23","value":{"created_at":"2026-10-19T03:31:39.404Z","author":"codex-agent","text":"Strict case-equivalence proof completed: full attribution suite 7/7, zero failures/cancellations/skips; the six original tests remain byte-for-byte unchanged. Enhanced 40-item/12-PR bulk case passes 1/1 against the original runCloseout function. Reverting only declarationKey to ordinary lowercasing makes that same result test fail with ERR_ASSERTION (exit 1); restoring the key passes 1/1. This preserves the original non-Unicode /i behavior, including Kelvin-sign rejection, without per-item body scans or timing assertions. Lint passes; docstring gate documents all 171 declarations. Commit this correction and run fresh complete npm and Bun gates sequentially at unchanged thresholds."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:31:39.404Z"}],"before_hash":"b4893df3a6edd935c840cd367159400aeda3ee5ee9b2e1db579157a5e315f073","after_hash":"fd3e16198661ad43159140861d6c17eaf5b495b18cb8170249044a95c90047c2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"60703ddf4177a372c6a40888a6b483e35437cc03fab78dc07306831d5d4023fa"} +{"hash_algorithm":"sha256","ts":"2026-10-19T03:53:51.087Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/24","value":{"created_at":"2026-10-19T03:53:51.087Z","author":"codex-agent","text":"Completed corrected npm run release:check at 23495cf5469f0d0b6f5795b147a852dde4ca41fb: exit 0; 910/910 tests; zero failures, cancellations or skips. Exact 100/100/100/100 over all 15 authored modules: 10518/10518 lines and statements, 276/276 functions and 3216/3216 branches. All remaining release-check stages passed, including privacy, canonical changelog and attestation checks. Thresholds, source scope, helper/test budgets and pins are unchanged. The full corrected Bun gate has now started alone after npm's successful exit. This receipt supersedes the earlier code-head acceptance evidence for final review."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T03:53:51.087Z"}],"before_hash":"fd3e16198661ad43159140861d6c17eaf5b495b18cb8170249044a95c90047c2","after_hash":"46828b0ff035cbc05c9accc8474b5652bb1be9206f66c063785adb64717c5270","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"d712030a1f656df604b9774857e33dbdf83534203e0f4aea68eaf7c421f1d10d"} +{"hash_algorithm":"sha256","ts":"2026-10-19T04:26:24.846Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/25","value":{"created_at":"2026-10-19T04:26:24.846Z","author":"codex-agent","text":"Completed BOTH corrected release gates at code head 23495cf5469f0d0b6f5795b147a852dde4ca41fb: npm run release:check exit 0 and bun run release:check exit 0. Each passed 910/910 tests, zero failures/skips/cancellations, exact 100/100/100/100 over all 15 authored modules: 10518/10518 lines and statements, 276/276 functions, 3216/3216 branches. Every stage passed at unchanged thresholds, source scope, helper/test budgets and pins. Corrected PM-linked commands passed 2/2 (attribution 7/7, compact dry-run 1/1); result tracking remains disabled by existing policy, so these comments are the durable record. Original six tests are unchanged; old-function bulk control passes, key-function-only lowercase revert fails with ERR_ASSERTION, restored bulk test passes. Merge c0aba84 incorporated main 68e7b89 with only the changelog conflict resolved from main then regenerated; no clone-local receipts or pending reconciliation. The existing startup-watchdog follow-up remains open; the completed corrected Bun gate passed it unchanged. Prepare the final receipt, release the claim, and leave the item in_progress. Exact receipt-head fresh-clone health and CI results will be recorded in https://github.com/unbraind/pm-github/pull/150 after the single push."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T04:26:24.846Z"}],"before_hash":"46828b0ff035cbc05c9accc8474b5652bb1be9206f66c063785adb64717c5270","after_hash":"48cd594514cdd691b009882657f1dd9744d955d4f8c633e922c641e78d84ddd9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"33d485dc6b4becb7ea4f526566a3cda5a32f5c7d8321909bcdca8a75e0b317b4"} +{"hash_algorithm":"sha256","ts":"2026-10-19T04:26:26.316Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e4446d2a1278b42fdf0b9f7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-19T04:26:26.316Z"}],"before_hash":"48cd594514cdd691b009882657f1dd9744d955d4f8c633e922c641e78d84ddd9","after_hash":"aa37cc1ab1f9c10529f9a870662959fa94d726c2beadc1d0fb80b378989edc10","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"67b288fb29983c42dd65a8e680ebf03eea1a4a8b35d56613b59c925363ec19c1"} diff --git a/.agents/pm/issues/pm-github-uqjf.toon b/.agents/pm/issues/pm-github-uqjf.toon index 28d59351..8b329d07 100644 --- a/.agents/pm/issues/pm-github-uqjf.toon +++ b/.agents/pm/issues/pm-github-uqjf.toon @@ -6,11 +6,9 @@ status: in_progress priority: 1 tags[1]: changelog-unreleased created_at: "2026-10-18T18:19:06.214Z" -updated_at: "2026-10-19T03:31:39.404Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-10-19T04:26:26.316Z" author: codex-agent -comments[24]{created_at,author,text}: +comments[26]{created_at,author,text}: "2026-10-18T18:21:36.252Z",codex-agent,"Root cause confirmed: in_progress bypasses delivery declarations, and ascending PR selection chooses the earlier gap-reporting PR. https://github.com/unbraind/pm-ado/issues/33 compares https://github.com/unbraind/pm-ado/pull/16 with https://github.com/unbraind/pm-ado/pull/19. Neither body declares closure, so both must be reported without closing unless a canonical item-to-PR link supplies delivery evidence. A separate fix/closeout-explicit-delivery PR will retain all prior history." "2026-10-18T18:27:43.386Z",codex-agent,"Implemented explicit delivery declarations (closing canonical links, whole-line closing item ids or pm-github trailers, and exact PR URL metadata). The full selection is resolved before SDK writes; competing merged delivery claims receive ambiguous_delivery. Added minimal public title/body/commit fixtures from real pm-ado PRs 16 and 19. The real bodies contain only mentions; both are now refused until delivery is explicitly linked. Updated previous positive fixtures to declare delivery instead of relying on status." "2026-10-18T18:30:07.355Z",codex-agent,"Function-only behavioral revert: restored the old in_progress bypass in runCloseout while retaining both real-shape regression tests. Both tests fail: the earlier gap-reporting PR is accepted and the explicit PR 19 selection loses provenance. Restoring explicit attribution makes the linked four-case suite pass, including ambiguous-claim refusal and whole-line declarations. The package pins, source inventory and 100 percent thresholds remain unchanged." @@ -35,6 +33,8 @@ comments[24]{created_at,author,text}: "2026-10-19T03:18:48.987Z",codex-agent,"Completed npm run release:check at code head ed63b0ef0fd2ea505d036144f3667beb6ad46b37: exit 0, 910/910 tests, zero failures/cancellations/skips. Exact coverage over all 15 authored modules: 10506/10506 lines and statements, 275/275 functions, 3210/3210 branches; all four dimensions 100 percent with no source exemptions or threshold changes. Every remaining release-check stage passed. The full unchanged Bun rerun started automatically only after npm exited successfully." "2026-10-19T03:30:14.703Z",codex-agent,"Equivalence review found an edge case in the first Set implementation: JavaScript lowercasing maps the Unicode Kelvin sign to ASCII k, but the old non-Unicode /i regex rejects that lookalike. Correct the lookup key to preserve the original regex canonicalization, retaining per-PR scanning and Set membership. Extend the same 40-item/12-PR result test with a bulk-prefixed item's Kelvin-sign lookalike and non-ASCII expansion cases; the original six tests remain unchanged. The completed 910-test npm receipt belongs to ed63b0e only. Stop the unfinished Bun run as superseded by this correction; verify the old-function equivalence control and a key-function-only failing revert, then rerun both complete unchanged gates on the corrected code." "2026-10-19T03:31:39.404Z",codex-agent,"Strict case-equivalence proof completed: full attribution suite 7/7, zero failures/cancellations/skips; the six original tests remain byte-for-byte unchanged. Enhanced 40-item/12-PR bulk case passes 1/1 against the original runCloseout function. Reverting only declarationKey to ordinary lowercasing makes that same result test fail with ERR_ASSERTION (exit 1); restoring the key passes 1/1. This preserves the original non-Unicode /i behavior, including Kelvin-sign rejection, without per-item body scans or timing assertions. Lint passes; docstring gate documents all 171 declarations. Commit this correction and run fresh complete npm and Bun gates sequentially at unchanged thresholds." + "2026-10-19T03:53:51.087Z",codex-agent,"Completed corrected npm run release:check at 23495cf5469f0d0b6f5795b147a852dde4ca41fb: exit 0; 910/910 tests; zero failures, cancellations or skips. Exact 100/100/100/100 over all 15 authored modules: 10518/10518 lines and statements, 276/276 functions and 3216/3216 branches. All remaining release-check stages passed, including privacy, canonical changelog and attestation checks. Thresholds, source scope, helper/test budgets and pins are unchanged. The full corrected Bun gate has now started alone after npm's successful exit. This receipt supersedes the earlier code-head acceptance evidence for final review." + "2026-10-19T04:26:24.846Z",codex-agent,"Completed BOTH corrected release gates at code head 23495cf5469f0d0b6f5795b147a852dde4ca41fb: npm run release:check exit 0 and bun run release:check exit 0. Each passed 910/910 tests, zero failures/skips/cancellations, exact 100/100/100/100 over all 15 authored modules: 10518/10518 lines and statements, 276/276 functions, 3216/3216 branches. Every stage passed at unchanged thresholds, source scope, helper/test budgets and pins. Corrected PM-linked commands passed 2/2 (attribution 7/7, compact dry-run 1/1); result tracking remains disabled by existing policy, so these comments are the durable record. Original six tests are unchanged; old-function bulk control passes, key-function-only lowercase revert fails with ERR_ASSERTION, restored bulk test passes. Merge c0aba84 incorporated main 68e7b89 with only the changelog conflict resolved from main then regenerated; no clone-local receipts or pending reconciliation. The existing startup-watchdog follow-up remains open; the completed corrected Bun gate passed it unchanged. Prepare the final receipt, release the claim, and leave the item in_progress. Exact receipt-head fresh-clone health and CI results will be recorded in https://github.com/unbraind/pm-github/pull/150 after the single push." files[7]{path,scope}: closeout.ts,project README.md,project diff --git a/docs/explicit-closeout-attribution.md b/docs/explicit-closeout-attribution.md index ae9f5adb..0ab652d2 100644 --- a/docs/explicit-closeout-attribution.md +++ b/docs/explicit-closeout-attribution.md @@ -24,3 +24,7 @@ The first concurrent Bun attempt hit the existing 500 ms startup-watchdog probe The follow-up merge incorporates main's PR 149 at 68e7b89 through merge commit c0aba84a9a57651a3e5cbeee0273916f8798153c. Only CHANGELOG.md conflicted: main's version was selected, then regenerated after tracker writes. PM files merged cleanly and no clone-local receipts were generated, so reconciliation was unnecessary. An independent fresh `--no-local` clone passed strict health with merge drivers required and tracker validation. The subsequent performance review removes per-item declaration regex construction and repeated body scans. Each PR body is scanned once into a declaration-ID set, preserving whole-line boundaries, spaces/tabs and optional punctuation. Lookup keys retain the original non-Unicode regex case rules, including rejection of a Unicode Kelvin-sign lookalike for ASCII k. The six existing attribution tests remain byte-for-byte unchanged; the complete focused suite passes 7/7. Its new real-SDK case uses 40 items and 12 PRs and verifies 24 distinct delivery choices, two ambiguous claimants, twelve mention-only rows and unchanged item statuses, including an item whose ID is imitated by the Kelvin sign. The same bulk-result case passes against the pre-refactor function, as expected for this behavior-preserving optimization; it makes no timing assertion. Reverting only the key function to ordinary lowercasing makes the bulk result assertion fail with ERR_ASSERTION (exit 1); restoring the key passes. Earlier correctness regressions and their failing function-only revert proofs remain applicable. Completed combined-head gate receipts are recorded separately in the tracker and PR, scoped to their exact code heads. + +The completed combined follow-up passes both `npm run release:check` and `bun run release:check` at code head 23495cf5469f0d0b6f5795b147a852dde4ca41fb. Each exits 0 with 910/910 tests and zero failures, skips or cancellations. Both measure exact 100/100/100/100 over all 15 authored modules: 10518/10518 lines and statements, 276/276 functions and 3216/3216 branches. Every release-check stage passes with unchanged thresholds, source scope, helper/test budgets and dependency pins. The corrected PM-linked run also passes both commands: attribution 7/7 and compact dry-run 1/1. Its project policy disables result storage, so the completed [tracker comments](../.agents/pm/history/pm-github-uqjf.jsonl) provide the durable receipt. + +A separate concurrent attempt at the earlier ed63b0e head reproduced the same startup-watchdog flake: empty output before the unchanged 500 ms deadline. That already-failed run was stopped; the unchanged isolated watchdog passed 2/2. Interrupted and superseded runs are not acceptance receipts. The complete corrected Bun gate ran after npm finished and passed the original watchdog plus all 910 tests. Final receipt-head CI and independent fresh-clone health are recorded in the PR after that head is prepared.