From bdc647a7909a38ce43c76ce50e7960fc6de4581d Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:37:38 +0200 Subject: [PATCH 1/2] Confirm a release the GitHub list has not caught up with before calling it missing The dispatched pm-ops release of 2026-09-28 (run 36380519990) published 2026.9.28 to npm and created GitHub Release v2026.09.28 at 05:15:32Z, then failed its own completeness audit one second later: the paginated repos//releases list did not show the release yet. The release was complete; the audit read an eventually consistent list. verify now reads each release tag the list lacks by its tag (repos//releases/tags/) before counting it missing. Only an HTTP 404 means absent; any other failure of that read reports GitHub Releases as an unreadable source, as the other reads already do. Tags the list shows are never re-read, so a healthy repository makes no extra requests. Tracker: ops-eys5 (closed with evidence). ops-jzp5, fixed by #124, is attributed to the 2026.9.28 release it shipped in, and CHANGELOG.md is regenerated with changelog:full. release:check: 446 tests, 100/100/100/100 coverage; changelog:check clean. --- .agents/pm/history/_workspace.jsonl | 1 + .agents/pm/history/ops-eys5.jsonl | 8 +++++ .agents/pm/history/ops-jzp5.jsonl | 4 +++ .agents/pm/issues/ops-eys5.toon | 25 +++++++++++++++ .agents/pm/issues/ops-jzp5.toon | 13 ++++++-- CHANGELOG.md | 12 +++++++ scripts/verify-release-completeness.ts | 26 +++++++++++++-- test/verify-release-completeness.test.ts | 41 ++++++++++++++++++++++++ 8 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 .agents/pm/history/ops-eys5.jsonl create mode 100644 .agents/pm/issues/ops-eys5.toon diff --git a/.agents/pm/history/_workspace.jsonl b/.agents/pm/history/_workspace.jsonl index 8301753..0ef9495 100644 --- a/.agents/pm/history/_workspace.jsonl +++ b/.agents/pm/history/_workspace.jsonl @@ -36,3 +36,4 @@ {"hash_algorithm":"sha256","ts":"2026-09-25T08:32:12.715Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"policy_refused","patch":[],"before_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","after_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","item_hash_version":3,"message":"Workflow policy refused a proposed item mutation.","context":{"item_id":"ops-me3v","operation":"close","workflow_policies":[{"policy_id":"completeness-chore","policy_fingerprint":"084270190bf6e5c3ed6b8610fdcd040f8aea28b85461a84ca1b0faa2d175a344","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":false,"missing_fields":["resolution"],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"5720a2882cad962e29e403f87abfd56cd5ad90b7671e643b732f36aece8e1bae"} {"hash_algorithm":"sha256","ts":"2026-09-25T08:32:14.966Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"policy_refused","patch":[],"before_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","after_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","item_hash_version":3,"message":"Workflow policy refused a proposed item mutation.","context":{"item_id":"ops-ssnr","operation":"close","workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":false,"missing_fields":["repro_steps","expected_result"],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":false,"missing_fields":["resolution"],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"1ab8885d5d257ae2507ba65a69fc1ef27bef60512535254e282a74ea5618e056"} {"hash_algorithm":"sha256","ts":"2026-09-25T09:08:31.010Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"policy_refused","patch":[],"before_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","after_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","item_hash_version":3,"message":"Workflow policy refused a proposed item mutation.","context":{"item_id":"ops-eyi8","operation":"close","workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":false,"missing_fields":["repro_steps"],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"0b127f6a676561ef8a7811d6f4a48172b41e45e5786d7ade2ec1dfaf836b0817"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:27:33.014Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"policy_refused","patch":[],"before_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","after_hash":"316b3153ae19030ba63b9440fcf07d26200698b54ef8b936da54442254e86ec8","item_hash_version":3,"message":"Workflow policy refused a proposed item mutation.","context":{"item_id":"ops-jzp5","operation":"close","workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":false,"missing_fields":["repro_steps","expected_result"],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":false,"missing_fields":["resolution"],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a5c6652d313d2ed3fb3a672bc16456d5f7851c457114591ce6b136d81d556284"} diff --git a/.agents/pm/history/ops-eys5.jsonl b/.agents/pm/history/ops-eys5.jsonl new file mode 100644 index 0000000..80633a9 --- /dev/null +++ b/.agents/pm/history/ops-eys5.jsonl @@ -0,0 +1,8 @@ +{"hash_algorithm":"sha256","ts":"2026-09-28T05:27:25.547Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"ops-eys5"},{"op":"add","path":"/metadata/title","value":"Release completeness audit fails a release it just created because the GitHub Release list lags creation"},{"op":"add","path":"/metadata/description","value":"Run 36380519990 (2026-09-28, workflow_dispatch) published pm-ops 2026.9.28 to npm and created GitHub Release v2026.09.28 at 05:15:32Z. The audit started in the same second, read the paginated release list, did not see the new release, and failed the run with 'release tag v2026.09.28 has no corresponding GitHub Release'. The release was complete; the audit read an eventually consistent list. Fix: a release tag missing from the list is read by its tag (repos//releases/tags/) before it counts as missing; only a 404 means absent, any other failure is an unreadable source."},{"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":["area:release","quality-gates","release"]},{"op":"add","path":"/metadata/created_at","value":"2026-09-28T05:27:25.547Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-09-28T05:27:25.547Z"},{"op":"add","path":"/metadata/author","value":"claude-hub"},{"op":"add","path":"/metadata/acceptance_criteria","value":"verify passes when the list lacks a release that the by-tag read finds; a 404 by-tag read still fails the audit naming the tag; any other by-tag failure is reported as an unreadable source; only tags the list lacks are read by tag"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"a978621587aad6de10b628e7b9bb74937efaa67e60a6a52541770f1ed2861163","item_hash_version":3,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a25a767fdaeaf87c5211657057a53775089b2eefce10c0c317f568dfff2fdc4b"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:27:58.525Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:27:58.525Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"},{"op":"add","path":"/metadata/repro_steps","value":"Create a GitHub Release, then immediately list repos//releases (as the release workflow's audit step does right after the release step)."},{"op":"add","path":"/metadata/expected_result","value":"The audit sees the release it just created and passes."}],"before_hash":"a978621587aad6de10b628e7b9bb74937efaa67e60a6a52541770f1ed2861163","after_hash":"64780053c4374cdcbcf5f43820f07ef03202e0a0e8858c01db64be0c36c2a7fc","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"6197dce265b8247b6f06949ff582b6c7194228360a789c23bddedc6262f811f0"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:28:00.935Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:28:00.935Z"},{"op":"add","path":"/metadata/assignee","value":"claude-hub"},{"op":"add","path":"/metadata/claim_principal","value":"claude-hub"}],"before_hash":"64780053c4374cdcbcf5f43820f07ef03202e0a0e8858c01db64be0c36c2a7fc","after_hash":"e8f75cfc0db63ff0eb24826f98a8ab040c83e083b5a19213ac4908ebe33eca52","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"632740a230babe2372032fc29c97764017529c0e25f49099bb602f364d034f3e"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:28:11.322Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:28:11.322Z"},{"op":"add","path":"/metadata/actual_result","value":"The list lacked v2026.09.28 (created at 05:15:32Z, audit read at 05:15:33Z) and the audit failed run 36380519990 although the release existed."}],"before_hash":"e8f75cfc0db63ff0eb24826f98a8ab040c83e083b5a19213ac4908ebe33eca52","after_hash":"2ff897e637dad479b790048b1b6c4d367a6cfd163d62c5281535393f396eb372","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"366396c7c0e7fd534b851cf75d698125658a6a85e6602da2e83dac2f1e66cf39"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:28:14.403Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:28:14.403Z"},{"op":"add","path":"/metadata/files","value":[{"path":"scripts/verify-release-completeness.ts","scope":"project"},{"path":"test/verify-release-completeness.test.ts","scope":"project"}]}],"before_hash":"2ff897e637dad479b790048b1b6c4d367a6cfd163d62c5281535393f396eb372","after_hash":"0442a0b5ccaf5fda3b71f28fd4803d95ea2d9b5111d065cce58c01be7766790d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"e821ade9d96da6b4f4967c9f0db2ec5d73d5d18c1d18e8a7311b8b1acf2e1e91"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:28:16.945Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:28:16.945Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/verify-release-completeness.test.ts","scope":"project","provenance":{"author":"claude-hub","created_at":"2026-09-28T05:28:16.795Z","source_kind":"local_mutation","source_ref":"fix/release-audit-confirms-new-releases-by-tag"}}]}],"before_hash":"0442a0b5ccaf5fda3b71f28fd4803d95ea2d9b5111d065cce58c01be7766790d","after_hash":"63551f7ea1a5e7da7441460af7de107a2a8750dfc33caa422e7fa9332745d6e1","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"988548c9916590a93e88abf6a58388ff4d796e60571593ff293abf4c0c6f9cdd"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:37:14.262Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:37:14.262Z"},{"op":"add","path":"/metadata/resolution","value":"verify reads each release tag the paginated list lacks through repos//releases/tags/ before calling it missing; only an HTTP 404 means absent, any other failure reports GitHub Releases as unreadable. Covered by the githubReleaseForTag and 'verify confirms a release the list lags behind' tests."}],"before_hash":"63551f7ea1a5e7da7441460af7de107a2a8750dfc33caa422e7fa9332745d6e1","after_hash":"66c15722a1219e1d31e127dc95462871d95072562af94b994fdc0baf0bc70823","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"627b67e71f617af71c3f34f7f9246680c8587be78ff206beb2b55a7201a263fe"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:37:15.545Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:37:15.545Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-09-28T05:37:15.515Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-09-28T05:37:15.515Z"},{"op":"add","path":"/metadata/close_reason","value":"Fixed on branch fix/release-audit-confirms-new-releases-by-tag: by-tag confirmation for listed-missing release tags. release:check passed its test stage (446 tests, 100/100/100/100); regenerated changelog committed with the fix."}],"before_hash":"66c15722a1219e1d31e127dc95462871d95072562af94b994fdc0baf0bc70823","after_hash":"0050a31bd0996c3ec8e27ba5f04425cb040be4106c3cfc58ae8ab8d7d5be4ab5","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"dc236c541d0fdf63de6ddf234c084af3b485540fa0499914b01f112937457a7c"} diff --git a/.agents/pm/history/ops-jzp5.jsonl b/.agents/pm/history/ops-jzp5.jsonl index 265624e..6a6f2f6 100644 --- a/.agents/pm/history/ops-jzp5.jsonl +++ b/.agents/pm/history/ops-jzp5.jsonl @@ -6,3 +6,7 @@ {"hash_algorithm":"sha256","ts":"2026-09-26T16:57:51.563Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"d1738b58d58bd67fa4411935","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-09-26T16:57:51.543Z","author":"harness:codex","text":"Final gate with exact @unbrained/pm-cli 2026.9.26 and pm-changelog 2026.9.25: npm run release:check exit 0, 446 tests (444 passed, 2 pre-existing skips), 100/100/100/100 measured coverage, 0 production vulnerabilities, 0 clone pairs, 221/221 declarations documented, changelog/attestation/lifecycle gates pass. pm health --strict-exit --json ok=true with stale_in_progress_items:3 advisory. Canonical release and consumer publication remain pending PR review."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-26T16:57:51.563Z"}],"before_hash":"2eaae6f00132348b8c196af846ccd8aab899deb81a8badb60d5118dec6507779","after_hash":"ffd36b004c3652b1feab33928ff7675dc5cbae8733feea28a1a2573a69408c08","item_hash_version":3,"message":"Record final exact-version release and PM health gates","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":"726dce816f1c988bb00025ea52c70706d1296ca0a8850c105fe93a484038c64a"} {"hash_algorithm":"sha256","ts":"2026-09-27T07:48:32.530Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"fb64a4867f43e1382591a8cb","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-09-27T07:48:32.530Z","author":"harness:codex","text":"2026-09-27: Published @unbrained/pm-cli 2026.9.27 superseded the candidate development pin. Updated package.json and lockfile to exact 2026.9.27 and the compatibility-floor assertion to match. npm ls resolves 2026.9.27, local CLI reports 2026.9.27, and host strict health passes with the existing stale_in_progress_items:3 advisory. The full release gate and exact-head review must be rerun after this commit."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-27T07:48:32.530Z"}],"before_hash":"ffd36b004c3652b1feab33928ff7675dc5cbae8733feea28a1a2573a69408c08","after_hash":"930af1a57b3894241bf572c9fc0b94d8e7df41ccc70ac416ec934035d6618bc1","item_hash_version":3,"message":"Refresh canonical pm-ops candidate to CLI 2026.9.27","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":"ba28b95d8abc21f4beb9d17fd77bf2e2a61aa10ca9357995dd17506c28e7e3d2"} {"hash_algorithm":"sha256","ts":"2026-09-27T07:50:37.366Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"fb64a4867f43e1382591a8cb","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-09-27T07:50:37.366Z","author":"harness:codex","text":"2026-09-27 07:51 UTC: Final local gate on exact @unbrained/pm-cli 2026.9.27 passes npm run release:check exit 0. Coverage gate enforces 100/100/100/100; 444 tests pass and 2 existing real-data tests skip, so zero-skip acceptance is not claimed. Duplication is 0/21831 lines across 43 tracked sources, docstrings 221/221, production audit 0 findings, changelog, attestation, lifecycle, and pack gates pass. Strict PM health passes with stale_in_progress_items:3 advisory. GitHub CI and new-head reviews are required after push."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-27T07:50:37.366Z"}],"before_hash":"930af1a57b3894241bf572c9fc0b94d8e7df41ccc70ac416ec934035d6618bc1","after_hash":"63419a90ad465feedbee0b4e91a44e25339c620c163449f42515553dbfb42d6a","item_hash_version":3,"message":"Record CLI 2026.9.27 full release gate","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":"687ddb4e765cdf6df5986e1f114b76be40c86afa5d50d31428ce26f239de2c05"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:27:48.790Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:27:48.790Z"},{"op":"add","path":"/metadata/repro_steps","value":"Install a consumer package beneath an ancestor node_modules/pm-ops that lacks package.json, then run the package's prepare script (scripts/prepare-merge-driver.ts)."},{"op":"add","path":"/metadata/resolution","value":"pm-ops#124 checks every Node resolution path (require.resolve.paths) for an incomplete pm-ops before allowing the omit-dev skip; test/prepare-merge-driver.test.ts runs a real child checkout beneath a broken ancestor install. Shipped in pm-ops 2026.9.28."},{"op":"add","path":"/metadata/expected_result","value":"The launcher detects the broken hoisted pm-ops, exits non-zero, and prints no omit-dev skip notice."}],"before_hash":"63419a90ad465feedbee0b4e91a44e25339c620c163449f42515553dbfb42d6a","after_hash":"963c939574eb30e6f35aed457b151018eb76d704c4d696f14074028f77c0c47a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"5c0b3c2e4e348ae6c180ad4768bdd33c39b3f5ee0bccb74ead2671fcb53ff427"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:27:53.285Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:27:53.285Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-09-28T05:27:53.214Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-09-28T05:27:53.214Z"},{"op":"add","path":"/metadata/close_reason","value":"Fixed by pm-ops#124 (merged 2026-09-28, 450e2be); released in pm-ops 2026.9.28 (npm + GitHub Release v2026.09.28)."}],"before_hash":"963c939574eb30e6f35aed457b151018eb76d704c4d696f14074028f77c0c47a","after_hash":"447567930c97d96a0d6e11da53d9dff126fa64c83da2490c6d79c30c8305cae0","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"3dbddcf4aa79ed4596530a0adc8faee945a6d0e6e0d9346057a623c4988e7037"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:28:08.885Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:28:08.885Z"},{"op":"add","path":"/metadata/actual_result","value":"The launcher probed only the checkout's own node_modules, classified the broken hoisted pm-ops as an omit-dev install, and exited 0 without registering the merge drivers."}],"before_hash":"447567930c97d96a0d6e11da53d9dff126fa64c83da2490c6d79c30c8305cae0","after_hash":"272d0e2f9526ad759d48bc5f6168c2f514d4a34e34ab46fb592cb378b589cf6d","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"6796285465396821b05083accd1ef4507620485947409bca40b47eb3a945e95a"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:37:13.207Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:37:13.207Z"},{"op":"add","path":"/metadata/release","value":"2026.9.28"}],"before_hash":"272d0e2f9526ad759d48bc5f6168c2f514d4a34e34ab46fb592cb378b589cf6d","after_hash":"2a3338976220d7f0d6b575ac2130217fe0bd79a8a0efd6652bb9b62014f9ca68","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"c5be3c6cedec1390e0b81cfff9ecb67afdf5cb78241f4151fc29b14127541e07"} diff --git a/.agents/pm/issues/ops-eys5.toon b/.agents/pm/issues/ops-eys5.toon new file mode 100644 index 0000000..2e736d3 --- /dev/null +++ b/.agents/pm/issues/ops-eys5.toon @@ -0,0 +1,25 @@ +id: ops-eys5 +title: Release completeness audit fails a release it just created because the GitHub Release list lags creation +description: "Run 36380519990 (2026-09-28, workflow_dispatch) published pm-ops 2026.9.28 to npm and created GitHub Release v2026.09.28 at 05:15:32Z. The audit started in the same second, read the paginated release list, did not see the new release, and failed the run with 'release tag v2026.09.28 has no corresponding GitHub Release'. The release was complete; the audit read an eventually consistent list. Fix: a release tag missing from the list is read by its tag (repos//releases/tags/) before it counts as missing; only a 404 means absent, any other failure is an unreadable source." +type: Issue +status: closed +priority: 1 +tags[3]: "area:release",quality-gates,release +created_at: "2026-09-28T05:27:25.547Z" +updated_at: "2026-09-28T05:37:15.545Z" +closed_at: "2026-09-28T05:37:15.515Z" +completed_at: "2026-09-28T05:37:15.515Z" +claim_principal: claude-hub +author: claude-hub +acceptance_criteria: verify passes when the list lacks a release that the by-tag read finds; a 404 by-tag read still fails the audit naming the tag; any other by-tag failure is reported as an unreadable source; only tags the list lacks are read by tag +repro_steps: "Create a GitHub Release, then immediately list repos//releases (as the release workflow's audit step does right after the release step)." +resolution: "verify reads each release tag the paginated list lacks through repos//releases/tags/ before calling it missing; only an HTTP 404 means absent, any other failure reports GitHub Releases as unreadable. Covered by the githubReleaseForTag and 'verify confirms a release the list lags behind' tests." +expected_result: The audit sees the release it just created and passes. +actual_result: "The list lacked v2026.09.28 (created at 05:15:32Z, audit read at 05:15:33Z) and the audit failed run 36380519990 although the release existed." +files[2]{path,scope}: + scripts/verify-release-completeness.ts,project + test/verify-release-completeness.test.ts,project +tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}: + node --test test/verify-release-completeness.test.ts,project,claude-hub,"2026-09-28T05:28:16.795Z",local_mutation,fix/release-audit-confirms-new-releases-by-tag +close_reason: "Fixed on branch fix/release-audit-confirms-new-releases-by-tag: by-tag confirmation for listed-missing release tags. release:check passed its test stage (446 tests, 100/100/100/100); regenerated changelog committed with the fix." +body: "" diff --git a/.agents/pm/issues/ops-jzp5.toon b/.agents/pm/issues/ops-jzp5.toon index 8a39d53..64e28fc 100644 --- a/.agents/pm/issues/ops-jzp5.toon +++ b/.agents/pm/issues/ops-jzp5.toon @@ -2,15 +2,21 @@ id: ops-jzp5 title: Hoisted broken pm-ops can bypass merge-driver install guard description: "Greptile found that the canonical prepare launcher searches only the consumer checkout node_modules/pm-ops after a MODULE_NOT_FOUND resolution. Node also resolves packages from ancestor node_modules. If a hoisted pm-ops directory exists but lacks package.json, the launcher treats it as an omit-dev install and silently skips field-aware merge-driver registration. This extends the local broken-install guard in ops-eyi8 to the full Node lookup path." type: Issue -status: in_progress +status: closed priority: 0 tags: [] created_at: "2026-09-26T16:43:46.361Z" -updated_at: "2026-09-27T07:50:37.366Z" -assignee: codex +updated_at: "2026-09-28T05:37:13.207Z" +closed_at: "2026-09-28T05:27:53.214Z" +completed_at: "2026-09-28T05:27:53.214Z" claim_principal: codex author: codex acceptance_criteria: "A real child checkout with an empty ancestor node_modules/pm-ops fails the prepare hook without the omit-dev skip notice; a truly absent package still skips once; an intact hoisted pm-ops runs the installer; the regression fails on the old template, full release:check and strict PM health pass; consumer copies update only after a canonical pm-ops release." +release: 2026.9.28 +repro_steps: "Install a consumer package beneath an ancestor node_modules/pm-ops that lacks package.json, then run the package's prepare script (scripts/prepare-merge-driver.ts)." +resolution: pm-ops#124 checks every Node resolution path (require.resolve.paths) for an incomplete pm-ops before allowing the omit-dev skip; test/prepare-merge-driver.test.ts runs a real child checkout beneath a broken ancestor install. Shipped in pm-ops 2026.9.28. +expected_result: "The launcher detects the broken hoisted pm-ops, exits non-zero, and prints no omit-dev skip notice." +actual_result: "The launcher probed only the checkout's own node_modules, classified the broken hoisted pm-ops as an omit-dev install, and exited 0 without registering the merge drivers." dependencies[1]{id,kind,created_at,author,source_kind,author_source}: ops-eyi8,related,"2026-09-26T16:43:51.944Z",codex,"cli:update:dep",detected notes[4]{created_at,author,text}: @@ -25,4 +31,5 @@ files[4]{path,scope,note}: test/compatibility-floor.test.ts,project,Current host version contract tests[1]{command,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: node --test test/merge-driver-launcher.test.ts,project,120,"harness:codex","2026-09-26T16:54:49.210Z",local_mutation,fix/pm-ops-hoisted-merge-driver-guard-2026-09-26 +close_reason: "Fixed by pm-ops#124 (merged 2026-09-28, 450e2be); released in pm-ops 2026.9.28 (npm + GitHub Release v2026.09.28)." body: "" diff --git a/CHANGELOG.md b/CHANGELOG.md index 56895b0..88202e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## Unreleased + +### Fixed + +- Release completeness audit fails a release it just created because the GitHub Release list lags creation ([ops-eys5](https://github.com/unbraind/pm-ops/blob/main/.agents/pm/issues/ops-eys5.toon)) + +## 2026.9.28 - 2026-09-28 + +### Fixed + +- Hoisted broken pm-ops can bypass merge-driver install guard ([ops-jzp5](https://github.com/unbraind/pm-ops/blob/main/.agents/pm/issues/ops-jzp5.toon)) + ## 2026.9.26 - 2026-09-26 ### Fixed diff --git a/scripts/verify-release-completeness.ts b/scripts/verify-release-completeness.ts index f658f73..8944f17 100644 --- a/scripts/verify-release-completeness.ts +++ b/scripts/verify-release-completeness.ts @@ -268,6 +268,12 @@ export interface CompletenessFetcher { npmVersions(pkgName: string): string[]; /** GitHub Release tag names for the `owner/name` slug. */ githubReleases(slug: string): string[]; + /** + * Whether the GitHub Release of one tag exists, read by its tag. The list + * endpoint can lag a release created seconds earlier; this read confirms a + * tag the list lacks before the audit calls it missing. + */ + githubReleaseForTag(slug: string, tag: string): boolean; } /** @@ -355,6 +361,15 @@ export function makeFetcher(exec: (command: string, args: string[], options: { c githubReleases(slug) { return fetchGithubReleasesPaginated(exec, slug); }, + githubReleaseForTag(slug, tag) { + try { + return exec("gh", ["api", `repos/${slug}/releases/tags/${tag}`, "--method", "GET", "--jq", ".tag_name"], {}).trim() === tag; + } catch (error) { + // Only a 404 answers "no such release"; any other failure is an unreadable source. + if (error instanceof Error && error.message.includes("HTTP 404")) return false; + throw error; + } + }, }; } @@ -366,6 +381,8 @@ export const realFetcher: CompletenessFetcher = makeFetcher(defaultExec); * * A failed read names its source and makes no absence claims from partial data. * Missing package or repository identity is rejected before any registry read. + * A release tag missing from the GitHub Release list is confirmed by a read of + * that tag's release, because the list lags a release created moments earlier. * * @param root - Repository root to verify. * @param fetcher - The source of the three lists. @@ -384,8 +401,13 @@ export function verify(root: string, fetcher: CompletenessFetcher): VerifierResu source = "npm versions"; const npmVersions = fetcher.npmVersions(pkgName); source = "GitHub Releases"; - const githubReleases = fetcher.githubReleases(slug); - return auditReleaseCompleteness(tags, npmVersions, githubReleases); + const listed = fetcher.githubReleases(slug); + // The list endpoint lags a release created moments before: it missed + // pm-ops v2026.09.28, created in the same second this audit ran after it. + // A release tag the list lacks is read by its tag before it counts as missing. + const listedSet = new Set(listed); + const confirmed = tags.filter((tag) => isReleaseTag(tag) && !listedSet.has(tag) && fetcher.githubReleaseForTag(slug, tag)); + return auditReleaseCompleteness(tags, npmVersions, [...listed, ...confirmed]); } catch (error) { return { failures: [`unable to determine release completeness while reading ${source}: ${error instanceof Error ? error.message : String(error)}`], diff --git a/test/verify-release-completeness.test.ts b/test/verify-release-completeness.test.ts index d01c67a..54b4930 100644 --- a/test/verify-release-completeness.test.ts +++ b/test/verify-release-completeness.test.ts @@ -45,12 +45,14 @@ function fixedFetcher(lists: { remoteUrl?: string; npmVersions?: string[]; githubReleases?: string[]; + releasesByTag?: string[]; }): CompletenessFetcher { return { tags: () => lists.tags ?? [], remoteUrl: () => lists.remoteUrl ?? "https://github.com/unbraind/pm-ops.git", npmVersions: () => lists.npmVersions ?? [], githubReleases: () => lists.githubReleases ?? [], + githubReleaseForTag: (_slug, tag) => (lists.releasesByTag ?? []).includes(tag), }; } @@ -226,6 +228,27 @@ test("makeFetcher builds a fetcher whose methods parse executor output", () => { assert.ok(calls.some((c) => c.includes("api") && c.includes("repos/unbraind/pm-ops/releases")), "the githubReleases method queries the GitHub releases API"); }); +test("githubReleaseForTag reads one release by its tag: found, a 404, or an unreadable source", () => { + const calls: string[] = []; + const replies: Record string> = { + "v2026.09.28": () => "v2026.09.28\n", + "v2026.09.27": () => { throw new Error("Command failed: gh api\ngh: Not Found (HTTP 404)"); }, + "v2026.09.26": () => { throw new Error("Command failed: gh api\nerror connecting to api.github.com"); }, + "v2026.09.25": () => { throw "a non-Error rejection"; }, + }; + const fetcher = makeFetcher((command, args) => { + calls.push(`${command} ${args.join(" ")}`); + const tag = /releases\/tags\/(.+)$/.exec(args[1] ?? "")?.[1] ?? ""; + return replies[tag]?.() ?? ""; + }); + assert.equal(fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.28"), true); + assert.equal(calls[0], "gh api repos/unbraind/pm-ops/releases/tags/v2026.09.28 --method GET --jq .tag_name"); + assert.equal(fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.27"), false); + assert.throws(() => fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.26"), /error connecting/); + assert.throws(() => fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.25"), (thrown: unknown) => thrown === "a non-Error rejection"); + assert.equal(fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.24"), false, "an empty answer is not the tag"); +}); + test("realFetcher is built from the real executor at module load", () => { assert.equal(typeof realFetcher.tags, "function"); assert.equal(typeof realFetcher.npmVersions, "function"); @@ -329,6 +352,23 @@ test("verify gathers the three lists through the fetcher and audits them", () => // for the real package's npm versions. }); +test("verify confirms a release the list lags behind by reading its tag, and only for tags the list lacks", () => { + const root = resolve(import.meta.dirname, ".."); + const tags = [...COMPLETE_TAGS, "v2026.09.28", "not-a-release"]; + const npmVersions = [...COMPLETE_NPM, "2026.9.28"]; + const asked: string[] = []; + const lagging = fixedFetcher({ tags, npmVersions, githubReleases: COMPLETE_GH, releasesByTag: ["v2026.09.28"] }); + const byTag = lagging.githubReleaseForTag.bind(lagging); + lagging.githubReleaseForTag = (slug, tag) => { asked.push(`${slug} ${tag}`); return byTag(slug, tag); }; + assert.deepEqual(verify(root, lagging).failures, [], "the release created moments ago exists"); + assert.deepEqual(asked, ["unbraind/pm-ops v2026.09.28"]); + const missing = verify(root, fixedFetcher({ tags, npmVersions, githubReleases: COMPLETE_GH })); + assert.deepEqual(missing.failures, ["release tag v2026.09.28 has no corresponding GitHub Release"]); + const unreadable = fixedFetcher({ tags, npmVersions, githubReleases: COMPLETE_GH }); + unreadable.githubReleaseForTag = () => { throw new Error("rate limited"); }; + assert.deepEqual(verify(root, unreadable).failures, ["unable to determine release completeness while reading GitHub Releases: rate limited"]); +}); + test("verify reports unreadable npm inventory without inventing missing releases", () => { const root = resolve(import.meta.dirname, ".."); for (const reply of ['["2026.8.17", null]', "registry temporarily unavailable"]) { @@ -383,6 +423,7 @@ test("verify derives the repo slug from the remote URL the fetcher returns", () remoteUrl: () => "https://github.com/unbraind/pm-ops.git", npmVersions: () => COMPLETE_NPM, githubReleases: (slug) => { askedSlug = slug; return COMPLETE_GH; }, + githubReleaseForTag: () => assert.fail("every release tag is listed"), }; verify(root, fetcher); assert.equal(askedSlug, "unbraind/pm-ops"); From 90480dac61328a2f4fef45c8ce6dc4061cf1e4c9 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:52:57 +0200 Subject: [PATCH 2/2] Treat a by-tag release read that names another tag as unreadable, and name pm-ops's own launcher test in ops-jzp5 CodeRabbit on #130: a successful by-tag read whose .tag_name is empty or a different tag returned false, so the audit reported the release as absent instead of the source as unreadable. Only an HTTP 404 means absent now; a mismatched answer throws and verify names GitHub Releases as unreadable. ops-jzp5's resolution named the consumer test file; pm-ops's own regression test is test/merge-driver-launcher.test.ts. The earlier value stays in the append-only history, superseded by this update. lint, coverage: 446 tests, 100/100/100/100; changelog:check clean. --- .agents/pm/history/ops-jzp5.jsonl | 1 + .agents/pm/issues/ops-jzp5.toon | 4 ++-- scripts/verify-release-completeness.ts | 6 +++++- test/verify-release-completeness.test.ts | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.agents/pm/history/ops-jzp5.jsonl b/.agents/pm/history/ops-jzp5.jsonl index 6a6f2f6..37633b8 100644 --- a/.agents/pm/history/ops-jzp5.jsonl +++ b/.agents/pm/history/ops-jzp5.jsonl @@ -10,3 +10,4 @@ {"hash_algorithm":"sha256","ts":"2026-09-28T05:27:53.285Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:27:53.285Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-09-28T05:27:53.214Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-09-28T05:27:53.214Z"},{"op":"add","path":"/metadata/close_reason","value":"Fixed by pm-ops#124 (merged 2026-09-28, 450e2be); released in pm-ops 2026.9.28 (npm + GitHub Release v2026.09.28)."}],"before_hash":"963c939574eb30e6f35aed457b151018eb76d704c4d696f14074028f77c0c47a","after_hash":"447567930c97d96a0d6e11da53d9dff126fa64c83da2490c6d79c30c8305cae0","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"3dbddcf4aa79ed4596530a0adc8faee945a6d0e6e0d9346057a623c4988e7037"} {"hash_algorithm":"sha256","ts":"2026-09-28T05:28:08.885Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:28:08.885Z"},{"op":"add","path":"/metadata/actual_result","value":"The launcher probed only the checkout's own node_modules, classified the broken hoisted pm-ops as an omit-dev install, and exited 0 without registering the merge drivers."}],"before_hash":"447567930c97d96a0d6e11da53d9dff126fa64c83da2490c6d79c30c8305cae0","after_hash":"272d0e2f9526ad759d48bc5f6168c2f514d4a34e34ab46fb592cb378b589cf6d","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"6796285465396821b05083accd1ef4507620485947409bca40b47eb3a945e95a"} {"hash_algorithm":"sha256","ts":"2026-09-28T05:37:13.207Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:37:13.207Z"},{"op":"add","path":"/metadata/release","value":"2026.9.28"}],"before_hash":"272d0e2f9526ad759d48bc5f6168c2f514d4a34e34ab46fb592cb378b589cf6d","after_hash":"2a3338976220d7f0d6b575ac2130217fe0bd79a8a0efd6652bb9b62014f9ca68","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"c5be3c6cedec1390e0b81cfff9ecb67afdf5cb78241f4151fc29b14127541e07"} +{"hash_algorithm":"sha256","ts":"2026-09-28T05:48:07.046Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"acec329f9c06ecc0d6dbecd4","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"high","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.283","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/resolution","value":"pm-ops#124 checks every Node resolution path (require.resolve.paths) for an incomplete pm-ops before allowing the omit-dev skip; test/merge-driver-launcher.test.ts runs a real child checkout beneath a broken ancestor install. Shipped in pm-ops 2026.9.28."},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-28T05:48:07.046Z"}],"before_hash":"2a3338976220d7f0d6b575ac2130217fe0bd79a8a0efd6652bb9b62014f9ca68","after_hash":"e704a37cde45c6824bb1788e64fccbc04dc983662338caf46b4ca64a203bb6a1","item_hash_version":3,"context":{"workflow_policies":[{"policy_id":"completeness-issue","policy_fingerprint":"29451c1f80adfbacaeaa7936a6a7f69fe058f29da602f3dfbc941152e36a7130","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."},{"policy_id":"fleet-closed-resolution","policy_fingerprint":"e13f80d0b1fcef3c8ead9b5e9a32723dc589cc189ef68aa23badea68dace3a17","rule":"require_fields","effect":"refuse","satisfied":true,"missing_fields":[],"remediation":"Supply the declared required fields before this lifecycle transition."}],"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"e74033a53a209674fb0f7f45f91fb4e0aa30703318ee1dc9149c7a4f78ac84be"} diff --git a/.agents/pm/issues/ops-jzp5.toon b/.agents/pm/issues/ops-jzp5.toon index 64e28fc..a99dd2e 100644 --- a/.agents/pm/issues/ops-jzp5.toon +++ b/.agents/pm/issues/ops-jzp5.toon @@ -6,7 +6,7 @@ status: closed priority: 0 tags: [] created_at: "2026-09-26T16:43:46.361Z" -updated_at: "2026-09-28T05:37:13.207Z" +updated_at: "2026-09-28T05:48:07.046Z" closed_at: "2026-09-28T05:27:53.214Z" completed_at: "2026-09-28T05:27:53.214Z" claim_principal: codex @@ -14,7 +14,7 @@ author: codex acceptance_criteria: "A real child checkout with an empty ancestor node_modules/pm-ops fails the prepare hook without the omit-dev skip notice; a truly absent package still skips once; an intact hoisted pm-ops runs the installer; the regression fails on the old template, full release:check and strict PM health pass; consumer copies update only after a canonical pm-ops release." release: 2026.9.28 repro_steps: "Install a consumer package beneath an ancestor node_modules/pm-ops that lacks package.json, then run the package's prepare script (scripts/prepare-merge-driver.ts)." -resolution: pm-ops#124 checks every Node resolution path (require.resolve.paths) for an incomplete pm-ops before allowing the omit-dev skip; test/prepare-merge-driver.test.ts runs a real child checkout beneath a broken ancestor install. Shipped in pm-ops 2026.9.28. +resolution: pm-ops#124 checks every Node resolution path (require.resolve.paths) for an incomplete pm-ops before allowing the omit-dev skip; test/merge-driver-launcher.test.ts runs a real child checkout beneath a broken ancestor install. Shipped in pm-ops 2026.9.28. expected_result: "The launcher detects the broken hoisted pm-ops, exits non-zero, and prints no omit-dev skip notice." actual_result: "The launcher probed only the checkout's own node_modules, classified the broken hoisted pm-ops as an omit-dev install, and exited 0 without registering the merge drivers." dependencies[1]{id,kind,created_at,author,source_kind,author_source}: diff --git a/scripts/verify-release-completeness.ts b/scripts/verify-release-completeness.ts index 8944f17..a8fbba8 100644 --- a/scripts/verify-release-completeness.ts +++ b/scripts/verify-release-completeness.ts @@ -362,13 +362,17 @@ export function makeFetcher(exec: (command: string, args: string[], options: { c return fetchGithubReleasesPaginated(exec, slug); }, githubReleaseForTag(slug, tag) { + let answered: string; try { - return exec("gh", ["api", `repos/${slug}/releases/tags/${tag}`, "--method", "GET", "--jq", ".tag_name"], {}).trim() === tag; + answered = exec("gh", ["api", `repos/${slug}/releases/tags/${tag}`, "--method", "GET", "--jq", ".tag_name"], {}).trim(); } catch (error) { // Only a 404 answers "no such release"; any other failure is an unreadable source. if (error instanceof Error && error.message.includes("HTTP 404")) return false; throw error; } + // A successful read that names another tag (or none) is unreadable, not absent. + if (answered !== tag) throw new Error(`the release read for ${tag} answered tag "${answered}"`); + return true; }, }; } diff --git a/test/verify-release-completeness.test.ts b/test/verify-release-completeness.test.ts index 54b4930..bde2174 100644 --- a/test/verify-release-completeness.test.ts +++ b/test/verify-release-completeness.test.ts @@ -246,7 +246,7 @@ test("githubReleaseForTag reads one release by its tag: found, a 404, or an unre assert.equal(fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.27"), false); assert.throws(() => fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.26"), /error connecting/); assert.throws(() => fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.25"), (thrown: unknown) => thrown === "a non-Error rejection"); - assert.equal(fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.24"), false, "an empty answer is not the tag"); + assert.throws(() => fetcher.githubReleaseForTag("unbraind/pm-ops", "v2026.09.24"), /the release read for v2026.09.24 answered tag ""/, "a successful read without the tag is unreadable, not absent"); }); test("realFetcher is built from the real executor at module load", () => {