diff --git a/.agents/pm/history/pm-github-fxhd.jsonl b/.agents/pm/history/pm-github-fxhd.jsonl index 1d5bd8ca..5c9c5a65 100644 --- a/.agents/pm/history/pm-github-fxhd.jsonl +++ b/.agents/pm/history/pm-github-fxhd.jsonl @@ -97,3 +97,31 @@ {"hash_algorithm":"sha256","ts":"2026-11-11T16:10:58.134Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"c5ca879c7f0abd7c24f7562f","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/41","value":{"created_at":"2026-11-11T16:10:58.133Z","author":"codex-agent","text":"Final gate receipt for the 2b8375a review continuation: lint, typecheck and build:test passed. Requested files: bot-pr-workflow 109/109; self-versioned-workflows 61/61 (combined initial run 170/170, then final bot-only run 109/109 after publishing the fixture proposal ref); closeout-workflow-publication 49/49 in 795.031 seconds; closeout-workflow-decisions 187/187 in 713.877 seconds. Total requested-file tests 406/406, no failures/cancellations/skips. The pinned PM focused linked command also passed 7/7 in a disposable tracker with --run --progress and schema/source context (8.921 seconds, exit 0), keeping run artifacts out of the package tracker. Every gate had an explicit cap no longer than 1200 seconds. Revert receipts above identify the exact function/file mutations and observed behavioral assertions; restored focused selection passes 7/7. Thread mapping: PRRT_kwDOTbN4pM62PU5Q caller templates; PRRT_kwDOTbN4pM62PU5I exact discovery documentation; PRRT_kwDOTbN4pM62PU5X remote base/proposal freshness and scheduled rebuild; PRRT_kwDOTbN4pM62PU5e all-initialized-fixture cleanup. Outside-diff PRR_kwDOTbN4pM8AAAABXVuP0Q is fixed by the shared strict-check/merge-queue guard, typed refusal and real remote stale-landing regression. Full coverage/release gates remain CI-owned at https://github.com/unbraind/pm-github/pull/229/checks. No review replies, resolutions or reactions posted. Releasing the claim without closing, then regenerating the changelog and pushing this scoped round."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-11T16:10:58.134Z"}],"before_hash":"11bf3611bc549ed40289fe1ca34d63903755edcf1d09bf66820ec71844862875","after_hash":"e679009737445f60eedc9d20f0d51effb74e8f7d7bf626309e8c6a82a336eabf","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":"7cf9456b69a15c1ecffbca846fd918d80e1aea4a7fa388039456b486fcfec98e"} {"hash_algorithm":"sha256","ts":"2026-11-11T16:10:59.223Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"c5ca879c7f0abd7c24f7562f","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-11-11T16:10:59.223Z"}],"before_hash":"e679009737445f60eedc9d20f0d51effb74e8f7d7bf626309e8c6a82a336eabf","after_hash":"107ad6fc7f0e264cd3fb7a658315c3371f30f6a15a406a93c6feaa56d44599ab","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":"bf3e550c46e6b25ff0f1dd56aee679565f451d00916095426c289ce8aac6cd55"} {"hash_algorithm":"sha256","ts":"2026-11-12T15:00:54.479Z","author":"claude-hub","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"45d551c0e5ac79602511fdd7","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.322","source":"probe"}},"op":"close","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-12T15:00:54.479Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-11-12T15:00:54.460Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-11-12T15:00:54.460Z"},{"op":"add","path":"/metadata/resolution","value":"Implemented and merged in #229 (6ccb293)."},{"op":"add","path":"/metadata/expected_result","value":"The change described by this item is on main."},{"op":"add","path":"/metadata/actual_result","value":"Merged in #229 as 6ccb293; checks successful at head 0428178: CodeQL, bun-release (22), bun-release (26), codeql, semgrep-cloud-platform/scan, test (22), test (26)."},{"op":"add","path":"/metadata/close_reason","value":"Delivered by #229 \"Stop gated sync and closeout jobs from waiting for caller CI\" (merged as 6ccb293). Checks successful at the PR head 0428178: CodeQL, bun-release (22), bun-release (26), codeql, semgrep-cloud-platform/scan, test (22), test (26)."}],"before_hash":"107ad6fc7f0e264cd3fb7a658315c3371f30f6a15a406a93c6feaa56d44599ab","after_hash":"c54997043e386f684ea8be732bc7d1714eda264d2157eb5888f6c48b90a81416","item_hash_version":3,"context":{"completion":{"timestamp":"2026-11-12T15:00:54.460Z","source":"close_time"},"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":"8657de60f55baf392e50a903760a818d847165dd1f160b66ac6d6c33ca72e264"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:00:20.029Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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":"remove","path":"/metadata/close_reason"},{"op":"remove","path":"/metadata/actual_result"},{"op":"remove","path":"/metadata/expected_result"},{"op":"remove","path":"/metadata/resolution"},{"op":"remove","path":"/metadata/completed_at"},{"op":"remove","path":"/metadata/closed_at"},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:00:20.029Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"c54997043e386f684ea8be732bc7d1714eda264d2157eb5888f6c48b90a81416","after_hash":"6ab4a468467c729031a0c8ea6c941d11f2b4b6d5354a6ed6e22de5c6d030e314","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":"a81c0cc9864554a270c106a685ad40471ee3ff19e333017c7541b356637728ad"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:00:21.055Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/42","value":{"created_at":"2026-11-14T17:00:21.054Z","author":"codex-github-nopoll-1114","text":"Based on PR #245 head while it remains open. Existing dispatch already exits without completion polling. Complete the contract with one shared bounded run-appearance confirmation window, informational timeout, PR/dispatched/parked URLs in summaries and reusable outputs, and workflow-shell regression/revert evidence. Preserve strict protected auto-merge, closeout review revocation, sync holds and reference ownership (#230). Other reusable workflows have no completion waits; the non-reusable release wait remains separately tracked by pm-github-sgyr."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:00:21.055Z"}],"before_hash":"6ab4a468467c729031a0c8ea6c941d11f2b4b6d5354a6ed6e22de5c6d030e314","after_hash":"5c19c4e9c3d1922e1d3d6e07037002d83498331bafd0f3590ca8be405f340c06","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":"c08902ffc8afc675599594f3e06ac25167ef21cc5599066ff3c3e572ce93d877"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:00:48.537Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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-11-14T17:00:48.537Z"},{"op":"add","path":"/metadata/assignee","value":"codex-github-nopoll-1114"},{"op":"add","path":"/metadata/claim_principal","value":"codex-github-nopoll-1114"}],"before_hash":"5c19c4e9c3d1922e1d3d6e07037002d83498331bafd0f3590ca8be405f340c06","after_hash":"e0cdd5032eed755d993ae4ef25a5246b17d411ca80a33e768535eddae1ca73ba","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":"1acfac90b50cabc20145775695a415c9921daf8f43f3f7e4226843ace7c7af04"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:06:26.044Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/18","value":{"command":"node --test --test-concurrency=2 test/ci-handoff-workflow.test.ts","scope":"project","provenance":{"author":"codex-github-nopoll-1114","created_at":"2026-11-14T17:06:25.987Z","source_kind":"local_mutation","source_ref":"fix/no-ci-polling-on-runners"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:06:26.044Z"}],"before_hash":"e0cdd5032eed755d993ae4ef25a5246b17d411ca80a33e768535eddae1ca73ba","after_hash":"b3fb1f5db636036cee7ad0018216f8f92a6474b4dcf124bd05c5d56ba4d0c771","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":"c6e1c3d9bd4d1b9e665ca05392594ea8ce3830e739b20a8de5b5255cfe7aa2c4"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:08:02.295Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/files/10/path","value":"test/closeout-workflow.test.ts"},{"op":"replace","path":"/metadata/files/9/path","value":"test/closeout-workflow-publication.ts"},{"op":"replace","path":"/metadata/files/8/path","value":"test/closeout-workflow-decisions.ts"},{"op":"replace","path":"/metadata/files/7/path","value":"test/ci-handoff-workflow.test.ts"},{"op":"add","path":"/metadata/files/11","value":{"path":"test/helpers/closeout-workflow.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:08:02.295Z"}],"before_hash":"b3fb1f5db636036cee7ad0018216f8f92a6474b4dcf124bd05c5d56ba4d0c771","after_hash":"704b03d5226f2390539cf82a6d8e3f4c94c3e403df124fe224ba333fb9dcf00e","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":"3afdf8f904c69b2037df509655f7b4e44e5ee2e4f3957ce5e9b52aa8cdaacbc5"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:08:06.856Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/docs/4/path","value":"docs/sync-workflow-caller.yml"},{"op":"replace","path":"/metadata/docs/3/path","value":"docs/no-ci-polling-migration.md"},{"op":"add","path":"/metadata/docs/5","value":{"path":"README.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:08:06.856Z"}],"before_hash":"704b03d5226f2390539cf82a6d8e3f4c94c3e403df124fe224ba333fb9dcf00e","after_hash":"068918913cef4a4af028586fdaeb9014ebc1b120b03ca85a187bd0cafdc80655","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":"620dca23c5f588891214622824494695e7c74cdcc5f9a6af6b49ae909d13c4e0"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:08:16.551Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/43","value":{"created_at":"2026-11-14T17:08:16.530Z","author":"codex-github-nopoll-1114","text":"Implemented additive reusable outputs and summary receipts with one shared 60-second appearance-only confirmation budget, at most 12 rounds, and ten-second hard-killed API calls started only while ten seconds remain. Queued runs count immediately; lookup failures/timeouts report without failing sync or revoking auto-merge. A separate receipt step preserves the existing dispatch no-wait contract. Closeout review holds still forbid arming, now retaining parked-run receipts. Historical completion loops restored independently from the parent of 6ccb293 in disposable copies each fail the all-reusable-step contract (1 failing test per loop); restoring fixed workflows passes. Focused workflow group passed 195/195. No authored TS/JS production source changed; all-source coverage thresholds remain 100/100/100/100 for CI certification as requested. actionlint is unavailable. Attestation gate passes with literal ten-second timeouts. The initial closeout replay lacked a built package; built locally and rerunning the safety cases."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:08:16.551Z"}],"before_hash":"068918913cef4a4af028586fdaeb9014ebc1b120b03ca85a187bd0cafdc80655","after_hash":"39ec601ad1a7ef0c64b82b4e5c03578a62c42a0db8afd5c24a91b401b3f3b569","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":"39cf84434262825b1e652a112fbc110abdd4075e16e4ebb22beacee6d099b6ab"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:09:40.707Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/12","value":{"path":"test/import-gate.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:09:40.707Z"}],"before_hash":"39ec601ad1a7ef0c64b82b4e5c03578a62c42a0db8afd5c24a91b401b3f3b569","after_hash":"5260f32927fbe782137614417591cff26a51347318e572ac03b6e6cc3f6d62f3","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":"24e9b912f7d30246416a28745593ae70545c9071b67c59935e9de6363a0c912c"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:09:42.561Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/19","value":{"command":"node --test --test-concurrency=2 test/ci-handoff-workflow.test.ts test/bot-pr-workflow.test.ts test/self-versioned-workflows.test.ts test/workflow-rule-names.test.ts test/daily-sync-alert.test.ts","scope":"project","provenance":{"author":"codex-github-nopoll-1114","created_at":"2026-11-14T17:09:42.481Z","source_kind":"local_mutation","source_ref":"fix/no-ci-polling-on-runners"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:09:42.561Z"}],"before_hash":"5260f32927fbe782137614417591cff26a51347318e572ac03b6e6cc3f6d62f3","after_hash":"918fd99db5e2de9d3aa5aa4f3cd1aed27ac67c0811cf01273fef49cd4c5afa12","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":"41554401cb7f93698880b0750d5ef62adb5112dc3ee8a277a595e7e55dae0b5a"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:09:44.072Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/20","value":{"command":"NPM_CONFIG_USERCONFIG=/dev/null node --test --test-concurrency=2 --test-name-pattern=\"workflow credentials|earlier armed auto-merge|informational parked-run failure|previous auto-merge is revoked on workflow exit: maintenance\" test/import-gate.test.ts test/closeout-workflow.test.ts","scope":"project","provenance":{"author":"codex-github-nopoll-1114","created_at":"2026-11-14T17:09:43.992Z","source_kind":"local_mutation","source_ref":"fix/no-ci-polling-on-runners"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:09:44.072Z"}],"before_hash":"918fd99db5e2de9d3aa5aa4f3cd1aed27ac67c0811cf01273fef49cd4c5afa12","after_hash":"8a380b451fb7f4599c0fd555e2c70279329fa3c665961218a638df694008c4e3","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":"0def070183cc39c85b6c98d0fb668183f082c99bd91dc7dec4346cc35f3b78c5"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:09:45.867Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/44","value":{"created_at":"2026-11-14T17:09:45.866Z","author":"codex-github-nopoll-1114","text":"Safety replay after build passed 4/4: prior armed request revoked before a later hold, parked-run lookup failure preserves the armed request, maintenance failure revokes, and workflow token bindings exclude installation/checkout persistence. typecheck, build:test, lint, privacy (9356 objects), docstrings (33 files / 315 declarations), and publish attestation passed. Runner-time expectation is below two minutes for the measured 44-second workload plus at most 60 seconds of registration discovery, versus the historical about 85-minute fleet average; not a newly measured fleet rollout. No caller changes beyond the #238 pin sweep. Final-head full npm/Bun release gates and unchanged four-dimensional 100-percent coverage are delegated to CI under the targeted task instructions."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:09:45.867Z"}],"before_hash":"8a380b451fb7f4599c0fd555e2c70279329fa3c665961218a638df694008c4e3","after_hash":"6ca8782e93740c1b6211f2474dfeb7289c35b2a0cd7bb4adad0ce57ab1410280","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":"85bcfabe9fa2025be36bedd58082d68733bdca7d147e4497c1a37c47ca74bbee"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:10:34.154Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/21","value":{"command":"bun test --timeout 120000 test/ci-handoff-workflow.test.ts","scope":"project","provenance":{"author":"codex-github-nopoll-1114","created_at":"2026-11-14T17:10:33.847Z","source_kind":"local_mutation","source_ref":"fix/no-ci-polling-on-runners"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:10:34.154Z"}],"before_hash":"6ca8782e93740c1b6211f2474dfeb7289c35b2a0cd7bb4adad0ce57ab1410280","after_hash":"4175cda7215d4f79045016113386869b6814750e25caee8bb00e4b2cc9396481","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":"b5814abd312cfb7db9576be79b9bcc262376f216cce13de01047ebfe9fa2937f"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:10:35.313Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/45","value":{"created_at":"2026-11-14T17:10:35.313Z","author":"codex-github-nopoll-1114","text":"Native Bun focused receipt suite passed 17/17 with zero failures. Pinned pm test executed the same 17 Node regressions with --run --progress successfully. Initial claim of the terminal item was refused; reopened the existing #228 item and claimed it successfully rather than creating a duplicate. No new pm CLI/SDK defect found."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:10:35.313Z"}],"before_hash":"4175cda7215d4f79045016113386869b6814750e25caee8bb00e4b2cc9396481","after_hash":"ee6a938aba65c9c684d230db5b90e6503d4c588147ed8a78c7ed8c2a9e469b81","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":"2efe2c93ee293ffe0d65e8d3a62d48930e51ec2f05cf153a8435c6d22f2646d3"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:12:15.605Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/46","value":{"created_at":"2026-11-14T17:12:15.605Z","author":"codex-github-nopoll-1114","text":"Implementation commit e0a83090f1a3963d8b7c1a1fc01064e278147379 pushed and PR https://github.com/unbraind/pm-github/pull/246 opened against main, based on still-open #245 head. At publication, CI https://github.com/unbraind/pm-github/actions/runs/43619767839 and CodeQL https://github.com/unbraind/pm-github/actions/runs/43619767884 are queued for that implementation head. Finish tracker writes and release the claim now, regenerate/check the changelog, then verify the resulting final head for at most 30 minutes; final CI status is reported separately without another tracker mutation. Shared-runner hold was checked before pushing and required no wait. Identity check printed only the required SteveBot noreply address. Leave this item in progress for orchestrator verification; no merge, release or review-thread interaction performed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:12:15.605Z"}],"before_hash":"ee6a938aba65c9c684d230db5b90e6503d4c588147ed8a78c7ed8c2a9e469b81","after_hash":"b6baba0a6ea517794482bfb97d5c197a90e8145cb1dec93a3eabc9f9c0b522a1","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":"a1d07e5491d4764eb1ee13c8010adb78ae72fe0c8c5af091e0750348cc5ec0fa"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:12:16.588Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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-11-14T17:12:16.588Z"}],"before_hash":"b6baba0a6ea517794482bfb97d5c197a90e8145cb1dec93a3eabc9f9c0b522a1","after_hash":"699e3430f434a95cc5ffbddf0ef2169769b61ab476b2f960cca55dc428abd596","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":"605315c8e2a52a20f4458890ecc376726a121fcae0ee1286a9006988bf09f9e3"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:17:13.586Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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-11-14T17:17:13.586Z"},{"op":"add","path":"/metadata/assignee","value":"codex-github-nopoll-1114"},{"op":"add","path":"/metadata/claim_principal","value":"codex-github-nopoll-1114"}],"before_hash":"699e3430f434a95cc5ffbddf0ef2169769b61ab476b2f960cca55dc428abd596","after_hash":"42d52c6a6cf5205cb77e0da5faf94e634c0922127489b7ace66a4e37cf5e8a5e","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":"2af2832a711923aa7fb10c7daca69dc687e2740e27d650febeaee6c6424f8585"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:17:14.760Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/47","value":{"created_at":"2026-11-14T17:17:14.759Z","author":"codex-github-nopoll-1114","text":"Read-only review inspection found a valid missing behavioral proof in review comment 4574827470: accelerated fixtures did not exercise shared deadline exhaustion or actual stuck-command termination. Add controlled-clock tests across three workflow selections and real GNU timeout cases, retaining partial URLs and refusing late lookups. Do not reply, resolve or react to the thread. Workflow implementation remains unchanged; record the fix commit/test mapping at final handoff."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:17:14.760Z"}],"before_hash":"42d52c6a6cf5205cb77e0da5faf94e634c0922127489b7ace66a4e37cf5e8a5e","after_hash":"6f0ae23e9e203e5249d10d6e6d7c92b29111b764afa9c58121193820597b7d72","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":"a6ae2020550e29a3d700fd7e79170abd444cfd620a5b928da0403ce86b104493"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:19:57.083Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/48","value":{"created_at":"2026-11-14T17:19:57.082Z","author":"codex-github-nopoll-1114","text":"Review thread PRRT_kwDOTbN4pM63g99s (comment 4574827470) now has behavioral coverage: controlled clock advances across ci.yml and codeql.yml to the shared deadline before extra.yml may start; already discovered CI URLs survive. Real GNU timeout kills a genuinely stuck lookup after ten seconds, and the receipt still exits successfully with partial URLs. All 4 selected Node budget tests and all 21 Bun receipt tests passed. In disposable copies, removing the admission guard fails each shared-deadline test, and changing the real timeout to one second fails each stuck-lookup test (four independent expected failures). No workflow implementation or approval/merge safety policy changed in this review fix. Thread remains open, with no reply/reaction/resolution."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:19:57.083Z"}],"before_hash":"6f0ae23e9e203e5249d10d6e6d7c92b29111b764afa9c58121193820597b7d72","after_hash":"4e3976700eef30c23b5a3a5f930521303594a57da80d9b3aa3a78906ecec03f0","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":"dc6a50d9ddd0aca174682b42d1ca31e8c21dd80908309a289c5c6b5a9e178bf4"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:19:57.945Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/22","value":{"command":"node --test --test-concurrency=2 --test-name-pattern=\"shared deadline|real timeout\" test/ci-handoff-workflow.test.ts","scope":"project","provenance":{"author":"codex-github-nopoll-1114","created_at":"2026-11-14T17:19:57.911Z","source_kind":"local_mutation","source_ref":"fix/no-ci-polling-on-runners"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:19:57.945Z"}],"before_hash":"4e3976700eef30c23b5a3a5f930521303594a57da80d9b3aa3a78906ecec03f0","after_hash":"8f843daa628ce33dfaf5f3cf4506f596ff42c89372e1ce3db097ea245728cd8a","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":"7a6479d82d856f274c5e1481f3f0319bec56e7b5ca63bdc06e86712dddcda97b"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:21:10.122Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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/49","value":{"created_at":"2026-11-14T17:21:10.122Z","author":"codex-github-nopoll-1114","text":"Updated focused workflow group passed 199/199 through pinned pm test --run --progress (linked test 20), with zero failures/skips; native Bun receipt suite passed 21/21. typecheck, build:test and lint passed after adding the behavioral budget cases. CI and CodeQL for the prior head remain queued; the next push supersedes that head. Finish tracker writes before final-head CI inspection, preserve the original bounded CI cutoff, and leave PRRT_kwDOTbN4pM63g99s open for orchestrator response with the upcoming test-fix commit."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T17:21:10.122Z"}],"before_hash":"8f843daa628ce33dfaf5f3cf4506f596ff42c89372e1ce3db097ea245728cd8a","after_hash":"24fcf33ddb15f95452afe504851eb0b9d1d5427a5f6ca2c3185c01d6f4082d0f","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":"d26c31077b48b983a752a8914ee8324b2aba2f394d18bbfb8fada396b11bf090"} +{"hash_algorithm":"sha256","ts":"2026-11-14T17:21:11.052Z","author":"codex-github-nopoll-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"aea3445a0d94d45c7124b0bf","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-11-14T17:21:11.052Z"}],"before_hash":"24fcf33ddb15f95452afe504851eb0b9d1d5427a5f6ca2c3185c01d6f4082d0f","after_hash":"405291f4bb560128b0da9b5b7401bef79909a291c74f1f186bd793b241a617e4","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":"c622009ba952179848a27ffd6dbbf4ac0d281e301ff4c16dcd3b9bd5aa0350da"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:23:59.524Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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-11-14T20:23:59.524Z"},{"op":"add","path":"/metadata/assignee","value":"codex-followups-1114"},{"op":"add","path":"/metadata/claim_principal","value":"codex-followups-1114"}],"before_hash":"405291f4bb560128b0da9b5b7401bef79909a291c74f1f186bd793b241a617e4","after_hash":"dbeaaa8de7a03fb393604d029813f8aa6a11921bb0f3648e517c494cc231880c","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":"0a49653531dca42a7cdab8b1ed2969dcd700c8414e7308432cc227cc8d96d242"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:24:05.039Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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/50","value":{"created_at":"2026-11-14T20:24:05.034Z","author":"codex-followups-1114","text":"Review follow-up for PR 246: merge the fixed PR 245 branch, regenerate the ownership inventory with the repository CLI if this branch shifted more workflow lines, and run the same inventory-related test files and typecheck/lint gates. Preserve all no-runner-polling behavior. Full npm/Bun certification remains in CI."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:24:05.039Z"}],"before_hash":"dbeaaa8de7a03fb393604d029813f8aa6a11921bb0f3648e517c494cc231880c","after_hash":"35c656dfb8dfdfd7a98acd1175001723ee8565ac6c63f9a57c166599cf04d50c","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":"44220d77138d35204da0a12c1d70574f2a93d37fe1fa35bc1cb9ae84e586b030"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:24:50.901Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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/files/12/path","value":"test/identity-inventory.test.ts"},{"op":"add","path":"/metadata/files/13","value":{"path":"test/import-gate.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:24:50.901Z"}],"before_hash":"35c656dfb8dfdfd7a98acd1175001723ee8565ac6c63f9a57c166599cf04d50c","after_hash":"00bc442d1debb702c0f9b2b2cc1ef7b50d73d18808085726b561478a9cbadd08","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":"79372e6aeb47e51998a00396ed2baada1624ea2af771bf01d81df046ee546ca2"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:24:53.644Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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/docs/5/path","value":"docs/sync-workflow-caller.yml"},{"op":"replace","path":"/metadata/docs/4/path","value":"docs/no-ci-polling-migration.md"},{"op":"replace","path":"/metadata/docs/3/path","value":"docs/identity-ownership-inventory.md"},{"op":"add","path":"/metadata/docs/6","value":{"path":"README.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:24:53.644Z"}],"before_hash":"00bc442d1debb702c0f9b2b2cc1ef7b50d73d18808085726b561478a9cbadd08","after_hash":"2627f2309a55d8c345b0dc0d615b54c248f2940a8126647cb98a2e83a00c9507","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":"e298c703cdf5579bb83cfa63be933df2a454fcd63560ee1b1cde12e1f215d70d"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:24:56.355Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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/23","value":{"command":"node --test --test-concurrency=2 test/identity-inventory.test.ts test/round-twelve.test.ts","scope":"project","timeout_seconds":600,"provenance":{"author":"codex-followups-1114","created_at":"2026-11-14T20:24:56.263Z","source_kind":"local_mutation","source_ref":"fix/no-ci-polling-on-runners"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:24:56.355Z"}],"before_hash":"2627f2309a55d8c345b0dc0d615b54c248f2940a8126647cb98a2e83a00c9507","after_hash":"790865d7ef1d9c09633726fe3d828438d38b36017618e9c1d05b9036df47c3bb","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":"f7b69ac63502fb325f534f19ea6451a30b9c456fad5a289a383c7e24f55ed823"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:26:31.165Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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/51","value":{"created_at":"2026-11-14T20:26:31.165Z","author":"codex-followups-1114","text":"Merged fixed PR 245 commit 4bd7c31. The inherited generated inventory still failed --check on this branch because its own workflow lines moved; repository generator --write changes the auxiliary anchor from 440 to 458 and --check now passes. The same complete inventory/round-twelve linked test command passes 37/37 with zero failures/skips; npm run typecheck, npm run build:test and npm run lint pass. Preserve both parents and merge this repair as one follow-up commit. No workflow implementation changed. Full npm/Bun release gates remain in CI."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:26:31.165Z"}],"before_hash":"790865d7ef1d9c09633726fe3d828438d38b36017618e9c1d05b9036df47c3bb","after_hash":"b046a0157926080a834b05221caea16bd56340cf2a43da8a5cb0b3862885bf51","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":"be1163340d6c86429b4f5bd9145cfa24369ec6984ec3660c31b5b183b0e9d041"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:26:32.212Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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-11-14T20:26:32.212Z"}],"before_hash":"b046a0157926080a834b05221caea16bd56340cf2a43da8a5cb0b3862885bf51","after_hash":"94b530e905468758c28670f92b624395244056a202aa6495ac2aacd5a096173a","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":"62fd1d1f947fb35e1f4f93a8495477e8c9c08dfa2a5eabdd105b30b78e5cdf68"} diff --git a/.agents/pm/history/pm-github-workflow-own-release.jsonl b/.agents/pm/history/pm-github-workflow-own-release.jsonl new file mode 100644 index 00000000..98b150fd --- /dev/null +++ b/.agents/pm/history/pm-github-workflow-own-release.jsonl @@ -0,0 +1,24 @@ +{"hash_algorithm":"sha256","ts":"2026-11-14T09:04:23.981Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-workflow-own-release"},{"op":"add","path":"/metadata/title","value":"Default reusable workflows to their own package release; warn on deliberate version holds"},{"op":"add","path":"/metadata/description","value":"Fix #238: derive the default extension from package.json at job.workflow_sha, preserve validated explicit holds with a two-version warning, and preflight registry existence before tracker writes. Migration: callers must drop pm-github-version so Dependabot workflow updates also select the extension version, including the curated identity fix in 2026.11.14."},{"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":["changelog-unreleased","gh:unbraind/pm-github#238","workflows"]},{"op":"add","path":"/metadata/created_at","value":"2026-11-14T09:04:23.981Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-11-14T09:04:23.981Z"},{"op":"add","path":"/metadata/author","value":"codex-github238-1114"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"fabf67c24a935e3fa26ac84937bbeb1746b0087d059d0d725075a5eddbc42cd6","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":"3d4d2d592c277fc50cb3f20e1b5056d137f1dabbd2a065fee241ec47a6d0d6fd"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:04:25.917Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:04:25.917Z"},{"op":"add","path":"/metadata/assignee","value":"codex-github238-1114"},{"op":"add","path":"/metadata/claim_principal","value":"codex-github238-1114"}],"before_hash":"fabf67c24a935e3fa26ac84937bbeb1746b0087d059d0d725075a5eddbc42cd6","after_hash":"bbf4203068fa68329280fef765f16dcdf0c1cb286cfd0bb1e944f4d82ba11b1a","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":"f942cba05807f514834fc161185c309f68d3515fa6d1f2f585ecf3bd3ec2a4ce"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:04:27.949Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:04:27.949Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"bbf4203068fa68329280fef765f16dcdf0c1cb286cfd0bb1e944f4d82ba11b1a","after_hash":"ab2114da2f668c07de8a5a172e33b37532cb392c000eec173f8fbe2d63432260","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":"a30c7571b00fcca265d469b055cda1cd5bf6103b743396669337864a1ba93f2a"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:04:30.564Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:04:30.564Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-11-14T09:04:30.564Z","author":"codex-github238-1114","text":"Plan: replace tag enumeration with a public package.json fetch at the reusable job workflow SHA, normalize exact numeric versions, honor explicit holds and warn with both versions, then check the selected exact registry release before any tracker write. Execute extracted workflow bash with real scratch pm trackers, cover the issue regression cases and behavioral reverts, document caller migration, and inventory public fleet callers through the contents API without changing them. Targeted gates follow the task brief; CI runs the full gate."}]}],"before_hash":"ab2114da2f668c07de8a5a172e33b37532cb392c000eec173f8fbe2d63432260","after_hash":"f892f4b445fba25aa07fb574182c684edba59c9424564fa808c7ac73e26320d7","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":"b08f04dcad31f9637e255c354fef7f156acd0eacbc570ad6bb280d1faaf692bc"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:09:29.562Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:09:29.562Z"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/pm-github-closeout.yml","scope":"project"},{"path":".github/workflows/pm-github-sync.yml","scope":"project"},{"path":"test/self-versioned-workflows.test.ts","scope":"project"}]}],"before_hash":"f892f4b445fba25aa07fb574182c684edba59c9424564fa808c7ac73e26320d7","after_hash":"b4746bc49e3bbaf9a5f41abf3ae00038fd89a52d5002b7ff4dcbe005aa8d6374","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":"f5f401533cce78328c71b065846b740b89ff6e00ef4ca741f9671c7c12eb4e8d"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:09:31.723Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:09:31.723Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/workflow-version-callers.md","scope":"project"},{"path":"README.md","scope":"project"}]}],"before_hash":"b4746bc49e3bbaf9a5f41abf3ae00038fd89a52d5002b7ff4dcbe005aa8d6374","after_hash":"814a2cc155d7d51f3d001dc3100ba748e513a6d2e7a4dcafdec2d58baf7416bc","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":"ffbfd3ac9779001164a2c8fae2c1106f222f1269828ec6ef85a444f979f6a8f1"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:09:34.287Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:09:34.287Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --test-concurrency=2 test/self-versioned-workflows.test.ts","scope":"project","provenance":{"author":"codex-github238-1114","created_at":"2026-11-14T09:09:34.220Z","source_kind":"local_mutation","source_ref":"fix/workflow-installs-own-release"}}]}],"before_hash":"814a2cc155d7d51f3d001dc3100ba748e513a6d2e7a4dcafdec2d58baf7416bc","after_hash":"6f8d3a185e8753f5dd3c4946d4c9742f6a8f4bdf1dff358db3a097da8137c8ae","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":"7d2bf10898c98f5f2799dba1047c2a3ea9f6ac003de8246e70a4138bb7220683"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:12:12.957Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:12:12.957Z","author":"codex-github238-1114","text":"Design verified against GitHub Actions job context documentation: job.workflow_sha identifies the reusable job commit for both tag and SHA pins. Both workflows fetch their own package.json at that SHA, validate and normalize its version, honor explicit exact holds, and warn with both versions on a mismatch. Exact public registry metadata is checked before checkout/setup or tracker writes. Live transport smoke: omitted input resolved 2026.11.14; explicit 2026.10.16 remained 2026.10.16 with both versions in the warning; 2099.1.1 returned a refusal with no exported install version. Migration is in the item description and README: callers should drop pm-github-version."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:12:12.957Z"}],"before_hash":"6f8d3a185e8753f5dd3c4946d4c9742f6a8f4bdf1dff358db3a097da8137c8ae","after_hash":"fb1de0ce4480ccd7ed1bb41b6ab24fba2af107d243eef85a4384926019bf806f","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":"e9b089c4f3d7dfe281a05c467f0c1e4f980ba903e0f146f78112ed23b955bf56"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:12:15.098Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:12:15.097Z","author":"codex-github238-1114","text":"Behavioral revert proofs ran in disposable copies, leaving the fixed checkout untouched. Reverting only default selection: 4/4 selected tests fail. Ignoring explicit holds: 2/2 fail. Removing the two-version warning: 2/2 fail. Removing registry preflight: 2/2 fail, including a real tracker mutation sentinel. Restoring the entire origin/main resolver: all 8 selected issue regressions fail (omitted, padded default, older hold, unavailable version across both workflows). Each refusal case snapshots every real tracker byte including native history; successes perform a real pinned-pm local fixture extension installation. Baseline focused run: 58/58; additional malformed metadata cases are in the linked final run."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:12:15.098Z"}],"before_hash":"fb1de0ce4480ccd7ed1bb41b6ab24fba2af107d243eef85a4384926019bf806f","after_hash":"a2344fcdd4246b52f3f5f1044e082a0f3c2bf59fff2b42238a79f66809b75069","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":"694aa1e470828cf7e7d8803f664b02b679e78e6cc71feb6bf9d94836593cff5b"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:15:57.371Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:15:57.370Z","author":"codex-github238-1114","text":"Caller inventory complete: 217 workflow files read via public contents API across 37 canonical fleet repository names, zero unavailable reads; 51 calls in 28 repositories still provide pm-github-version. docs/workflow-version-callers.md records every caller file, exact reusable workflow pin, pin comment, input version, and contents blob SHA. No callers changed. Existing caller-adoption follow-up pm-github-samq can carry the later migration sweep. Focused workflow-order checks passed 2/2; typecheck, build:test and lint passed; privacy audited 9288 Git objects without findings; docstrings checked 33 files and 315 declarations. actionlint is unavailable. No authored TypeScript/JavaScript source modules changed (34 inventoried); full CI retains unchanged 100/100/100/100 coverage thresholds. The first linked run reached its default 120-second deadline; retry uses 300 seconds. A request to disable linked pm context in the source workspace was correctly refused, so the retry retains schema isolation."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:15:57.371Z"}],"before_hash":"a2344fcdd4246b52f3f5f1044e082a0f3c2bf59fff2b42238a79f66809b75069","after_hash":"8aedf096869c7d3b26026954c210d0dda89179e048a5829972119744d77f3c3d","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":"e4df209281dc5a84b9108f296080856bfc0a3ccbde7f4d50fd5e776ffc27ea61"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:15:59.218Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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/files/2/path","value":"docs/scheduled-workflow-watch-1106.md"},{"op":"add","path":"/metadata/files/3","value":{"path":"test/self-versioned-workflows.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:15:59.218Z"}],"before_hash":"8aedf096869c7d3b26026954c210d0dda89179e048a5829972119744d77f3c3d","after_hash":"09995fce4ddbf90fc380dd8be9d6ac0b45163f325d2b3e75f27791f30595abd6","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":"a0cf0b7ece1baeb6be098c8011db313d375febed74c440399b5953ac01dadc66"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:19:21.792Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_update","patch":[{"op":"add","path":"/metadata/tests/0/timeout_seconds","value":600},{"op":"add","path":"/metadata/tests/0/updated_by","value":"codex-github238-1114"},{"op":"add","path":"/metadata/tests/0/updated_at","value":"2026-11-14T09:19:21.734Z"},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:19:21.792Z"},{"op":"add","path":"/metadata/pm_format_version","value":2}],"before_hash":"09995fce4ddbf90fc380dd8be9d6ac0b45163f325d2b3e75f27791f30595abd6","after_hash":"cb94477df4a7a10a860f83225df537f1cb1631fed4b61384ffb089710452eec3","item_hash_version":3,"context":{"linked_test_update":{"index":1,"fields":["timeout_seconds"]},"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":"7ea5c6094712adbca3c564244708068faff745eaf1ceb8e96fb058c6f5dd57df"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:19:26.407Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:19:26.367Z","author":"codex-github238-1114","text":"Final linked focused run passed 64/64 tests, zero failures/skips/cancellations, in 286.7 seconds using pm test --run --progress --timeout 300. This passing run followed the final disposable revert proofs (default 4 failures, hold 2, warning 2, existence 2, full old resolver 8). Linked-test timeout is now 600 seconds for slower shared execution; the first 120-second deadline was execution overhead, not an assertion failure. Additional workflow-order tests passed 2/2. npm run typecheck, npm run build:test, npm run lint, npm run privacy, npm run docstring and git diff --check passed; both changed workflows parse as YAML. actionlint was not installed. Full npm/Bun release and all-source coverage gates remain CI responsibilities under the targeted task brief. Remaining rollout work is caller pin adoption and removal of the 51 explicit holds; raw manifest/registry transport failure deliberately stops before writes. No pm SDK defect was observed; linked execution requires an explicit timeout for this real-installer test file."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:19:26.407Z"}],"before_hash":"cb94477df4a7a10a860f83225df537f1cb1631fed4b61384ffb089710452eec3","after_hash":"e421bfa3f5ccb8fef25d54d05b52b8e0ef862073c474a36404ac1cc52d237b0f","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":"97171dff35e29310b38ec98b333d8dc7a3e35ed46a043a6e03244a2c865b8e7e"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:19:32.474Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:19:32.474Z"}],"before_hash":"e421bfa3f5ccb8fef25d54d05b52b8e0ef862073c474a36404ac1cc52d237b0f","after_hash":"0486c6c40cfacafbb198eced14214d3842d613d8016de89b5d11233af040c664","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":"d54b6a3d7f087ce70c040483f72474e15502c7ed3f17d7f6e46cf2400eb26662"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:20:39.544Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:20:39.544Z"},{"op":"replace","path":"/metadata/title","value":"Default reusable workflows to their own release (migration: drop pm-github-version); warn on explicit holds"}],"before_hash":"0486c6c40cfacafbb198eced14214d3842d613d8016de89b5d11233af040c664","after_hash":"ae83130effab24743f525319578108b52f0df38a9e258be55f2a5124c0336dad","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":"8c63900e0c1c2d9b43e1691da95f63230e9fc033e2c6555726e1d33106f9cfb2"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:20:42.014Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:20:41.960Z","author":"codex-github238-1114","text":"Changelog generator renders item titles, so the migration instruction is also in this item title and is visible in release notes. Supplemental read-only strict health found one pre-existing local gate failure: pinned host pm-cli 2026.11.6 versus installed global pm-github host requirement 2026.11.13; tracker settings/directories/values/locks/integrity passed and seven older in-progress items are advisory. No dependency pin or global extension was changed. This environment condition is already recorded by the reference-identity work; clean CI owns the full gate. Changelog generation and check are rerun after this final tracker write."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:20:42.014Z"}],"before_hash":"ae83130effab24743f525319578108b52f0df38a9e258be55f2a5124c0336dad","after_hash":"de042ffa97f9e5a4d420449dc0a21d6a9e023fb0c3ef19dd3a9fd4f0c0a5bf54","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":"1f293b1e93f7804ef5d59584ff7427990172d1954338f8cd9d61124aa55a221c"} +{"hash_algorithm":"sha256","ts":"2026-11-14T09:50:05.388Z","author":"codex-github238-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b8a6e793d100f7f4487a97c2","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-11-14T09:50:05.380Z","author":"codex-github238-1114","text":"CI closeout at 2026-11-14 09:49 UTC after approximately 28 minutes of the 30-minute wait window: PR https://github.com/unbraind/pm-github/pull/245, implementation commit 0f58f1096c33a9fa3d0e9482d64c7a01fc23a16d. CI https://github.com/unbraind/pm-github/actions/runs/43580183069 still has test (22), test (26), bun-release (22), and bun-release (26) queued. CodeQL https://github.com/unbraind/pm-github/actions/runs/43580183044 completed successfully on that implementation head. Greptile and Semgrep checks succeeded; CodeRabbit/Sourcery skipped and cubic neutral are missing review approval. No review comments were returned and no threads were replied to, resolved or reacted to. This final evidence-only commit changes no implementation or test source; fresh final-head checks remain pending under the original wait cutoff. Once replacement checks register, cancel only the superseded queued CI run to avoid duplicate runner consumption. Orchestrator must verify the final-head full npm/Bun gates and unchanged 100/100/100/100 coverage; local strict-health environment skew remains recorded above. The item remains released and in_progress for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T09:50:05.388Z"}],"before_hash":"de042ffa97f9e5a4d420449dc0a21d6a9e023fb0c3ef19dd3a9fd4f0c0a5bf54","after_hash":"328481471bdcf2fadedebad9ef1d13e748a3d5ac934eb98319a34b13f66b2578","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":"1c80646f15a8cccc62b515db29a93e02aeb0e49cce1d2c1d37eb382a10965c52"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:19:06.752Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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-11-14T20:19:06.752Z"},{"op":"add","path":"/metadata/assignee","value":"codex-followups-1114"},{"op":"add","path":"/metadata/claim_principal","value":"codex-followups-1114"}],"before_hash":"328481471bdcf2fadedebad9ef1d13e748a3d5ac934eb98319a34b13f66b2578","after_hash":"dae95a68f22a6b35965acc962843983eebc6ff8d0b87e19b038313dd7bdab6c9","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":"2154c84968bbe6aa1f1bf387cd55da5f8a9faa056402d0d7e3b9220c85529d2f"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:19:11.394Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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-11-14T20:19:11.380Z","author":"codex-followups-1114","text":"Review follow-up for PR 245: the merged source shifted ownership anchors. Before regeneration, identity-inventory.test.ts reproduces four failures and one pass. Regenerate only with node scripts/identity-inventory.ts --write, run the complete inventory-related test files plus typecheck and lint, then carry the fix into PR 246. Targeted local gates follow the brief; full gates remain in CI."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:19:11.394Z"}],"before_hash":"dae95a68f22a6b35965acc962843983eebc6ff8d0b87e19b038313dd7bdab6c9","after_hash":"8223656ae053dd34e8dae1716471ed24210146c7ee592e61823a71091fd03094","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":"81daa0a4d94724dd14f5bd6e7b24de2ecafab116cc15a7fa944e00d0ab24b8f9"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:19:16.082Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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/files/3/path","value":"test/identity-inventory.test.ts"},{"op":"add","path":"/metadata/files/4","value":{"path":"test/self-versioned-workflows.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:19:16.082Z"}],"before_hash":"8223656ae053dd34e8dae1716471ed24210146c7ee592e61823a71091fd03094","after_hash":"b78f6462551f7ab78fa6b68cb794ac5a3ceaf28b5de37da4e973a1088c654ad1","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":"337d2decdf0b7f96c2e859a1262eaa1f53975c9a878e83110e0a21e3a21b6441"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:19:17.293Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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/docs/1/path","value":"docs/workflow-version-callers.md"},{"op":"replace","path":"/metadata/docs/0/path","value":"docs/identity-ownership-inventory.md"},{"op":"add","path":"/metadata/docs/2","value":{"path":"README.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:19:17.293Z"}],"before_hash":"b78f6462551f7ab78fa6b68cb794ac5a3ceaf28b5de37da4e973a1088c654ad1","after_hash":"acb0ce4a482955095aa9f58e1cfba67e1bc3b5321b54689896fdaee809962aeb","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":"5d6fdbcd3ae0a4e13dfdf9fd47c350775bb426f58735d420ba26f89b12f5f4d3"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:19:18.581Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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-concurrency=2 test/identity-inventory.test.ts test/round-twelve.test.ts","scope":"project","timeout_seconds":600,"provenance":{"author":"codex-followups-1114","created_at":"2026-11-14T20:19:18.462Z","source_kind":"local_mutation","source_ref":"fix/workflow-installs-own-release"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:19:18.581Z"}],"before_hash":"acb0ce4a482955095aa9f58e1cfba67e1bc3b5321b54689896fdaee809962aeb","after_hash":"29e6ac91e7918aa3457bcd7cdd76ba5565acfed2335e387ec3ebc31eaf6b0f3a","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":"52debde3df4790aa7cead5f7bac98fd05b9e45ae42df5439b036b3edd1f14a21"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:21:23.611Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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-11-14T20:21:23.562Z","author":"codex-followups-1114","text":"Generated fix changes one auxiliary workflow anchor from line 466 to 440. Before generation the inventory file caused 4/5 inventory tests to fail; after generation the complete test/identity-inventory.test.ts and test/round-twelve.test.ts run passes 37/37 with no skipped or failed tests via pinned pm test. npm run typecheck, npm run lint and npm run build:test pass. The existing stale-anchor and line-shift regressions remain intact; no generator or anchor hand edits. Full release certification remains in CI."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-14T20:21:23.611Z"}],"before_hash":"29e6ac91e7918aa3457bcd7cdd76ba5565acfed2335e387ec3ebc31eaf6b0f3a","after_hash":"3d94e535c763223f0b296456a976fe70035c61e4b7c1ddd2ff05cfad70fbf30c","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":"51a1351037e9a40bf764e2d8d4fc076f51d543567b27665e0e554240539c7bfd"} +{"hash_algorithm":"sha256","ts":"2026-11-14T20:21:27.457Z","author":"codex-followups-1114","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"3cfd5fadd87f9c45b3e054f8","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-11-14T20:21:27.457Z"}],"before_hash":"3d94e535c763223f0b296456a976fe70035c61e4b7c1ddd2ff05cfad70fbf30c","after_hash":"dcfc6886aff10bdbf2ff0cc315ce26a98e80b83977219ecf84000a99372dca1b","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":"9a03e26b920ad2ec2c558ee20a507374624c710aa25db08f9766201425683185"} diff --git a/.agents/pm/issues/pm-github-fxhd.toon b/.agents/pm/issues/pm-github-fxhd.toon index ad142a2c..3cca10ee 100644 --- a/.agents/pm/issues/pm-github-fxhd.toon +++ b/.agents/pm/issues/pm-github-fxhd.toon @@ -2,18 +2,13 @@ id: pm-github-fxhd title: The gated sync holds a runner for up to two hours while it waits for caller CI it competes with for the same pool description: "" type: Issue -status: closed +status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,fleet,"gh:unbraind/pm-github#228",sync created_at: "2026-11-09T19:25:14.594Z" -updated_at: "2026-11-12T15:00:54.479Z" -closed_at: "2026-11-12T15:00:54.460Z" -completed_at: "2026-11-12T15:00:54.460Z" +updated_at: "2026-11-14T20:26:32.212Z" author: codex-agent -resolution: Implemented and merged in #229 (6ccb293). -expected_result: The change described by this item is on main. -actual_result: "Merged in #229 as 6ccb293; checks successful at head 0428178: CodeQL, bun-release (22), bun-release (26), codeql, semgrep-cloud-platform/scan, test (22), test (26)." -comments[42]{created_at,author,text}: +comments[52]{created_at,author,text}: "2026-11-09T20:06:55.015Z",codex-agent,"Continuation: retained the existing uncommitted sync and closeout edits. No remote task branch or PR exists. Auditing all workflow_call jobs and the dispatch/landing contracts before focused verification." "2026-11-09T20:09:30.888Z",codex-agent,"Class audit: both reusable sync and closeout dispatch steps contained the long PR/ancestry monitor; removed both. Dependabot has bounded API retries with no run polling and already has a job timeout. Added 60-minute sync/closeout job limits and five-minute dispatch caps; sync alerts retain 10 minutes. Before gh --auto, one-time repository setting and protected-base reads prevent immediate unprotected landing. Unknown metadata exits with a reason. Retained App-only unattended merging, scoped tokens, owned PR checks, closeout attribution holds/revocation, publication gates and parked-run reporting. A later schedule rebuilds/retries; persistent configuration problems need a maintainer." "2026-11-09T20:11:34.716Z",codex-agent,"Revert proof in a disposable copy: replaced only the sync/closeout Dispatch caller CI and report parked runs blocks with HEAD baseline blocks, leaving current tests and job timeouts. node --test --test-concurrency=2 --test-name-pattern=\"armed auto-merge is requested exactly once|unavailable auto-merge reports|the dispatch step shell never sleeps|fallback without merging or polling\" test/bot-pr-workflow.test.ts exited 1: all 18 selected regressions failed (0 passed). Separately removed only the two new job timeout-minutes declarations with fixed dispatch: the every job in the reusable workflows contract exited 1 (1 failed). The package worktree stayed fixed throughout; the fixed focused contract suite passed 117/117 after these source changes. Build, build:test, typecheck and lint passed. No full release gate was run locally; CI owns it." @@ -56,7 +51,17 @@ comments[42]{created_at,author,text}: "2026-11-11T15:56:23.709Z",codex-agent,"Implementation: both caller templates now distinguish immediate App-created runs from per-new-run GITHUB_TOKEN approval by a maintainer or approval sweep. The automation guide now states REST classic contexts/checks plus every active branch-rules page, the any-nonempty-string presence rule, no caller-CI name matching, and refusal conditions; maintainers must require every intended CI check. Outside-diff fix: accept native freshness only from an active strict required-check ruleset, applicable merge queue, or readable matching classic GraphQL branchProtectionRule with requiresStatusChecks/requiresStrictStatusChecks true and classic enforcement not off. Refuse loose/unreadable freshness with typed notices, no polling. Live public pm-csv read confirmed classic strictness true via GraphQL while REST branch summary omits it. New base-advance contracts inspect real remote refs and retained release content, block a stale successful-check event under strict policy, rebuild on the advanced base, and land only after rebuilt checks pass. New real-fixture cleanup contract injects setup and teardown failures and proves every initialized tracker/server reaches cleanup before errors are reported. Bot plus self-versioned contracts passed 170/170; lint, typecheck and build:test passed. Full publication and decision partitions are running under separate 1200-second caps." "2026-11-11T16:08:43.336Z",codex-agent,"Behavioral revert proofs in disposable copies, leaving the source worktree fixed: (1) restoring only enable_auto_merge in both workflows to 2b8375a fails both loose-required-check contracts (2/2) on actual remote base movement after the native passing-check event; (2) removing only the native model freshness refusal fails both strict-base-advance contracts (2/2), demonstrating they inspect remote landing, not merely local checkout state; (3) restoring only releaseDecisionFixtures to serial teardown fails the new real-fixture cleanup regression (1/1) because initialized tracker roots remain when setup failure is reported, with the test finally still cleaning them to avoid hanging; (4) reverting each caller template independently fails its contract (1/1 each); (5) reverting only the automation guide fails its contract (1/1). Restored selection passes 7/7, zero skips/cancellations. Selection: timeout 1200s node --test --test-concurrency=1 --test-name-pattern=\"loose required checks refuse|strict required checks prevent|caller templates|automation guide|decision cleanup\" test/bot-pr-workflow.test.ts test/closeout-workflow-decisions.ts. Outside-diff guard regression uses the loose-check selection; PRRT_kwDOTbN4pM62PU5X uses the strict remote-base selection; PRRT_kwDOTbN4pM62PU5e uses decision cleanup. Full publication 49/49 in 795.031 seconds and decisions 187/187 in 713.877 seconds passed under their 1200-second caps. The existing linked 1140/1200-second deadlines retain measured margin. Final full bot and focused pinned-PM replay are running." "2026-11-11T16:10:58.133Z",codex-agent,"Final gate receipt for the 2b8375a review continuation: lint, typecheck and build:test passed. Requested files: bot-pr-workflow 109/109; self-versioned-workflows 61/61 (combined initial run 170/170, then final bot-only run 109/109 after publishing the fixture proposal ref); closeout-workflow-publication 49/49 in 795.031 seconds; closeout-workflow-decisions 187/187 in 713.877 seconds. Total requested-file tests 406/406, no failures/cancellations/skips. The pinned PM focused linked command also passed 7/7 in a disposable tracker with --run --progress and schema/source context (8.921 seconds, exit 0), keeping run artifacts out of the package tracker. Every gate had an explicit cap no longer than 1200 seconds. Revert receipts above identify the exact function/file mutations and observed behavioral assertions; restored focused selection passes 7/7. Thread mapping: PRRT_kwDOTbN4pM62PU5Q caller templates; PRRT_kwDOTbN4pM62PU5I exact discovery documentation; PRRT_kwDOTbN4pM62PU5X remote base/proposal freshness and scheduled rebuild; PRRT_kwDOTbN4pM62PU5e all-initialized-fixture cleanup. Outside-diff PRR_kwDOTbN4pM8AAAABXVuP0Q is fixed by the shared strict-check/merge-queue guard, typed refusal and real remote stale-landing regression. Full coverage/release gates remain CI-owned at https://github.com/unbraind/pm-github/pull/229/checks. No review replies, resolutions or reactions posted. Releasing the claim without closing, then regenerating the changelog and pushing this scoped round." -files[11]{path,scope}: + "2026-11-14T17:00:21.054Z",codex-github-nopoll-1114,"Based on PR #245 head while it remains open. Existing dispatch already exits without completion polling. Complete the contract with one shared bounded run-appearance confirmation window, informational timeout, PR/dispatched/parked URLs in summaries and reusable outputs, and workflow-shell regression/revert evidence. Preserve strict protected auto-merge, closeout review revocation, sync holds and reference ownership (#230). Other reusable workflows have no completion waits; the non-reusable release wait remains separately tracked by pm-github-sgyr." + "2026-11-14T17:08:16.530Z",codex-github-nopoll-1114,"Implemented additive reusable outputs and summary receipts with one shared 60-second appearance-only confirmation budget, at most 12 rounds, and ten-second hard-killed API calls started only while ten seconds remain. Queued runs count immediately; lookup failures/timeouts report without failing sync or revoking auto-merge. A separate receipt step preserves the existing dispatch no-wait contract. Closeout review holds still forbid arming, now retaining parked-run receipts. Historical completion loops restored independently from the parent of 6ccb293 in disposable copies each fail the all-reusable-step contract (1 failing test per loop); restoring fixed workflows passes. Focused workflow group passed 195/195. No authored TS/JS production source changed; all-source coverage thresholds remain 100/100/100/100 for CI certification as requested. actionlint is unavailable. Attestation gate passes with literal ten-second timeouts. The initial closeout replay lacked a built package; built locally and rerunning the safety cases." + "2026-11-14T17:09:45.866Z",codex-github-nopoll-1114,"Safety replay after build passed 4/4: prior armed request revoked before a later hold, parked-run lookup failure preserves the armed request, maintenance failure revokes, and workflow token bindings exclude installation/checkout persistence. typecheck, build:test, lint, privacy (9356 objects), docstrings (33 files / 315 declarations), and publish attestation passed. Runner-time expectation is below two minutes for the measured 44-second workload plus at most 60 seconds of registration discovery, versus the historical about 85-minute fleet average; not a newly measured fleet rollout. No caller changes beyond the #238 pin sweep. Final-head full npm/Bun release gates and unchanged four-dimensional 100-percent coverage are delegated to CI under the targeted task instructions." + "2026-11-14T17:10:35.313Z",codex-github-nopoll-1114,Native Bun focused receipt suite passed 17/17 with zero failures. Pinned pm test executed the same 17 Node regressions with --run --progress successfully. Initial claim of the terminal item was refused; reopened the existing #228 item and claimed it successfully rather than creating a duplicate. No new pm CLI/SDK defect found. + "2026-11-14T17:12:15.605Z",codex-github-nopoll-1114,"Implementation commit e0a83090f1a3963d8b7c1a1fc01064e278147379 pushed and PR https://github.com/unbraind/pm-github/pull/246 opened against main, based on still-open #245 head. At publication, CI https://github.com/unbraind/pm-github/actions/runs/43619767839 and CodeQL https://github.com/unbraind/pm-github/actions/runs/43619767884 are queued for that implementation head. Finish tracker writes and release the claim now, regenerate/check the changelog, then verify the resulting final head for at most 30 minutes; final CI status is reported separately without another tracker mutation. Shared-runner hold was checked before pushing and required no wait. Identity check printed only the required SteveBot noreply address. Leave this item in progress for orchestrator verification; no merge, release or review-thread interaction performed." + "2026-11-14T17:17:14.759Z",codex-github-nopoll-1114,"Read-only review inspection found a valid missing behavioral proof in review comment 4574827470: accelerated fixtures did not exercise shared deadline exhaustion or actual stuck-command termination. Add controlled-clock tests across three workflow selections and real GNU timeout cases, retaining partial URLs and refusing late lookups. Do not reply, resolve or react to the thread. Workflow implementation remains unchanged; record the fix commit/test mapping at final handoff." + "2026-11-14T17:19:57.082Z",codex-github-nopoll-1114,"Review thread PRRT_kwDOTbN4pM63g99s (comment 4574827470) now has behavioral coverage: controlled clock advances across ci.yml and codeql.yml to the shared deadline before extra.yml may start; already discovered CI URLs survive. Real GNU timeout kills a genuinely stuck lookup after ten seconds, and the receipt still exits successfully with partial URLs. All 4 selected Node budget tests and all 21 Bun receipt tests passed. In disposable copies, removing the admission guard fails each shared-deadline test, and changing the real timeout to one second fails each stuck-lookup test (four independent expected failures). No workflow implementation or approval/merge safety policy changed in this review fix. Thread remains open, with no reply/reaction/resolution." + "2026-11-14T17:21:10.122Z",codex-github-nopoll-1114,"Updated focused workflow group passed 199/199 through pinned pm test --run --progress (linked test 20), with zero failures/skips; native Bun receipt suite passed 21/21. typecheck, build:test and lint passed after adding the behavioral budget cases. CI and CodeQL for the prior head remain queued; the next push supersedes that head. Finish tracker writes before final-head CI inspection, preserve the original bounded CI cutoff, and leave PRRT_kwDOTbN4pM63g99s open for orchestrator response with the upcoming test-fix commit." + "2026-11-14T20:24:05.034Z",codex-followups-1114,"Review follow-up for PR 246: merge the fixed PR 245 branch, regenerate the ownership inventory with the repository CLI if this branch shifted more workflow lines, and run the same inventory-related test files and typecheck/lint gates. Preserve all no-runner-polling behavior. Full npm/Bun certification remains in CI." + "2026-11-14T20:26:31.165Z",codex-followups-1114,"Merged fixed PR 245 commit 4bd7c31. The inherited generated inventory still failed --check on this branch because its own workflow lines moved; repository generator --write changes the auxiliary anchor from 440 to 458 and --check now passes. The same complete inventory/round-twelve linked test command passes 37/37 with zero failures/skips; npm run typecheck, npm run build:test and npm run lint pass. Preserve both parents and merge this repair as one follow-up commit. No workflow implementation changed. Full npm/Bun release gates remain in CI." +files[14]{path,scope}: .github/workflows/pm-github-closeout.yml,project .github/workflows/pm-github-sync.yml,project docs/closeout-workflow-caller.yml,project @@ -64,11 +69,14 @@ files[11]{path,scope}: docs/sync-workflow-caller.yml,project README.md,project test/bot-pr-workflow.test.ts,project + test/ci-handoff-workflow.test.ts,project test/closeout-workflow-decisions.ts,project test/closeout-workflow-publication.ts,project test/closeout-workflow.test.ts,project test/helpers/closeout-workflow.ts,project -tests[18]: + test/identity-inventory.test.ts,project + test/import-gate.test.ts,project +tests[24]: - command: node --test --test-concurrency=2 test/bot-pr-workflow.test.ts test/self-versioned-workflows.test.ts test/workflow-rule-names.test.ts scope: project timeout_seconds: 180 @@ -212,11 +220,55 @@ tests[18]: created_at: "2026-11-11T15:58:51.264Z" source_kind: local_mutation source_ref: fix/sync-does-not-hold-runners -docs[5]{path,scope}: + - command: node --test --test-concurrency=2 test/ci-handoff-workflow.test.ts + scope: project + provenance: + author: codex-github-nopoll-1114 + created_at: "2026-11-14T17:06:25.987Z" + source_kind: local_mutation + source_ref: fix/no-ci-polling-on-runners + - command: node --test --test-concurrency=2 test/ci-handoff-workflow.test.ts test/bot-pr-workflow.test.ts test/self-versioned-workflows.test.ts test/workflow-rule-names.test.ts test/daily-sync-alert.test.ts + scope: project + provenance: + author: codex-github-nopoll-1114 + created_at: "2026-11-14T17:09:42.481Z" + source_kind: local_mutation + source_ref: fix/no-ci-polling-on-runners + - command: "NPM_CONFIG_USERCONFIG=/dev/null node --test --test-concurrency=2 --test-name-pattern=\"workflow credentials|earlier armed auto-merge|informational parked-run failure|previous auto-merge is revoked on workflow exit: maintenance\" test/import-gate.test.ts test/closeout-workflow.test.ts" + scope: project + provenance: + author: codex-github-nopoll-1114 + created_at: "2026-11-14T17:09:43.992Z" + source_kind: local_mutation + source_ref: fix/no-ci-polling-on-runners + - command: bun test --timeout 120000 test/ci-handoff-workflow.test.ts + scope: project + provenance: + author: codex-github-nopoll-1114 + created_at: "2026-11-14T17:10:33.847Z" + source_kind: local_mutation + source_ref: fix/no-ci-polling-on-runners + - command: "node --test --test-concurrency=2 --test-name-pattern=\"shared deadline|real timeout\" test/ci-handoff-workflow.test.ts" + scope: project + provenance: + author: codex-github-nopoll-1114 + created_at: "2026-11-14T17:19:57.911Z" + source_kind: local_mutation + source_ref: fix/no-ci-polling-on-runners + - command: node --test --test-concurrency=2 test/identity-inventory.test.ts test/round-twelve.test.ts + scope: project + timeout_seconds: 600 + provenance: + author: codex-followups-1114 + created_at: "2026-11-14T20:24:56.263Z" + source_kind: local_mutation + source_ref: fix/no-ci-polling-on-runners +docs[7]{path,scope}: docs/bot-pr-automation.md,project docs/closeout-workflow-caller.yml,project docs/explicit-closeout-attribution.md,project + docs/identity-ownership-inventory.md,project + docs/no-ci-polling-migration.md,project docs/sync-workflow-caller.yml,project README.md,project -close_reason: "Delivered by #229 \"Stop gated sync and closeout jobs from waiting for caller CI\" (merged as 6ccb293). Checks successful at the PR head 0428178: CodeQL, bun-release (22), bun-release (26), codeql, semgrep-cloud-platform/scan, test (22), test (26)." body: "Issue: https://github.com/unbraind/pm-github/issues/228. The dispatch step polls until it can land the PR (7200 s with github.token, 2400 s with an app token, over 120/40 attempts). The caller CI it waits for needs runners from the same free-tier account pool: between 10:00Z and 12:00Z on 2026-11-09, 77 fleet runs queued while 8 ran; a scheduled sync sat in that step from 10:27Z, holding a runner while waiting for CI that could not start. An automation job must never wait in-job for work that competes for the same runner pool.\n\nPlan: dispatch caller CI, arm GitHub native auto-merge on the owned PR, report parked runs and exit. When auto-merge cannot be armed (no required checks, loose or unreadable freshness policy, unreadable branch rules, disabled setting), print a notice with the reason; the next scheduled sync rebuilds and retries the PR. Persistent configuration problems require maintainer attention. Remove the whole polling class from both reusable workflows (sync and closeout), add timeout-minutes to every reusable job, add a contract test that fails on revert.\n\nNo-App mode also arms the protected request: approved native PR runs can satisfy required checks, as verified on public pm-csv#241. A maintainer must approve parked native runs; the App enables unattended runs and default-branch push events. Full closeout replay is linked as separately measured publication and decision files, with the original entry point retained for existing commands." diff --git a/.agents/pm/issues/pm-github-workflow-own-release.toon b/.agents/pm/issues/pm-github-workflow-own-release.toon new file mode 100644 index 00000000..8e9e2a6a --- /dev/null +++ b/.agents/pm/issues/pm-github-workflow-own-release.toon @@ -0,0 +1,51 @@ +id: pm-github-workflow-own-release +title: "Default reusable workflows to their own release (migration: drop pm-github-version); warn on explicit holds" +description: "Fix #238: derive the default extension from package.json at job.workflow_sha, preserve validated explicit holds with a two-version warning, and preflight registry existence before tracker writes. Migration: callers must drop pm-github-version so Dependabot workflow updates also select the extension version, including the curated identity fix in 2026.11.14." +type: Issue +pm_format_version: 2 +status: in_progress +priority: 1 +tags[3]: changelog-unreleased,"gh:unbraind/pm-github#238",workflows +created_at: "2026-11-14T09:04:23.981Z" +updated_at: "2026-11-14T20:21:27.457Z" +author: codex-github238-1114 +comments[9]{created_at,author,text}: + "2026-11-14T09:04:30.564Z",codex-github238-1114,"Plan: replace tag enumeration with a public package.json fetch at the reusable job workflow SHA, normalize exact numeric versions, honor explicit holds and warn with both versions, then check the selected exact registry release before any tracker write. Execute extracted workflow bash with real scratch pm trackers, cover the issue regression cases and behavioral reverts, document caller migration, and inventory public fleet callers through the contents API without changing them. Targeted gates follow the task brief; CI runs the full gate." + "2026-11-14T09:12:12.957Z",codex-github238-1114,"Design verified against GitHub Actions job context documentation: job.workflow_sha identifies the reusable job commit for both tag and SHA pins. Both workflows fetch their own package.json at that SHA, validate and normalize its version, honor explicit exact holds, and warn with both versions on a mismatch. Exact public registry metadata is checked before checkout/setup or tracker writes. Live transport smoke: omitted input resolved 2026.11.14; explicit 2026.10.16 remained 2026.10.16 with both versions in the warning; 2099.1.1 returned a refusal with no exported install version. Migration is in the item description and README: callers should drop pm-github-version." + "2026-11-14T09:12:15.097Z",codex-github238-1114,"Behavioral revert proofs ran in disposable copies, leaving the fixed checkout untouched. Reverting only default selection: 4/4 selected tests fail. Ignoring explicit holds: 2/2 fail. Removing the two-version warning: 2/2 fail. Removing registry preflight: 2/2 fail, including a real tracker mutation sentinel. Restoring the entire origin/main resolver: all 8 selected issue regressions fail (omitted, padded default, older hold, unavailable version across both workflows). Each refusal case snapshots every real tracker byte including native history; successes perform a real pinned-pm local fixture extension installation. Baseline focused run: 58/58; additional malformed metadata cases are in the linked final run." + "2026-11-14T09:15:57.370Z",codex-github238-1114,"Caller inventory complete: 217 workflow files read via public contents API across 37 canonical fleet repository names, zero unavailable reads; 51 calls in 28 repositories still provide pm-github-version. docs/workflow-version-callers.md records every caller file, exact reusable workflow pin, pin comment, input version, and contents blob SHA. No callers changed. Existing caller-adoption follow-up pm-github-samq can carry the later migration sweep. Focused workflow-order checks passed 2/2; typecheck, build:test and lint passed; privacy audited 9288 Git objects without findings; docstrings checked 33 files and 315 declarations. actionlint is unavailable. No authored TypeScript/JavaScript source modules changed (34 inventoried); full CI retains unchanged 100/100/100/100 coverage thresholds. The first linked run reached its default 120-second deadline; retry uses 300 seconds. A request to disable linked pm context in the source workspace was correctly refused, so the retry retains schema isolation." + "2026-11-14T09:19:26.367Z",codex-github238-1114,"Final linked focused run passed 64/64 tests, zero failures/skips/cancellations, in 286.7 seconds using pm test --run --progress --timeout 300. This passing run followed the final disposable revert proofs (default 4 failures, hold 2, warning 2, existence 2, full old resolver 8). Linked-test timeout is now 600 seconds for slower shared execution; the first 120-second deadline was execution overhead, not an assertion failure. Additional workflow-order tests passed 2/2. npm run typecheck, npm run build:test, npm run lint, npm run privacy, npm run docstring and git diff --check passed; both changed workflows parse as YAML. actionlint was not installed. Full npm/Bun release and all-source coverage gates remain CI responsibilities under the targeted task brief. Remaining rollout work is caller pin adoption and removal of the 51 explicit holds; raw manifest/registry transport failure deliberately stops before writes. No pm SDK defect was observed; linked execution requires an explicit timeout for this real-installer test file." + "2026-11-14T09:20:41.960Z",codex-github238-1114,"Changelog generator renders item titles, so the migration instruction is also in this item title and is visible in release notes. Supplemental read-only strict health found one pre-existing local gate failure: pinned host pm-cli 2026.11.6 versus installed global pm-github host requirement 2026.11.13; tracker settings/directories/values/locks/integrity passed and seven older in-progress items are advisory. No dependency pin or global extension was changed. This environment condition is already recorded by the reference-identity work; clean CI owns the full gate. Changelog generation and check are rerun after this final tracker write." + "2026-11-14T09:50:05.380Z",codex-github238-1114,"CI closeout at 2026-11-14 09:49 UTC after approximately 28 minutes of the 30-minute wait window: PR https://github.com/unbraind/pm-github/pull/245, implementation commit 0f58f1096c33a9fa3d0e9482d64c7a01fc23a16d. CI https://github.com/unbraind/pm-github/actions/runs/43580183069 still has test (22), test (26), bun-release (22), and bun-release (26) queued. CodeQL https://github.com/unbraind/pm-github/actions/runs/43580183044 completed successfully on that implementation head. Greptile and Semgrep checks succeeded; CodeRabbit/Sourcery skipped and cubic neutral are missing review approval. No review comments were returned and no threads were replied to, resolved or reacted to. This final evidence-only commit changes no implementation or test source; fresh final-head checks remain pending under the original wait cutoff. Once replacement checks register, cancel only the superseded queued CI run to avoid duplicate runner consumption. Orchestrator must verify the final-head full npm/Bun gates and unchanged 100/100/100/100 coverage; local strict-health environment skew remains recorded above. The item remains released and in_progress for orchestrator verification." + "2026-11-14T20:19:11.380Z",codex-followups-1114,"Review follow-up for PR 245: the merged source shifted ownership anchors. Before regeneration, identity-inventory.test.ts reproduces four failures and one pass. Regenerate only with node scripts/identity-inventory.ts --write, run the complete inventory-related test files plus typecheck and lint, then carry the fix into PR 246. Targeted local gates follow the brief; full gates remain in CI." + "2026-11-14T20:21:23.562Z",codex-followups-1114,"Generated fix changes one auxiliary workflow anchor from line 466 to 440. Before generation the inventory file caused 4/5 inventory tests to fail; after generation the complete test/identity-inventory.test.ts and test/round-twelve.test.ts run passes 37/37 with no skipped or failed tests via pinned pm test. npm run typecheck, npm run lint and npm run build:test pass. The existing stale-anchor and line-shift regressions remain intact; no generator or anchor hand edits. Full release certification remains in CI." +files[5]{path,scope}: + .github/workflows/pm-github-closeout.yml,project + .github/workflows/pm-github-sync.yml,project + docs/scheduled-workflow-watch-1106.md,project + test/identity-inventory.test.ts,project + test/self-versioned-workflows.test.ts,project +tests[2]: + - command: node --test --test-concurrency=2 test/self-versioned-workflows.test.ts + scope: project + timeout_seconds: 600 + provenance: + author: codex-github238-1114 + created_at: "2026-11-14T09:09:34.220Z" + source_kind: local_mutation + source_ref: fix/workflow-installs-own-release + updated_by: codex-github238-1114 + updated_at: "2026-11-14T09:19:21.734Z" + - command: node --test --test-concurrency=2 test/identity-inventory.test.ts test/round-twelve.test.ts + scope: project + timeout_seconds: 600 + provenance: + author: codex-followups-1114 + created_at: "2026-11-14T20:19:18.462Z" + source_kind: local_mutation + source_ref: fix/workflow-installs-own-release +docs[3]{path,scope}: + docs/identity-ownership-inventory.md,project + docs/workflow-version-callers.md,project + README.md,project +body: "" diff --git a/.github/workflows/pm-github-closeout.yml b/.github/workflows/pm-github-closeout.yml index 04c8dd73..3394fffe 100644 --- a/.github/workflows/pm-github-closeout.yml +++ b/.github/workflows/pm-github-closeout.yml @@ -12,7 +12,7 @@ on: required: false default: ${{ github.repository }} pm-github-version: - description: "Deprecated: fallback only for a non-release commit; pin a release SHA instead" + description: "Deprecated exact extension hold; omit to follow the workflow release (differing holds warn)" type: string required: false default: "" @@ -47,6 +47,20 @@ on: required: false default: "22.18.0" + outputs: + pr-url: + description: "Owned automation pull request URL" + value: ${{ jobs.gated-closeout.outputs.pr-url }} + ci-run-urls: + description: "JSON array of registered caller CI run URLs" + value: ${{ jobs.gated-closeout.outputs.ci-run-urls }} + parked-run-urls: + description: "JSON array of already approval-required run URLs" + value: ${{ jobs.gated-closeout.outputs.parked-run-urls }} + ci-confirmation: + description: "confirmed, timed_out, or disabled; never waits for completion" + value: ${{ jobs.gated-closeout.outputs.ci-confirmation }} + secrets: automation_app_private_key: description: "Private key for the optional automation App installed on the calling repository" @@ -72,6 +86,11 @@ jobs: # owned by GitHub's native auto-merge and the next scheduled closeout, so a # hung job must stop here instead of holding a shared fleet runner. timeout-minutes: 60 + outputs: + pr-url: ${{ steps.ci_handoff.outputs.pr-url }} + ci-run-urls: ${{ steps.ci_handoff.outputs.ci-run-urls }} + parked-run-urls: ${{ steps.ci_handoff.outputs.parked-run-urls }} + ci-confirmation: ${{ steps.ci_handoff.outputs.ci-confirmation }} env: REPOSITORY: ${{ inputs.repository }} SYNC_BRANCH: pm-github/closeout @@ -132,20 +151,13 @@ jobs: run: | set -euo pipefail [[ "${WORKFLOW_SHA}" =~ ^[a-f0-9]{40}$ ]] || { echo "::error::Cannot identify the reusable workflow commit from job.workflow_sha."; exit 1; } - # No checkout, authentication, API pagination, or local tags involved. - remote_tags="$(git ls-remote --tags https://github.com/unbraind/pm-github 'refs/tags/v*')" || { echo "::error::Cannot read pm-github release tags; refusing an unverified install."; exit 1; } - declare -A tag_commits=() peeled_commits=() - while read -r sha ref; do - [[ "${sha}" =~ ^[a-f0-9]{40}$ ]] || continue - # Validate the whole tag before using it as an associative-array key. - # declare evaluates subscripts, so arbitrary remote refs are not operands. - grep -zEq '^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?(\^\{\})?$' < <(printf '%s\0' "${ref}") || continue - if [[ "${ref}" == *'^{}' ]]; then - declare "peeled_commits[${ref%'^{}'}]=${sha}" - elif [[ "${ref}" == refs/tags/v* ]]; then - declare "tag_commits[${ref}]=${sha}" - fi - done <<< "${remote_tags}" + # job.workflow_sha belongs to this reusable job, even for a tag pin. + # Fetch our manifest at that immutable commit; the caller checkout and + # release-tag spelling are never version authorities. + manifest="$(curl --fail --silent --show-error --location --retry 2 --max-time 60 \ + "https://raw.githubusercontent.com/unbraind/pm-github/${WORKFLOW_SHA}/package.json")" || { echo "::error::Cannot read pm-github package.json at the reusable workflow commit."; exit 1; } + workflow_version="$(jq -ser 'select(length == 1) | .[0] | select(.name == "pm-github") | .version | select(type == "string" and (contains("\n") | not) and (contains("\r") | not))' <<< "${manifest}" 2>/dev/null)" || { echo "::error::Invalid pm-github workflow package metadata."; exit 1; } + grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${workflow_version}") || { echo "::error::Workflow package version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } # Release tags keep zero-padded date components (v2026.11.02) while the # daily release plan publishes npm versions without padding (2026.11.2), # so strip the padding from every component before comparing or installing. @@ -166,39 +178,20 @@ jobs: done printf '%s' "${normalized}${suffix:+-${suffix}}" } - PM_GITHUB_VERSION="" - for ref in "${!tag_commits[@]}"; do - # Annotated tags must match their peeled commit, never the tag object. - [[ "${peeled_commits[$ref]-${tag_commits[$ref]}}" == "${WORKFLOW_SHA}" ]] || continue - candidate="${ref#refs/tags/v}" - grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${candidate}") || continue - candidate="$(normalize_pm_github_version "${candidate}")" - if [[ -n "${PM_GITHUB_VERSION}" && "${PM_GITHUB_VERSION}" != "${candidate}" ]]; then - echo "::error::Multiple release versions tag this workflow commit; refusing an ambiguous install." - exit 1 - fi - PM_GITHUB_VERSION="${candidate}" - done - if [[ -z "${PM_GITHUB_VERSION}" ]]; then - if [[ -z "${REQUESTED_PM_GITHUB_VERSION}" ]]; then - echo "::error::No release tag matches this reusable workflow commit. Pin a release SHA (a commit tagged vN.N.N or vN.N.N-N)." - exit 1 - fi - echo "::warning::pm-github-version is deprecated; no release tag matches this workflow commit, so using the legacy input as a normalized version. Pin a release SHA instead." + workflow_version="$(normalize_pm_github_version "${workflow_version}")" + PM_GITHUB_VERSION="${workflow_version}" + if [[ -n "${REQUESTED_PM_GITHUB_VERSION}" ]]; then grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${REQUESTED_PM_GITHUB_VERSION}") || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } PM_GITHUB_VERSION="$(normalize_pm_github_version "${REQUESTED_PM_GITHUB_VERSION}")" - elif [[ -n "${REQUESTED_PM_GITHUB_VERSION}" ]]; then - requested="${REQUESTED_PM_GITHUB_VERSION}" - # An unparseable legacy input stays raw: it can never equal a validated - # tag version, so it only warns, and the arithmetic never sees it. - if grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${requested}"); then - requested="$(normalize_pm_github_version "${requested}")" - fi - if [[ "${requested}" != "${PM_GITHUB_VERSION}" ]]; then - echo "::warning::pm-github-version is deprecated and differs from the workflow release tag; using the tag version." + if [[ "${PM_GITHUB_VERSION}" != "${workflow_version}" ]]; then + echo "::warning::pm-github-version is deprecated: workflow ${workflow_version} installs extension ${PM_GITHUB_VERSION} as an explicit hold. Drop pm-github-version to follow the workflow release." fi fi - grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${PM_GITHUB_VERSION}") || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } + # Resolve existence before setup, extension installation, or tracker writes. + # Public registry reads require no npm configuration or credentials. + package="$(curl --fail --silent --show-error --location --retry 2 --max-time 60 \ + "https://registry.npmjs.org/pm-github/${PM_GITHUB_VERSION}")" || { echo "::error::Selected pm-github extension ${PM_GITHUB_VERSION} is unavailable; refusing tracker writes."; exit 1; } + jq -se --arg version "${PM_GITHUB_VERSION}" 'length == 1 and .[0].name == "pm-github" and .[0].version == $version' <<< "${package}" >/dev/null 2>&1 || { echo "::error::Registry metadata does not identify the selected pm-github version; refusing tracker writes."; exit 1; } printf 'PM_GITHUB_VERSION=%s\n' "${PM_GITHUB_VERSION}" >> "${GITHUB_ENV}" - name: Validate closeout inputs @@ -770,14 +763,18 @@ jobs: return 1 } head_sha="$(git rev-parse HEAD)" + printf 'DISPATCHED_CI_WORKFLOWS=%s\n' "${workflows[*]}" >> "${GITHUB_ENV}" + printf 'CI_DISPATCH_STARTED_AT=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${GITHUB_ENV}" dispatch_checks if [[ "${CLOSEOUT_REVIEW:-0}" != 0 ]]; then echo "::notice::Delivery attribution needs review; caller checks were dispatched and automatic landing is disabled." - exit 0 fi # Capture the arm status before any later command: a reported fallback # must not fail the step, and only an observed success may record it. - enable_auto_merge && auto_merge_armed=true || auto_merge_armed=false + auto_merge_armed=false + if [[ "${CLOSEOUT_REVIEW:-0}" == 0 ]]; then + enable_auto_merge && auto_merge_armed=true || auto_merge_armed=false + fi # The flag records an observed successful arm, never the intention: a # failed arm leaves any request armed earlier in this run revocable by # the always() cleanup instead of surviving it. @@ -789,6 +786,8 @@ jobs: parked_runs="" echo "::notice::Could not list approval-required runs; inspect the bot PR for workflows awaiting maintainer approval. Dispatch and auto-merge preparation are unchanged." fi + parked_json="$(jq -cn --arg urls "${parked_runs}" '$urls | split("\n") | map(select(length > 0))')" + printf 'PARKED_CI_RUN_URLS=%s\n' "${parked_json}" >> "${GITHUB_ENV}" while IFS= read -r run_url; do [[ -n "${run_url}" ]] || continue echo "::notice::A pull-request workflow requires maintainer approval: ${run_url}. Dispatched head checks do not satisfy PR branch rules; the optional automation App starts eligible native PR runs." @@ -802,6 +801,66 @@ jobs: # report reads runs GitHub already registered for the published head. echo "::notice::Landing stays outside this runner: required checks decide the armed auto-merge, and the next scheduled run retries when auto-merge cannot be armed or the base advanced. Unresolved conversations still require resolution." + - name: Confirm caller CI registration and summarize handoff + id: ci_handoff + if: env.SYNC_PUBLISHED == 'true' + timeout-minutes: 2 + env: + GH_TOKEN: ${{ steps.automation_token.outputs.token || github.token }} + shell: bash + run: | + set -euo pipefail + pr_url="${GITHUB_SERVER_URL:-https://github.com}/${REPOSITORY}/pull/${SYNC_PR_NUMBER}" + run_urls='[]' + confirmation=disabled + read -r -a pending <<< "${DISPATCHED_CI_WORKFLOWS:-}" + head_sha="$(git rev-parse HEAD)" + # One shared 60-second budget for all workflows, including API time. + # Only discover registration: queued, running and completed all count. + deadline=$((SECONDS + 60)) + for round in {1..12}; do + [[ ${#pending[@]} != 0 ]] || break + missing=() + for workflow_file in "${pending[@]}"; do + remaining=$((deadline - SECONDS)) + [[ ${remaining} -ge 10 ]] || break + if urls="$(timeout --signal=KILL 10s gh run list -R "${REPOSITORY}" \ + --workflow "${workflow_file}" --branch "${SYNC_BRANCH}" --commit "${head_sha}" \ + --event workflow_dispatch --created ">=${CI_DISPATCH_STARTED_AT}" \ + --limit 100 --json url --jq '.[].url')" && [[ -n "${urls}" ]]; then + run_urls="$(jq -cn --argjson previous "${run_urls}" --arg urls "${urls}" '$previous + ($urls | split("\n") | map(select(length > 0))) | unique')" + else + missing+=("${workflow_file}") + fi + done + if [[ ${remaining:-0} -lt 10 ]]; then break; fi + pending=("${missing[@]}") + [[ ${#pending[@]} != 0 ]] || break + remaining=$((deadline - SECONDS)) + [[ ${remaining} -gt 0 && ${round} -lt 12 ]] || break + pause=$((remaining < 5 ? remaining : 5)) + sleep "${pause}" + done + if [[ -n "${DISPATCHED_CI_WORKFLOWS:-}" ]]; then + confirmation=confirmed + if [[ ${#pending[@]} != 0 ]]; then + confirmation=timed_out + echo "::notice::Caller CI registration not confirmed within 60 seconds; inspect ${pr_url}. Sync remains successful; the next scheduled run retries." + fi + fi + parked_urls="${PARKED_CI_RUN_URLS:-[]}" + { + printf 'pr-url=%s\nci-run-urls=%s\nparked-run-urls=%s\nci-confirmation=%s\n' "${pr_url}" "${run_urls}" "${parked_urls}" "${confirmation}" + } >> "${GITHUB_OUTPUT}" + { + printf '### Caller CI handoff\n\nPR: %s\n\nRegistration: `%s` (appearance only; no completion wait).\n\n' "${pr_url}" "${confirmation}" + printf 'Registered caller CI runs:\n' + jq -r '.[] | "- " + .' <<< "${run_urls}" + printf '\nAlready parked as `action_required`:\n' + jq -r '.[] | "- " + .' <<< "${parked_urls}" + printf '\nRequired PR checks decide protected auto-merge. The hourly approval sweep or a maintainer handles parked native runs. The next scheduled run rebuilds and retries if landing is blocked.\n' + } >> "${GITHUB_STEP_SUMMARY}" + # Also revoke on dispatch/maintenance failure, cancellation, disabled CI, # no publication, or a hold. This runs even when an earlier step failed. - name: Disarm closeout auto-merge on unsuccessful landing diff --git a/.github/workflows/pm-github-sync.yml b/.github/workflows/pm-github-sync.yml index 58f5ec16..8c979b7a 100644 --- a/.github/workflows/pm-github-sync.yml +++ b/.github/workflows/pm-github-sync.yml @@ -29,7 +29,7 @@ on: required: false default: false pm-github-version: - description: "Deprecated: fallback only for a non-release commit; pin a release SHA instead" + description: "Deprecated exact extension hold; omit to follow the workflow release (differing holds warn)" type: string required: false default: "" @@ -64,6 +64,20 @@ on: required: false default: "22.18.0" + outputs: + pr-url: + description: "Owned automation pull request URL" + value: ${{ jobs.gated-sync.outputs.pr-url }} + ci-run-urls: + description: "JSON array of registered caller CI run URLs" + value: ${{ jobs.gated-sync.outputs.ci-run-urls }} + parked-run-urls: + description: "JSON array of already approval-required run URLs" + value: ${{ jobs.gated-sync.outputs.parked-run-urls }} + ci-confirmation: + description: "confirmed, timed_out, or disabled; never waits for completion" + value: ${{ jobs.gated-sync.outputs.ci-confirmation }} + secrets: automation_app_private_key: description: "Private key for the optional automation App installed on the calling repository" @@ -90,6 +104,10 @@ jobs: # job must stop here instead of holding a shared fleet runner. timeout-minutes: 60 outputs: + pr-url: ${{ steps.ci_handoff.outputs.pr-url }} + ci-run-urls: ${{ steps.ci_handoff.outputs.ci-run-urls }} + parked-run-urls: ${{ steps.ci_handoff.outputs.parked-run-urls }} + ci-confirmation: ${{ steps.ci_handoff.outputs.ci-confirmation }} failure-rules: ${{ steps.failure_rules.outputs.rules }} env: REPOSITORY: ${{ inputs.repository }} @@ -132,20 +150,13 @@ jobs: run: | set -euo pipefail [[ "${WORKFLOW_SHA}" =~ ^[a-f0-9]{40}$ ]] || { echo "::error::Cannot identify the reusable workflow commit from job.workflow_sha."; exit 1; } - # No checkout, authentication, API pagination, or local tags involved. - remote_tags="$(git ls-remote --tags https://github.com/unbraind/pm-github 'refs/tags/v*')" || { echo "::error::Cannot read pm-github release tags; refusing an unverified install."; exit 1; } - declare -A tag_commits=() peeled_commits=() - while read -r sha ref; do - [[ "${sha}" =~ ^[a-f0-9]{40}$ ]] || continue - # Validate the whole tag before using it as an associative-array key. - # declare evaluates subscripts, so arbitrary remote refs are not operands. - grep -zEq '^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?(\^\{\})?$' < <(printf '%s\0' "${ref}") || continue - if [[ "${ref}" == *'^{}' ]]; then - declare "peeled_commits[${ref%'^{}'}]=${sha}" - elif [[ "${ref}" == refs/tags/v* ]]; then - declare "tag_commits[${ref}]=${sha}" - fi - done <<< "${remote_tags}" + # job.workflow_sha belongs to this reusable job, even for a tag pin. + # Fetch our manifest at that immutable commit; the caller checkout and + # release-tag spelling are never version authorities. + manifest="$(curl --fail --silent --show-error --location --retry 2 --max-time 60 \ + "https://raw.githubusercontent.com/unbraind/pm-github/${WORKFLOW_SHA}/package.json")" || { echo "::error::Cannot read pm-github package.json at the reusable workflow commit."; exit 1; } + workflow_version="$(jq -ser 'select(length == 1) | .[0] | select(.name == "pm-github") | .version | select(type == "string" and (contains("\n") | not) and (contains("\r") | not))' <<< "${manifest}" 2>/dev/null)" || { echo "::error::Invalid pm-github workflow package metadata."; exit 1; } + grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${workflow_version}") || { echo "::error::Workflow package version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } # Release tags keep zero-padded date components (v2026.11.02) while the # daily release plan publishes npm versions without padding (2026.11.2), # so strip the padding from every component before comparing or installing. @@ -166,39 +177,20 @@ jobs: done printf '%s' "${normalized}${suffix:+-${suffix}}" } - PM_GITHUB_VERSION="" - for ref in "${!tag_commits[@]}"; do - # Annotated tags must match their peeled commit, never the tag object. - [[ "${peeled_commits[$ref]-${tag_commits[$ref]}}" == "${WORKFLOW_SHA}" ]] || continue - candidate="${ref#refs/tags/v}" - grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${candidate}") || continue - candidate="$(normalize_pm_github_version "${candidate}")" - if [[ -n "${PM_GITHUB_VERSION}" && "${PM_GITHUB_VERSION}" != "${candidate}" ]]; then - echo "::error::Multiple release versions tag this workflow commit; refusing an ambiguous install." - exit 1 - fi - PM_GITHUB_VERSION="${candidate}" - done - if [[ -z "${PM_GITHUB_VERSION}" ]]; then - if [[ -z "${REQUESTED_PM_GITHUB_VERSION}" ]]; then - echo "::error::No release tag matches this reusable workflow commit. Pin a release SHA (a commit tagged vN.N.N or vN.N.N-N)." - exit 1 - fi - echo "::warning::pm-github-version is deprecated; no release tag matches this workflow commit, so using the legacy input as a normalized version. Pin a release SHA instead." + workflow_version="$(normalize_pm_github_version "${workflow_version}")" + PM_GITHUB_VERSION="${workflow_version}" + if [[ -n "${REQUESTED_PM_GITHUB_VERSION}" ]]; then grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${REQUESTED_PM_GITHUB_VERSION}") || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } PM_GITHUB_VERSION="$(normalize_pm_github_version "${REQUESTED_PM_GITHUB_VERSION}")" - elif [[ -n "${REQUESTED_PM_GITHUB_VERSION}" ]]; then - requested="${REQUESTED_PM_GITHUB_VERSION}" - # An unparseable legacy input stays raw: it can never equal a validated - # tag version, so it only warns, and the arithmetic never sees it. - if grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${requested}"); then - requested="$(normalize_pm_github_version "${requested}")" - fi - if [[ "${requested}" != "${PM_GITHUB_VERSION}" ]]; then - echo "::warning::pm-github-version is deprecated and differs from the workflow release tag; using the tag version." + if [[ "${PM_GITHUB_VERSION}" != "${workflow_version}" ]]; then + echo "::warning::pm-github-version is deprecated: workflow ${workflow_version} installs extension ${PM_GITHUB_VERSION} as an explicit hold. Drop pm-github-version to follow the workflow release." fi fi - grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${PM_GITHUB_VERSION}") || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } + # Resolve existence before setup, extension installation, or tracker writes. + # Public registry reads require no npm configuration or credentials. + package="$(curl --fail --silent --show-error --location --retry 2 --max-time 60 \ + "https://registry.npmjs.org/pm-github/${PM_GITHUB_VERSION}")" || { echo "::error::Selected pm-github extension ${PM_GITHUB_VERSION} is unavailable; refusing tracker writes."; exit 1; } + jq -se --arg version "${PM_GITHUB_VERSION}" 'length == 1 and .[0].name == "pm-github" and .[0].version == $version' <<< "${package}" >/dev/null 2>&1 || { echo "::error::Registry metadata does not identify the selected pm-github version; refusing tracker writes."; exit 1; } printf 'PM_GITHUB_VERSION=%s\n' "${PM_GITHUB_VERSION}" >> "${GITHUB_ENV}" - name: Validate sync inputs @@ -803,6 +795,8 @@ jobs: return 1 } head_sha="$(git rev-parse HEAD)" + printf 'DISPATCHED_CI_WORKFLOWS=%s\n' "${workflows[*]}" >> "${GITHUB_ENV}" + printf 'CI_DISPATCH_STARTED_AT=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${GITHUB_ENV}" dispatch_checks # Capture the arm status before any later command: a reported fallback # must not fail the step, and only an observed success may record it. @@ -812,6 +806,8 @@ jobs: parked_runs="" echo "::notice::Could not list approval-required runs; inspect the bot PR for workflows awaiting maintainer approval. Dispatch and auto-merge preparation are unchanged." fi + parked_json="$(jq -cn --arg urls "${parked_runs}" '$urls | split("\n") | map(select(length > 0))')" + printf 'PARKED_CI_RUN_URLS=%s\n' "${parked_json}" >> "${GITHUB_ENV}" while IFS= read -r run_url; do [[ -n "${run_url}" ]] || continue echo "::notice::A pull-request workflow requires maintainer approval: ${run_url}. Dispatched head checks do not satisfy PR branch rules; the optional automation App starts eligible native PR runs." @@ -825,6 +821,66 @@ jobs: # report reads runs GitHub already registered for the published head. echo "::notice::Landing stays outside this runner: required checks decide the armed auto-merge, and the next scheduled run retries when auto-merge cannot be armed or the base advanced. Unresolved conversations still require resolution." + - name: Confirm caller CI registration and summarize handoff + id: ci_handoff + if: env.SYNC_PUBLISHED == 'true' + timeout-minutes: 2 + env: + GH_TOKEN: ${{ steps.automation_token.outputs.token || github.token }} + shell: bash + run: | + set -euo pipefail + pr_url="${GITHUB_SERVER_URL:-https://github.com}/${REPOSITORY}/pull/${SYNC_PR_NUMBER}" + run_urls='[]' + confirmation=disabled + read -r -a pending <<< "${DISPATCHED_CI_WORKFLOWS:-}" + head_sha="$(git rev-parse HEAD)" + # One shared 60-second budget for all workflows, including API time. + # Only discover registration: queued, running and completed all count. + deadline=$((SECONDS + 60)) + for round in {1..12}; do + [[ ${#pending[@]} != 0 ]] || break + missing=() + for workflow_file in "${pending[@]}"; do + remaining=$((deadline - SECONDS)) + [[ ${remaining} -ge 10 ]] || break + if urls="$(timeout --signal=KILL 10s gh run list -R "${REPOSITORY}" \ + --workflow "${workflow_file}" --branch "${SYNC_BRANCH}" --commit "${head_sha}" \ + --event workflow_dispatch --created ">=${CI_DISPATCH_STARTED_AT}" \ + --limit 100 --json url --jq '.[].url')" && [[ -n "${urls}" ]]; then + run_urls="$(jq -cn --argjson previous "${run_urls}" --arg urls "${urls}" '$previous + ($urls | split("\n") | map(select(length > 0))) | unique')" + else + missing+=("${workflow_file}") + fi + done + if [[ ${remaining:-0} -lt 10 ]]; then break; fi + pending=("${missing[@]}") + [[ ${#pending[@]} != 0 ]] || break + remaining=$((deadline - SECONDS)) + [[ ${remaining} -gt 0 && ${round} -lt 12 ]] || break + pause=$((remaining < 5 ? remaining : 5)) + sleep "${pause}" + done + if [[ -n "${DISPATCHED_CI_WORKFLOWS:-}" ]]; then + confirmation=confirmed + if [[ ${#pending[@]} != 0 ]]; then + confirmation=timed_out + echo "::notice::Caller CI registration not confirmed within 60 seconds; inspect ${pr_url}. Sync remains successful; the next scheduled run retries." + fi + fi + parked_urls="${PARKED_CI_RUN_URLS:-[]}" + { + printf 'pr-url=%s\nci-run-urls=%s\nparked-run-urls=%s\nci-confirmation=%s\n' "${pr_url}" "${run_urls}" "${parked_urls}" "${confirmation}" + } >> "${GITHUB_OUTPUT}" + { + printf '### Caller CI handoff\n\nPR: %s\n\nRegistration: `%s` (appearance only; no completion wait).\n\n' "${pr_url}" "${confirmation}" + printf 'Registered caller CI runs:\n' + jq -r '.[] | "- " + .' <<< "${run_urls}" + printf '\nAlready parked as `action_required`:\n' + jq -r '.[] | "- " + .' <<< "${parked_urls}" + printf '\nRequired PR checks decide protected auto-merge. The hourly approval sweep or a maintainer handles parked native runs. The next scheduled run rebuilds and retries if landing is blocked.\n' + } >> "${GITHUB_STEP_SUMMARY}" + - name: Record safe failure rules id: failure_rules if: always() diff --git a/CHANGELOG.md b/CHANGELOG.md index 502f9c17..714b50d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Fixed + +- The gated sync holds a runner for up to two hours while it waits for caller CI it competes with for the same pool ([pm-github-fxhd](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-fxhd.toon)) +- Default reusable workflows to their own release (migration: drop pm-github-version); warn on explicit holds ([pm-github-workflow-own-release](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-workflow-own-release.toon)) + ### Other - Close delivered pm items and reserve in progress for active work (2026-10-24) ([pm-github-rv9m](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-rv9m.toon)) @@ -15,10 +20,6 @@ ## 2026.11.12 - 2026-11-12 -### Fixed - -- The gated sync holds a runner for up to two hours while it waits for caller CI it competes with for the same pool ([pm-github-fxhd](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-fxhd.toon)) - ### Dependencies - Bump jscpd ([#232](https://github.com/unbraind/pm-github/pull/232)) diff --git a/README.md b/README.md index c04385c5..084670fe 100644 --- a/README.md +++ b/README.md @@ -984,22 +984,37 @@ workflow. `github.job_workflow_sha` is not a documented context property; never used to discover the reusable workflow's version. These workflow identity context properties are not available on GitHub Enterprise Server. -The resolver uses public `git ls-remote --tags https://github.com/unbraind/pm-github -'refs/tags/v*'`. It matches lightweight tags directly and annotated tags by their -peeled `^{}` commit (never the tag object), strips `v`, and requires an exact -numeric version `N.N.N` or `N.N.N-N`. It reads all matching refs in one request, -without API pagination or new credentials. Lookup errors and multiple release -versions on one commit fail closed. Release version discovery needs no GitHub App, and no release-time workflow -stamping is required. The release workflow keeps its existing token behavior. - -Pin **release commit SHAs**, with their matching tag in a same-line comment -(for example `uses: unbraind/pm-github/.github/workflows/pm-github-sync.yml@<40-character-release-sha> # v2026.10.16`). -New callers omit `pm-github-version`. If no release tag matches the workflow -commit, the job fails with instructions to pin a release SHA. For compatibility, -a nonempty deprecated input provides an exact-version fallback with a warning -only when no tag matches. If a tag matches and the input differs, the job warns -and uses the tag version. Untrusted input is passed through `env:`, never inserted -into shell source or warning text. No `latest`, range, or floating install tag is used. +The resolver fetches public `package.json` from +`https://raw.githubusercontent.com/unbraind/pm-github//package.json`. +It validates the package name and exact numeric version `N.N.N` or `N.N.N-N`, +then strips leading zeros from date components as the daily release plan does +(for example `v2026.11.04` publishes `2026.11.4`). The SHA is resolved by GitHub +for both SHA and tag pins; no ref string parsing, local tags, mutable branch, +or caller package metadata participates. Missing or invalid workflow metadata +fails closed even with an explicit input. Release version discovery needs no +GitHub App, and no release-time workflow stamping is required. The release +workflow keeps its existing token behavior. + +**Migration:** remove `pm-github-version` from existing callers in the same change +that moves their workflow pin; a pin update alone keeps a stale input as an +explicit hold. Pin the workflow +release SHA and keep its same-line release tag comment for Dependabot. With the +input omitted, a workflow pin update also updates the installed extension, +including the curated import identity fix released in `2026.11.14`. +An explicit exact version remains a deliberate hold: a differing value is +honored and emits `::warning::` naming both the workflow and extension versions. +Both values are validated and normalized before comparison or diagnostic output. +No `latest`, range, or floating install tag is accepted. + +Before checkout/setup can write any tracker data, the resolver verifies the +selected extension using the public exact-version npm registry endpoint +`https://registry.npmjs.org/pm-github/` and checks its name and version. +A missing release, network error, or invalid response stops the job before +tracker writes. Installation still verifies the installed version independently. +An untagged development commit uses its own manifest version and therefore +requires that exact version to be published; an explicit published hold may +be used for development. See [the caller inventory](docs/workflow-version-callers.md) +for the follow-up migration sweep; this change does not edit callers. GitHub documents [reusable-workflow updates](https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/secure-your-dependencies/auto-update-actions) and [SHA pins and same-line version comments](https://docs.github.com/en/code-security/reference/supply-chain-security/supported-ecosystems-and-repositories#github-actions). @@ -1166,10 +1181,21 @@ those follow-up workflows must run automatically. GitHub documents the [PR approval exception and suppressed push events](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow). GitHub enforces required checks and conversation resolution. **Never wait in-job for caller CI: it takes runners from the same account pool**, so polling here can -only lengthen the queue it waits on. The dispatch step performs no in-job wait — -no contract requires one: the parked-run report reads runs GitHub already -registered for the published head, and landing is owned by armed auto-merge or -a later scheduled retry. The closeout armed flag records only an observed +only lengthen the queue it waits on. After dispatch and auto-merge preparation, a separate receipt step confirms only +that each selected workflow run has appeared. It shares a 60-second budget +across all selected workflows (including API time), with at most 12 discovery +rounds. Queued or running runs count immediately; no check conclusion is read. +The query matches the workflow, branch, published head, dispatch event and +dispatch start time, so an older run for the same head cannot confirm a new +dispatch. Timeout or API failure emits a notice and keeps the sync successful. +The job summary lists the PR URL, registered CI run URLs and any runs already +parked as `action_required`. The reusable outputs are `pr-url`, `ci-run-urls` +and `parked-run-urls` (both run lists are JSON arrays), and `ci-confirmation` +(`confirmed`, `timed_out`, or `disabled`). The hourly approval sweep or a +maintainer approves parked native runs; protected native auto-merge or a later +scheduled retry owns landing. Callers need only the workflow pin bump already +covered by the #238 sweep; no input or permission change is required. See the +[migration notes](docs/no-ci-polling-migration.md). The closeout armed flag records only an observed successful arm, so a failed arm stays revocable by the always() cleanup. A base advance after publication, or an unavailable auto-merge, waits for the next scheduled rebuild, which force-rebuilds the bot branch from the current default branch and re-arms. The existing daily schedules @@ -1177,7 +1203,9 @@ and separate sync/closeout concurrency groups remain. Unresolved conversations continue to block landing. Every job in the reusable workflows declares an explicit `timeout-minutes` (60 for sync, closeout and Dependabot; 10 for sync alerts), and each sync/closeout dispatch step has a five-minute hard limit. -That limit covers API failures or hangs, not a wait for checks. +That limit covers dispatch API failures or hangs. The separate registration +receipt step has a two-minute hard limit and its own 60-second discovery budget; +neither step waits for checks to finish. See [the caller audit and one-time setup](docs/bot-pr-automation.md) for the App credential mapping, CodeQL trigger changes and explicit CI overrides. GitHub diff --git a/docs/bot-pr-automation.md b/docs/bot-pr-automation.md index 2f8b78ce..ce67ab83 100644 --- a/docs/bot-pr-automation.md +++ b/docs/bot-pr-automation.md @@ -194,7 +194,9 @@ the queued merge against the latest base; configure caller checks for merge-grou validation when selecting that policy. An unavailable auto-merge also waits for the next scheduled retry. See GitHub's [strict checks and merge queue rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches). Sync and closeout jobs have 60-minute timeouts; -their dispatch steps have five-minute timeouts and no polling. Sync alerts use +their dispatch steps have five-minute timeouts and no completion polling. +The following receipt steps have two-minute timeouts and confirm run appearance +within one shared 60-second budget; see the [migration notes](no-ci-polling-migration.md). Sync alerts use 10 minutes. The Dependabot reusable job already had a 60-minute timeout; its bounded API retries do not wait for caller runs. The non-reusable release workflow is outside this caller-workflow change. @@ -225,7 +227,8 @@ and closeout suites retain privacy, scope, ownership, lease, no-op and attributi revocation boundaries. The approval-required run listing is informational. Both dispatch steps query it -once after auto-merge preparation; if the lookup fails, they print a notice to +once after auto-merge preparation; the receipt step exports that snapshot without +retrying the parked-run lookup. If the lookup fails, they print a notice to inspect the bot PR and exit successfully without retrying or revoking auto-merge. Successful lookups still report each parked run URL. Dispatch failures continue to fail the job before auto-merge can be armed. diff --git a/docs/identity-ownership-inventory.md b/docs/identity-ownership-inventory.md index 011cc706..37a6dfc5 100644 --- a/docs/identity-ownership-inventory.md +++ b/docs/identity-ownership-inventory.md @@ -123,4 +123,4 @@ The identity-field/tag scan finds 5 auxiliary locations. The inventory generator | `scripts/dependabot-merge.ts:145` | Explicit GitHub CLI transport parameter; targets come from repository/PR arguments. | | `scripts/prove-gate-public-forms.ts:4` | Privacy-gate documentation reference; no target lookup. | | `.github/workflows/pm-github-sync.yml:27` | Importer option documentation or typed diagnostic rendering; no independent target lookup. | -| `.github/workflows/pm-github-sync.yml:466` | Importer option documentation or typed diagnostic rendering; no independent target lookup. | +| `.github/workflows/pm-github-sync.yml:458` | Importer option documentation or typed diagnostic rendering; no independent target lookup. | diff --git a/docs/no-ci-polling-migration.md b/docs/no-ci-polling-migration.md new file mode 100644 index 00000000..d60818e2 --- /dev/null +++ b/docs/no-ci-polling-migration.md @@ -0,0 +1,75 @@ +# Caller CI handoff migration + +Sync and closeout must release their runner before caller CI finishes. The +measured historical fleet cost was about 85 runner-minutes per run; one sync +spent 44 seconds doing useful work and then 120 minutes polling caller CI. +The #228/#229 implementation already replaced the completion loop with protected +native auto-merge. This change completes registration receipts and caller outputs. + +Bump the reusable workflow pin through the existing #238 fleet sweep. No caller +input, secret, permission, trigger or schedule change is required by this change. +The optional App and existing GITHUB_TOKEN approval paths continue to work. + +After publishing the owned PR, dispatch all selected caller workflows for its +head. Capture the dispatch start before dispatching. Request +`gh pr merge --auto --squash --match-head-commit` only through the existing +verified repository, required-check and strict-freshness policy. Closeout review +holds still forbid arming and keep the always-run revocation safeguard. + +A separate step then discovers the dispatched runs, matching workflow, branch, +head SHA, `workflow_dispatch` and creation at or after dispatch start. It accepts +any registered run regardless of status or conclusion. All selected workflows +share 60 seconds, including API calls, at most 12 rounds and at most five seconds +between rounds; each call is capped by the remaining budget and ten seconds. +Already discovered workflows are removed from subsequent discovery rounds. +A missing registration, denied lookup or CLI timeout reports `timed_out` and +exits successfully, retaining any URLs already found. It never disables an +armed auto-merge request. Genuine dispatch failures still fail before arming. + +The job summary includes the PR URL, registered run URLs and the snapshot of +runs already parked as `action_required`. Additive reusable outputs are: + +| Output | Format | +| --- | --- | +| `pr-url` | Owned bot PR URL | +| `ci-run-urls` | JSON array of registered caller run URLs | +| `parked-run-urls` | JSON array from the single parked-run lookup | +| `ci-confirmation` | `confirmed`, `timed_out`, or `disabled` | + +No published PR means the receipt step is skipped and outputs are empty. +Explicitly disabled CI reports the PR with empty arrays and `disabled`. +Confirmation is evidence of registration, never proof that CI passed. Runs +parked after the snapshot are handled by the existing hourly approval sweep +or a maintainer; no runner remains assigned to observe them. Required PR checks +control native auto-merge. Missing/loose/unreadable protection or unavailable +auto-merge uses the next scheduled rebuild and retry. An advanced base must be +rebuilt by that scheduled run, or validated by GitHub's configured merge queue. +There is no later in-job step that depends on caller CI completion. + +The expected ordinary sync/closeout duration remains under two minutes when +the measured 44-second workload is representative: registration usually appears +on the first lookup, and its additional wait budget is at most 60 seconds. +This is a budget and expectation, not a new live fleet timing measurement. +Installation, import size and API latency can increase total job duration. + +Gated import, idempotency, privacy/redaction, sync holds and #230 ownership +selection are unchanged. Other reusable workflows were searched: Dependabot +uses bounded API retries and native auto-merge, with no completion wait. The +separate non-reusable release workflow has a durable publication continuation +requirement tracked by `pm-github-sgyr`; its release-PR wait is outside this +reusable-workflow change. + +`test/ci-handoff-workflow.test.ts` executes the actual receipt shell with a real +Git head and synthetic GitHub responses. It covers immediate/queued, delayed, +absent, partial, denied and timed-out registration; summaries and workflow/job +output wiring; and disabled dispatch. A contract scans every reusable step to +reject completion waits while permitting only the bounded appearance loop. +Existing workflow tests keep the protected merge, hold, ownership and revocation +checks. Restoring the historical completion-wait shell must fail that contract. + +Controlled-clock cases advance elapsed time across multiple workflow lookups to +exhaust the shared deadline, retain a discovered URL and prevent the next lookup +from starting. Stuck-command cases use real GNU `timeout` to kill a ten-second +lookup, then verify successful informational reporting and retained partial URLs. +Removing the deadline admission guard or shortening the real timeout independently +fails these behavioral regressions in disposable copies. diff --git a/docs/scheduled-workflow-watch-1106.md b/docs/scheduled-workflow-watch-1106.md index 0bc934a3..8eea56bf 100644 --- a/docs/scheduled-workflow-watch-1106.md +++ b/docs/scheduled-workflow-watch-1106.md @@ -21,9 +21,12 @@ about a later default branch or rerun. No caller repository was modified. | Planned source content fails privacy scan | Sync: pm-jev. | [job](https://github.com/unbraind/pm-jev/actions/runs/42207291810/job/127857017395), annotation: item `pm-jev-admission-teardown`, field `body`, rule `absolute-host-path`. | Caller-owned source remediation or reviewed content-addressed allowlist. Latest package removes one historical finding but retains another; no matched values are published here. | | Closeout hides the command's actual stderr | Closeout: pm-brief, pm-changelog, pm-context, pm-csv, pm-gantt-chart, pm-jira, pm-linear, pm-rl, pm-slack, pm-slack-standup, pm-vcs. | [pm-slack job](https://github.com/unbraind/pm-slack/actions/runs/42210316609/job/127867049216), [pm-context job](https://github.com/unbraind/pm-context/actions/runs/42210348997/job/127867158497). Both expose only the generic token/validation message. A pm-slack read-only local replay reveals `Unverifiable GitHub pagination.` | These pins precede numeric repository pagination and actionable diagnostic fixes. Adopt 2026.11.4 or later. All eleven callers currently return a numeric next-page URL on the first 100-PR REST page, which their pinned guard rejects. Historical hidden stderr is unavailable; later required-check or close-validation refusals remain independently actionable. This PR also handles plain host stderr and redacts candidate diagnostics before the existing final privacy scan. | -The deprecated `pm-github-version` input does **not** override a release-tagged -workflow SHA. Removing a stale input removes a warning; it does not repair any -of the failures above. No ambiguity error was observed in the sampled install +At the time of this watch, the deprecated `pm-github-version` input did **not** +override a release-tagged workflow SHA. Issue #238 subsequently changes this +contract: an explicit hold is honored with a two-version warning; callers should +remove the input to follow the workflow manifest version. For the historical +runs above, removing a stale input only removed a warning; it did not repair +those failures. No ambiguity error was observed in the sampled install failures: the failure occurred after installation in the exact-version verifier. ## Disposable public-data replays diff --git a/docs/workflow-version-callers.md b/docs/workflow-version-callers.md new file mode 100644 index 00000000..b5be783b --- /dev/null +++ b/docs/workflow-version-callers.md @@ -0,0 +1,120 @@ +# Fleet workflow extension holds + +Public default-branch contents API scan completed: 2026-11-14 09:14 UTC. +Scanned 37 canonical fleet repository names and read 217 workflow files through `gh api repos/unbraind//contents/.github/workflows/`. Private repositories are excluded. No caller was edited. + +Migration: in each caller, move the workflow pin to the release containing #238 **and remove `pm-github-version` in the same change**. A pin update alone keeps every stale input below as an explicit hold, so the caller would go on installing the older extension (now with a warning). Keep the Dependabot-maintained workflow SHA and its same-line release comment. Keep an input only as a deliberate hold; it installs the requested version and warns when it differs from the workflow release. + +| Caller | Workflow file | Reusable workflow | Current pin | Pin comment | Input version | +| --- | --- | --- | --- | --- | --- | +| [pm-ado](https://github.com/unbraind/pm-ado) | [pm-github-closeout.yml](https://github.com/unbraind/pm-ado/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-ado](https://github.com/unbraind/pm-ado) | [pm-github-sync.yml](https://github.com/unbraind/pm-ado/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-beads](https://github.com/unbraind/pm-beads) | [pm-github-closeout.yml](https://github.com/unbraind/pm-beads/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-beads](https://github.com/unbraind/pm-beads) | [pm-github-sync.yml](https://github.com/unbraind/pm-beads/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-brief](https://github.com/unbraind/pm-brief) | [pm-github-closeout.yml](https://github.com/unbraind/pm-brief/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-brief](https://github.com/unbraind/pm-brief) | [pm-github-sync.yml](https://github.com/unbraind/pm-brief/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-changelog](https://github.com/unbraind/pm-changelog) | [pm-github-closeout.yml](https://github.com/unbraind/pm-changelog/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-changelog](https://github.com/unbraind/pm-changelog) | [pm-github-sync.yml](https://github.com/unbraind/pm-changelog/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-context](https://github.com/unbraind/pm-context) | [pm-github-closeout.yml](https://github.com/unbraind/pm-context/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-context](https://github.com/unbraind/pm-context) | [pm-github-sync.yml](https://github.com/unbraind/pm-context/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-csv](https://github.com/unbraind/pm-csv) | [pm-github-closeout.yml](https://github.com/unbraind/pm-csv/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-csv](https://github.com/unbraind/pm-csv) | [pm-github-sync.yml](https://github.com/unbraind/pm-csv/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-evidence](https://github.com/unbraind/pm-evidence) | [pm-github-sync.yml](https://github.com/unbraind/pm-evidence/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `a935d7b9cefde7c5099e552914653217e1024c3f` | pm-github 2026.11.02 | `2026.10.19` | +| [pm-gantt-chart](https://github.com/unbraind/pm-gantt-chart) | [pm-github-closeout.yml](https://github.com/unbraind/pm-gantt-chart/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-gantt-chart](https://github.com/unbraind/pm-gantt-chart) | [pm-github-sync.yml](https://github.com/unbraind/pm-gantt-chart/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-http](https://github.com/unbraind/pm-http) | [pm-github-sync.yml](https://github.com/unbraind/pm-http/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.28` | +| [pm-hypothesis](https://github.com/unbraind/pm-hypothesis) | [pm-github-sync.yml](https://github.com/unbraind/pm-hypothesis/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.24` | +| [pm-jev](https://github.com/unbraind/pm-jev) | [pm-github-closeout.yml](https://github.com/unbraind/pm-jev/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-jev](https://github.com/unbraind/pm-jev) | [pm-github-sync.yml](https://github.com/unbraind/pm-jev/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-jira](https://github.com/unbraind/pm-jira) | [pm-github-closeout.yml](https://github.com/unbraind/pm-jira/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-jira](https://github.com/unbraind/pm-jira) | [pm-github-sync.yml](https://github.com/unbraind/pm-jira/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-linear](https://github.com/unbraind/pm-linear) | [pm-github-closeout.yml](https://github.com/unbraind/pm-linear/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-linear](https://github.com/unbraind/pm-linear) | [pm-github-sync.yml](https://github.com/unbraind/pm-linear/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-lint](https://github.com/unbraind/pm-lint) | [pm-github-closeout.yml](https://github.com/unbraind/pm-lint/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-lint](https://github.com/unbraind/pm-lint) | [pm-github-sync.yml](https://github.com/unbraind/pm-lint/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-memory](https://github.com/unbraind/pm-memory) | [pm-github-sync.yml](https://github.com/unbraind/pm-memory/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.19` | +| [pm-ops](https://github.com/unbraind/pm-ops) | [pm-github-closeout.yml](https://github.com/unbraind/pm-ops/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-ops](https://github.com/unbraind/pm-ops) | [pm-github-sync.yml](https://github.com/unbraind/pm-ops/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-presets](https://github.com/unbraind/pm-presets) | [pm-github-closeout.yml](https://github.com/unbraind/pm-presets/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-presets](https://github.com/unbraind/pm-presets) | [pm-github-sync.yml](https://github.com/unbraind/pm-presets/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-review](https://github.com/unbraind/pm-review) | [pm-github-closeout.yml](https://github.com/unbraind/pm-review/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-review](https://github.com/unbraind/pm-review) | [pm-github-sync.yml](https://github.com/unbraind/pm-review/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-rl](https://github.com/unbraind/pm-rl) | [pm-github-closeout.yml](https://github.com/unbraind/pm-rl/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-rl](https://github.com/unbraind/pm-rl) | [pm-github-sync.yml](https://github.com/unbraind/pm-rl/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-rlm](https://github.com/unbraind/pm-rlm) | [pm-github-sync.yml](https://github.com/unbraind/pm-rlm/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-rust](https://github.com/unbraind/pm-rust) | [pm-github-closeout.yml](https://github.com/unbraind/pm-rust/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-rust](https://github.com/unbraind/pm-rust) | [pm-github-sync.yml](https://github.com/unbraind/pm-rust/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-slack](https://github.com/unbraind/pm-slack) | [pm-github-closeout.yml](https://github.com/unbraind/pm-slack/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-slack](https://github.com/unbraind/pm-slack) | [pm-github-sync.yml](https://github.com/unbraind/pm-slack/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-slack-standup](https://github.com/unbraind/pm-slack-standup) | [pm-github-closeout.yml](https://github.com/unbraind/pm-slack-standup/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | v2026.11.04 | `2026.10.16` | +| [pm-slack-standup](https://github.com/unbraind/pm-slack-standup) | [pm-github-sync.yml](https://github.com/unbraind/pm-slack-standup/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | v2026.11.04 | `2026.10.16` | +| [pm-starter](https://github.com/unbraind/pm-starter) | [pm-github-closeout.yml](https://github.com/unbraind/pm-starter/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-starter](https://github.com/unbraind/pm-starter) | [pm-github-sync.yml](https://github.com/unbraind/pm-starter/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-swarm](https://github.com/unbraind/pm-swarm) | [pm-github-closeout.yml](https://github.com/unbraind/pm-swarm/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-swarm](https://github.com/unbraind/pm-swarm) | [pm-github-sync.yml](https://github.com/unbraind/pm-swarm/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-todos](https://github.com/unbraind/pm-todos) | [pm-github-closeout.yml](https://github.com/unbraind/pm-todos/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-todos](https://github.com/unbraind/pm-todos) | [pm-github-sync.yml](https://github.com/unbraind/pm-todos/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-ts-starter](https://github.com/unbraind/pm-ts-starter) | [pm-github-closeout.yml](https://github.com/unbraind/pm-ts-starter/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-ts-starter](https://github.com/unbraind/pm-ts-starter) | [pm-github-sync.yml](https://github.com/unbraind/pm-ts-starter/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-vcs](https://github.com/unbraind/pm-vcs) | [pm-github-closeout.yml](https://github.com/unbraind/pm-vcs/blob/main/.github/workflows/pm-github-closeout.yml) | `pm-github-closeout.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | +| [pm-vcs](https://github.com/unbraind/pm-vcs) | [pm-github-sync.yml](https://github.com/unbraind/pm-vcs/blob/main/.github/workflows/pm-github-sync.yml) | `pm-github-sync.yml` | `eb89c6a19ecf6301cd074f3dfb36c7da36135f90` | pm-github 2026.11.04 | `2026.10.16` | + +51 calls across 28 repositories still supply the input. + +## Content receipts + +Git blob SHAs of the workflow files above; the inventory describes this snapshot only. + +| Caller / file | Contents blob SHA | +| --- | --- | +| pm-ado / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-ado / pm-github-sync.yml | `491d597b05a5de56fc60828aae573bd46b572e5b` | +| pm-beads / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-beads / pm-github-sync.yml | `200ddcdece64ebc7ddbc2cf869ef78387d00c9ef` | +| pm-brief / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-brief / pm-github-sync.yml | `da793ddc78eb4f192aaa1374d178d0a42f47c111` | +| pm-changelog / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-changelog / pm-github-sync.yml | `451a8c80209acd9e0cf4fd22cb14a334be1ca951` | +| pm-context / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-context / pm-github-sync.yml | `491d597b05a5de56fc60828aae573bd46b572e5b` | +| pm-csv / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-csv / pm-github-sync.yml | `200ddcdece64ebc7ddbc2cf869ef78387d00c9ef` | +| pm-evidence / pm-github-sync.yml | `798ea9d0279de35f93b1b4912dc23dbf2b6d9bfe` | +| pm-gantt-chart / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-gantt-chart / pm-github-sync.yml | `491d597b05a5de56fc60828aae573bd46b572e5b` | +| pm-http / pm-github-sync.yml | `6bcbb4a19a9563fbc4c7dad4a752d5dca555920c` | +| pm-hypothesis / pm-github-sync.yml | `8fb88eb47d6a8e569d8e15a87c36d96693c4447d` | +| pm-jev / pm-github-closeout.yml | `9f3b13e8d2bdb739c0b717a3754edd6be4e0ada7` | +| pm-jev / pm-github-sync.yml | `bf24cf138c54edcbbeaccad5b580e1f990898720` | +| pm-jira / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-jira / pm-github-sync.yml | `451a8c80209acd9e0cf4fd22cb14a334be1ca951` | +| pm-linear / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-linear / pm-github-sync.yml | `da793ddc78eb4f192aaa1374d178d0a42f47c111` | +| pm-lint / pm-github-closeout.yml | `38709d463904942fbf800a45963a9d544e6b378d` | +| pm-lint / pm-github-sync.yml | `d49f6bafa1234490c5bf59db48c42bbc03843708` | +| pm-memory / pm-github-sync.yml | `b56226445786fcc338361c12c6a98f936dd4ae8f` | +| pm-ops / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-ops / pm-github-sync.yml | `491d597b05a5de56fc60828aae573bd46b572e5b` | +| pm-presets / pm-github-closeout.yml | `2abe5c332c5cc525e2cc5e0d948e9d6e3b6753e2` | +| pm-presets / pm-github-sync.yml | `451a8c80209acd9e0cf4fd22cb14a334be1ca951` | +| pm-review / pm-github-closeout.yml | `533f69847bedeb36217761ef7cee2e26d06dca93` | +| pm-review / pm-github-sync.yml | `9eb2b196a77fc6909ae8bdbb98e83520a0f798e0` | +| pm-rl / pm-github-closeout.yml | `292ecd5b16ffb9f80c7b0691730a4ed302199054` | +| pm-rl / pm-github-sync.yml | `ace3c2fc93bde30b5b8e1b3a39079dde9ff0d4ce` | +| pm-rlm / pm-github-sync.yml | `927648b7646f0d788f5715bdf8bc1f4c6760df89` | +| pm-rust / pm-github-closeout.yml | `22a990f0dd958b5eb8ca04a606192cc4c555c7a2` | +| pm-rust / pm-github-sync.yml | `368132ea9d46fa293d02ac2dbfb02e489a738f14` | +| pm-slack / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-slack / pm-github-sync.yml | `491d597b05a5de56fc60828aae573bd46b572e5b` | +| pm-slack-standup / pm-github-closeout.yml | `fd228b6cafddf85e9644f119ab3c3975e4d232e7` | +| pm-slack-standup / pm-github-sync.yml | `43bf182284e73da1c59e24806dca3082d31c5968` | +| pm-starter / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-starter / pm-github-sync.yml | `200ddcdece64ebc7ddbc2cf869ef78387d00c9ef` | +| pm-swarm / pm-github-closeout.yml | `28b65f3fb7cd582052db100edaf3149cbaa4f126` | +| pm-swarm / pm-github-sync.yml | `de2e09cd72047976e01637e470c77f3ad553f02f` | +| pm-todos / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-todos / pm-github-sync.yml | `da793ddc78eb4f192aaa1374d178d0a42f47c111` | +| pm-ts-starter / pm-github-closeout.yml | `7a95e866ac022111d4102f05cd0e3c0c7b7607c3` | +| pm-ts-starter / pm-github-sync.yml | `451a8c80209acd9e0cf4fd22cb14a334be1ca951` | +| pm-vcs / pm-github-closeout.yml | `c3373b2f7da2ec441c904e532b32b0a514ec6640` | +| pm-vcs / pm-github-sync.yml | `d72b6616a5893e37a2b6625a03f403927e44a585` | diff --git a/test/bot-pr-workflow.test.ts b/test/bot-pr-workflow.test.ts index adb00ab2..63b654ee 100644 --- a/test/bot-pr-workflow.test.ts +++ b/test/bot-pr-workflow.test.ts @@ -527,7 +527,7 @@ for (const kind of ["sync", "closeout"]) { test("sync and closeout share PR maintenance apart from the closeout attribution hold", () => { const hold = 'if [[ "${CLOSEOUT_REVIEW:-0}" != 0 ]]; then\n' + ' echo "::notice::Delivery attribution needs review; caller checks were dispatched and automatic landing is disabled."\n' - + ' exit 0\nfi\n'; + + 'fi\n'; const closeout = maintenance("closeout"); assert.equal(closeout.split(hold).length, 2); const landingReceipt = '# The flag records an observed successful arm, never the intention: a\n' @@ -536,7 +536,11 @@ test("sync and closeout share PR maintenance apart from the closeout attribution + 'if [[ "${auto_merge_armed}" == true ]]; then\n' + ' printf \'CLOSEOUT_AUTOMERGE_ALLOWED=true\\n\' >> "${GITHUB_ENV}"\nfi\n'; assert.equal(closeout.split(landingReceipt).length, 2); - assert.equal(maintenance("sync"), closeout.replace(hold, "").replace(landingReceipt, "")); + const guardedArm = 'auto_merge_armed=false\nif [[ "${CLOSEOUT_REVIEW:-0}" == 0 ]]; then\n' + + ' enable_auto_merge && auto_merge_armed=true || auto_merge_armed=false\nfi'; + assert.equal(closeout.split(guardedArm).length, 2); + assert.equal(maintenance("sync"), closeout.replace(hold, "").replace(landingReceipt, "") + .replace(guardedArm, "enable_auto_merge && auto_merge_armed=true || auto_merge_armed=false")); }); for (const kind of ["sync", "closeout"]) { @@ -556,7 +560,7 @@ for (const kind of ["sync", "closeout"]) { assert.match(mint, /skip-token-revoke: false/); assert.ok(source.indexOf("name: Verify strict pm health") < source.indexOf("name: Create automation app token")); assert.ok(source.indexOf("name: Validate publication credentials") < source.indexOf("name: Commit gated")); - assert.equal(source.split("GH_TOKEN: ${{ steps.automation_token.outputs.token || github.token }}").length - 1, kind === "closeout" ? 3 : 2); + assert.equal(source.split("GH_TOKEN: ${{ steps.automation_token.outputs.token || github.token }}").length - 1, kind === "closeout" ? 4 : 3); assert.equal((source.match(/secrets\.automation_app_private_key/g) ?? []).length, 2); assert.match(source, /APP_DEADLINE: \$\{\{ steps.publication_auth.outputs.deadline \}\}/); assert.doesNotMatch(source, /MAINTENANCE_|--admin|gh pr review|resolveReviewThread|\/check-runs.*--method POST/); @@ -575,6 +579,18 @@ for (const kind of ["sync", "closeout"]) { } } })); + if (kind === "closeout") { + test("closeout: attribution hold still dispatches and captures parked URLs without arming", () => fixture(f => { + f.env.CLOSEOUT_REVIEW = "1"; + f.env.PARKED_URL = "https://github.com/acme/widgets/actions/runs/123"; + const r = f.execute(kind); + assert.equal(r.status, 0, String(r.stderr)); + assert.match(String(r.stdout), /Delivery attribution needs review/); + assert.match(f.receipts(), /workflow run/); + assert.match(f.receipts(), /PARKED_CI_RUN_URLS=\["https:/); + assert.doesNotMatch(f.receipts(), /pr merge|CLOSEOUT_AUTOMERGE_ALLOWED=true/); + })); + } test(`${kind}: the dispatch step shell never sleeps or polls other runs`, () => { const source = parse(readFileSync(join(root, `.github/workflows/pm-github-${kind}.yml`), "utf8")) as { jobs: Record[] }> }; const step = source.jobs[`gated-${kind}`]!.steps.find(step => step.name === "Dispatch caller CI and report parked runs")!; diff --git a/test/ci-handoff-workflow.test.ts b/test/ci-handoff-workflow.test.ts new file mode 100644 index 00000000..dd4204dc --- /dev/null +++ b/test/ci-handoff-workflow.test.ts @@ -0,0 +1,176 @@ +/** Exercise bounded registration discovery and exported receipts from the reusable workflow shell. */ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import test from "node:test"; +import { parse } from "yaml"; + +const root = resolve(import.meta.dirname, ".."); +const handoffName = "Confirm caller CI registration and summarize handoff"; +interface Workflow { + on: { workflow_call?: { outputs?: Record } }; + jobs: Record; steps?: { name?: string; run?: string; id?: string; "timeout-minutes"?: number }[] }>; +} + +/** Parse the actual reusable workflow, retaining its executable bash. */ +function workflow(kind: string): Workflow { + return parse(readFileSync(join(root, `.github/workflows/pm-github-${kind}.yml`), "utf8")) as Workflow; +} + +/** Run discovery against a real Git head and receipt-only GitHub responses without wall-clock sleeps. */ +function fixture(kind: string, overrides: NodeJS.ProcessEnv, verify: (result: { status: number | null; stdout: string; stderr: string; output: Record; summary: string; calls: string }) => void): void { + const dir = mkdtempSync(join(tmpdir(), "ci-handoff-")); + const env = { ...process.env, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_NOSYSTEM: "1", + GIT_AUTHOR_NAME: "Fixture", GIT_COMMITTER_NAME: "Fixture", GIT_AUTHOR_EMAIL: "fixture@users.noreply.github.com", GIT_COMMITTER_EMAIL: "fixture@users.noreply.github.com", + REPOSITORY: "acme/widgets", SYNC_PR_NUMBER: "7", SYNC_BRANCH: "automation/proposal", GITHUB_SERVER_URL: "https://github.com", + DISPATCHED_CI_WORKFLOWS: "ci.yml codeql.yml", CI_DISPATCH_STARTED_AT: "2026-11-14T00:00:00Z", + PARKED_CI_RUN_URLS: '["https://github.com/acme/widgets/actions/runs/99"]', + GITHUB_OUTPUT: join(dir, "output"), GITHUB_STEP_SUMMARY: join(dir, "summary"), RECEIPTS: join(dir, "receipts"), + REAL_SLEEP: spawnSync("bash", ["-c", "command -v sleep"], { encoding: "utf8" }).stdout.trim(), + ...overrides, PATH: join(dir, "bin") + ":" + process.env.PATH }; + try { + mkdirSync(join(dir, "bin")); + for (const args of [["init", "-q"], ["commit", "--allow-empty", "-qm", "Head"]]) { + const result = spawnSync("git", args, { cwd: dir, env, encoding: "utf8" }); + assert.equal(result.status, 0, result.stderr); + } + writeFileSync(join(dir, "bin/gh"), `#!/usr/bin/env bash +set -euo pipefail +printf '%s\\n' "$*" >> "$RECEIPTS" +[[ "$1 $2" == 'run list' ]] || exit 99 +[[ "$*" != *--status* && "$*" != *conclusion* ]] || exit 98 +if [[ "\${LOOKUP:-}" == deadline || "\${LOOKUP:-}" == stuck ]]; then + if [[ "$*" == *'--workflow ci.yml'* ]]; then + printf '20' > "$RECEIPTS.clock" + else + printf '60' > "$RECEIPTS.clock" + if [[ "\${LOOKUP:-}" == stuck ]]; then + printf 'stuck-started\\n' >> "$RECEIPTS" + "$REAL_SLEEP" 120 + printf 'stuck-finished\\n' >> "$RECEIPTS" + fi + exit 0 + fi +fi +if [[ "\${LOOKUP:-}" == denied ]]; then printf 'partial output'; exit 1; fi +if [[ "\${LOOKUP:-}" == timeout ]]; then exit 124; fi +if [[ "\${LOOKUP:-}" == absent ]]; then exit 0; fi +if [[ "\${LOOKUP:-}" == partial && "$*" == *'--workflow codeql.yml'* ]]; then exit 0; fi +if [[ "\${LOOKUP:-}" == delayed && ! -f "$RECEIPTS.seen" ]]; then touch "$RECEIPTS.seen"; exit 0; fi +if [[ "$*" == *'--workflow ci.yml'* ]]; then printf 'https://github.com/acme/widgets/actions/runs/11\\n'; +else printf 'https://github.com/acme/widgets/actions/runs/12\\n'; fi +`, { mode: 0o755 }); + writeFileSync(join(dir, "bin/sleep"), '#!/usr/bin/env bash\nprintf "sleep %s\\n" "$*" >> "$RECEIPTS"\n', { mode: 0o755 }); + if (overrides.LOOKUP !== "stuck") writeFileSync(join(dir, "bin/timeout"), '#!/usr/bin/env bash\nprintf "budget %s\\n" "$2" >> "$RECEIPTS"\nshift 2\nexec "$@"\n', { mode: 0o755 }); + const shell = workflow(kind).jobs[`gated-${kind}`]!.steps!.find(step => step.name === handoffName)!.run!; + // Keep the production shell intact. A parent-shell DEBUG trap models only + // elapsed time; child lookups advance a deterministic shared clock receipt. + const controlledClock = overrides.LOOKUP === "deadline" || overrides.LOOKUP === "stuck" + ? `SECONDS=0\ntrap 'if [[ -f "$RECEIPTS.clock" ]]; then read -r SECONDS < "$RECEIPTS.clock" || :; fi' DEBUG\n` : ""; + const result = spawnSync("bash", ["-c", controlledClock + shell], { cwd: dir, env, encoding: "utf8", timeout: 30_000 }); + const optionalRead = (file: string) => { try { return readFileSync(file, "utf8"); } catch { return ""; } }; + const output = Object.fromEntries(optionalRead(env.GITHUB_OUTPUT).trim().split("\n").map(line => { + const index = line.indexOf("="); return [line.slice(0, index), line.slice(index + 1)]; + })); + verify({ status: result.status, stdout: result.stdout, stderr: result.stderr, output, + summary: optionalRead(env.GITHUB_STEP_SUMMARY), calls: optionalRead(env.RECEIPTS) }); + } finally { rmSync(dir, { recursive: true, force: true }); } +} + +for (const kind of ["sync", "closeout"]) { + test(`${kind}: registration accepts queued runs immediately and exports PR, CI and parked URLs`, () => fixture(kind, {}, r => { + assert.equal(r.status, 0, r.stderr); + assert.equal(r.output["pr-url"], "https://github.com/acme/widgets/pull/7"); + assert.equal(r.output["ci-confirmation"], "confirmed"); + assert.deepEqual(JSON.parse(r.output["ci-run-urls"]!), ["https://github.com/acme/widgets/actions/runs/11", "https://github.com/acme/widgets/actions/runs/12"]); + assert.deepEqual(JSON.parse(r.output["parked-run-urls"]!), ["https://github.com/acme/widgets/actions/runs/99"]); + for (const url of [r.output["pr-url"]!, ...JSON.parse(r.output["ci-run-urls"]!), ...JSON.parse(r.output["parked-run-urls"]!)]) assert.ok(r.summary.includes(url)); + assert.match(r.summary, /hourly approval sweep/); + assert.doesNotMatch(r.calls, /sleep|--status|conclusion|watch|pr merge/); + assert.equal(r.calls.split("\n").filter(line => line.startsWith("run list")).length, 2); + assert.match(r.calls, /--branch automation\/proposal --commit [a-f0-9]{40} --event workflow_dispatch --created >=2026-11-14T00:00:00Z/); + })); + test(`${kind}: delayed registration retries only a missing workflow and exits before completion`, () => fixture(kind, { LOOKUP: "delayed" }, r => { + assert.equal(r.status, 0, r.stderr); + assert.equal(r.output["ci-confirmation"], "confirmed"); + assert.equal(r.calls.split("\n").filter(line => line.includes("--workflow ci.yml")).length, 2); + assert.equal(r.calls.split("\n").filter(line => line.includes("--workflow codeql.yml")).length, 1); + assert.equal(r.calls.split("\n").filter(line => line.startsWith("sleep")).length, 1); + })); + for (const lookup of ["absent", "denied", "timeout", "partial"]) { + test(`${kind}: bounded ${lookup} registration reports without failing sync or changing auto-merge`, () => fixture(kind, { LOOKUP: lookup }, r => { + assert.equal(r.status, 0, r.stderr); + assert.equal(r.output["ci-confirmation"], "timed_out"); + assert.match(r.stdout, /registration not confirmed within 60 seconds/); + assert.match(r.summary, /timed_out/); + assert.doesNotMatch(r.calls, /pr merge|watch|--status|conclusion/); + assert.doesNotMatch(r.output["ci-run-urls"]!, /partial output/); + assert.ok(r.calls.split("\n").filter(line => line.startsWith("run list")).length <= 24); + assert.equal(r.calls.split("\n").filter(line => line.startsWith("sleep")).length, 11); + if (lookup === "partial") assert.deepEqual(JSON.parse(r.output["ci-run-urls"]!), ["https://github.com/acme/widgets/actions/runs/11"]); + })); + } + for (const lookup of ["deadline", "stuck"]) { + test(`${kind}: ${lookup === "stuck" ? "real timeout kills a stuck lookup" : "shared deadline expires across workflows"} and retains partial URLs without a late lookup`, () => { + const started = Date.now(); + fixture(kind, { LOOKUP: lookup, DISPATCHED_CI_WORKFLOWS: "ci.yml codeql.yml extra.yml" }, r => { + assert.equal(r.status, 0, r.stderr); + assert.equal(r.output["ci-confirmation"], "timed_out"); + assert.deepEqual(JSON.parse(r.output["ci-run-urls"]!), ["https://github.com/acme/widgets/actions/runs/11"]); + assert.match(r.summary, /runs\/11/); + assert.doesNotMatch(r.calls, /--workflow extra.yml|sleep|stuck-finished/); + assert.equal(r.calls.split("\n").filter(line => line.startsWith("run list")).length, 2); + if (lookup === "stuck") { + assert.match(r.calls, /stuck-started/); + assert.doesNotMatch(r.calls, /budget/, "the real GNU timeout must execute"); + assert.ok(Date.now() - started >= 9_000, "lookup must actually remain stuck until killed"); + assert.ok(Date.now() - started < 25_000, "the stuck lookup must not consume the fixture deadline"); + } + }); + }); + } + test(`${kind}: explicitly disabled dispatch reports the PR without querying or waiting`, () => fixture(kind, { DISPATCHED_CI_WORKFLOWS: "", PARKED_CI_RUN_URLS: undefined }, r => { + assert.equal(r.status, 0, r.stderr); + assert.equal(r.output["ci-confirmation"], "disabled"); + assert.equal(r.output["ci-run-urls"], "[]"); + assert.equal(r.output["parked-run-urls"], "[]"); + assert.equal(r.calls, ""); + })); + test(`${kind}: caller outputs are wired through job and workflow without new inputs`, () => { + const source = workflow(kind); + const job = source.jobs[`gated-${kind}`]!; + for (const key of ["pr-url", "ci-run-urls", "parked-run-urls", "ci-confirmation"]) { + assert.equal(source.on.workflow_call!.outputs![key]!.value, `\${{ jobs.gated-${kind}.outputs.${key} }}`); + assert.equal(job.outputs![key], `\${{ steps.ci_handoff.outputs.${key} }}`); + } + const dispatch = job.steps!.find(step => step.name === "Dispatch caller CI and report parked runs")!.run!; + assert.ok(dispatch.indexOf("CI_DISPATCH_STARTED_AT=") < dispatch.indexOf("\ndispatch_checks\n")); + assert.match(dispatch, /PARKED_CI_RUN_URLS=/); + if (kind === "closeout") assert.match(dispatch, /if \[\[ "\$\{CLOSEOUT_REVIEW:-0\}" == 0 \]\]; then\n enable_auto_merge/); + }); +} + +test("every reusable step forbids completion waits; only registration has a bounded appearance loop", () => { + for (const file of readdirSync(join(root, ".github/workflows")).filter(file => /\.ya?ml$/.test(file))) { + const source = parse(readFileSync(join(root, ".github/workflows", file), "utf8")) as Workflow; + if (!source.on?.workflow_call) continue; + for (const job of Object.values(source.jobs)) for (const step of job.steps ?? []) { + const shell = step.run ?? ""; + assert.doesNotMatch(shell, /\bgh\s+(?:[^;\n]*?\s)?run\s+watch\b|\bgh\s+pr\s+checks\b[^\n]*--watch/, `${file}: completion watch forbidden`); + if (step.name === handoffName) { + assert.equal(step["timeout-minutes"], 2); + assert.match(shell, /deadline=\$\(\(SECONDS \+ 60\)\)/); + assert.match(shell, /for round in \{1\.\.12\}/); + assert.match(shell, /\[\[ \$\{remaining\} -ge 10 \]\] \|\| break/); + assert.match(shell, /timeout --signal=KILL 10s gh run list/); + assert.match(shell, /--event workflow_dispatch --created/); + assert.doesNotMatch(shell, /--status|conclusion|check-runs|check-suites|\bgh\s+run\s+view/); + assert.equal((shell.match(/\bsleep\b/g) ?? []).length, 1); + } else { + assert.doesNotMatch(shell, /\bsleep\s+\S+/, `${file} ${step.name}: runner cannot sleep waiting for another run`); + } + } + } +}); diff --git a/test/import-gate.test.ts b/test/import-gate.test.ts index 4258a418..44ae0a4e 100644 --- a/test/import-gate.test.ts +++ b/test/import-gate.test.ts @@ -1647,7 +1647,7 @@ test("workflow input shell diagnoses a foreign repository and branch outside aut test("workflow credentials stay out of install steps and checkout persistence", () => { const steps = workflow.split(/\n - name: /).slice(1); const tokenSteps = steps.filter(step => step.includes("GH_TOKEN:")).map(step => step.split("\n")[0]); - assert.deepEqual(tokenSteps, ["Require caller default branch", "Validate GitHub access", "Revalidate GitHub blockers (report only)", "Preview GitHub to pm plan", "Gated GitHub to pm import", "Verify repeat import is a no-op", "Commit gated sync changes and open review PR", "Dispatch caller CI and report parked runs", "Reconcile sync failure tracking issue"]); + assert.deepEqual(tokenSteps, ["Require caller default branch", "Validate GitHub access", "Revalidate GitHub blockers (report only)", "Preview GitHub to pm plan", "Gated GitHub to pm import", "Verify repeat import is a no-op", "Commit gated sync changes and open review PR", "Dispatch caller CI and report parked runs", "Confirm caller CI registration and summarize handoff", "Reconcile sync failure tracking issue"]); assert.doesNotMatch(workflow.slice(0, stepOffset("Require caller default branch")), /GH_TOKEN:/); assert.match(steps.find(step => step.startsWith("Checkout caller default branch"))!, /persist-credentials: false/); assert.match(workflowShell(["Install dependencies"]), /npm ci --ignore-scripts/); diff --git a/test/self-versioned-workflows.test.ts b/test/self-versioned-workflows.test.ts index a7154acb..0f17b8f9 100644 --- a/test/self-versioned-workflows.test.ts +++ b/test/self-versioned-workflows.test.ts @@ -1,18 +1,19 @@ -/** Execute release-tag resolution and exact installation from the real workflows. */ +/** Execute manifest-based resolution and real package installation from workflow bash. */ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import test from "node:test"; import { decideDailyRelease } from "../scripts/daily-release-plan.ts"; const root = resolve(import.meta.dirname, ".."); +const cli = join(root, "node_modules/.bin/pm"); const files = [".github/workflows/pm-github-sync.yml", ".github/workflows/pm-github-closeout.yml"]; const release = readFileSync(join(root, ".github/workflows/release.yml"), "utf8"); const workflowSha = "a".repeat(40); -const otherSha = "b".repeat(40); const version = "2027.1.2-3"; +const template = mkdtempSync(join(tmpdir(), "workflow-tracker-template-")); /** Extract one named step's real bash without executing adjacent steps. */ function shell(source: string, name: string): string { @@ -29,116 +30,183 @@ function fixture(run: (cwd: string) => void): void { try { run(cwd); } finally { rmSync(cwd, { recursive: true, force: true }); } } -/** Execute real bash steps with an explicit environment and bounded runtime. */ +/** Execute real bash steps with isolated pm configuration and bounded runtime. */ function bash(cwd: string, script: string, env: NodeJS.ProcessEnv = {}) { - return spawnSync("bash", ["-c", script], { cwd, env: { ...process.env, ...env }, encoding: "utf8", timeout: 30_000 }); + const childEnv: NodeJS.ProcessEnv = { ...process.env, ...env, PM_AUTHOR: "fixture", PM_TELEMETRY_INLINE_FLUSH: "1", + PM_GLOBAL_PATH: join(cwd, "global"), PM_PATH: join(cwd, ".agents/pm"), NPM_CONFIG_USERCONFIG: "/dev/null" }; + for (const key of ["NPM_TOKEN", "NODE_AUTH_TOKEN", "NPM_MEDIA_SERVER"]) delete childEnv[key]; + return spawnSync("bash", ["-c", script], { cwd, env: childEnv, encoding: "utf8", timeout: 30_000 }); } -/** Inject only remote transport and package installation, retaining real resolver bash. */ +test.before(() => { + const init = bash(template, '"$REAL_PM" init fixture --defaults --agent-guidance skip', { REAL_PM: cli }); + assert.equal(init.status, 0, init.stderr + init.stdout); + const seed = bash(template, '"$REAL_PM" create task "Existing curated item" --id curated --description "Curated content"', { REAL_PM: cli }); + assert.equal(seed.status, 0, seed.stderr + seed.stdout); +}); +test.after(() => rmSync(template, { recursive: true, force: true })); + +/** Snapshot every durable tracker byte, including native append-only history. */ +function snapshot(directory: string): Record { + return Object.fromEntries(readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name)) + .flatMap(entry => entry.isDirectory() + ? Object.entries(snapshot(join(directory, entry.name))).map(([key, value]) => [entry.name + "/" + key, value]) + : [[entry.name, readFileSync(join(directory, entry.name)).toString("base64")]])); +} + +/** Replace external HTTP transport only; local fixture installation uses the real pinned pm. */ function setup(cwd: string): NodeJS.ProcessEnv { + cpSync(join(template, ".agents"), join(cwd, ".agents"), { recursive: true }); mkdirSync(join(cwd, "bin")); - mkdirSync(join(cwd, ".agents/pm/extensions/pm-github"), { recursive: true }); - writeFileSync(join(cwd, "bin/git"), `#!/usr/bin/env bash + mkdirSync(join(cwd, "packages")); + // Deliberately unrelated caller metadata must never determine the installed version. + writeFileSync(join(cwd, "package.json"), '{"name":"caller","version":"1999.1.1"}'); + writeFileSync(join(cwd, "bin/curl"), `#!/usr/bin/env bash set -euo pipefail -[[ "$#" == 4 && "$1" == ls-remote && "$2" == --tags && "$3" == https://github.com/unbraind/pm-github && "$4" == 'refs/tags/v*' ]] || exit 95 -printf '%s\\n' "$REMOTE_TAGS" -exit "\${REMOTE_STATUS:-0}" +url="\${@: -1}" +printf '%s\\n' "$url" >> http-receipt +case "$url" in + "https://raw.githubusercontent.com/unbraind/pm-github/$WORKFLOW_SHA/package.json") + printf '%s' "$WORKFLOW_MANIFEST"; exit "\${MANIFEST_STATUS:-0}" ;; + "https://registry.npmjs.org/pm-github/"*) + selected="\${url##*/}" + [[ "$selected" != "\${MISSING_VERSION:-}" ]] || exit 22 + [[ "\${REGISTRY_STATUS:-0}" == 0 ]] || exit "$REGISTRY_STATUS" + if [[ -n "\${REGISTRY_METADATA:-}" ]]; then printf '%s' "$REGISTRY_METADATA"; else + printf '{"name":"pm-github","version":"%s"}' "$selected" + fi ;; + *) exit 95 ;; +esac `, { mode: 0o755 }); writeFileSync(join(cwd, "bin/pm"), `#!/usr/bin/env bash set -euo pipefail -[[ "$1" == package && "$2" == install && "$4" == --project ]] -printf '%s\\n' "$3" > receipt -printf '{"version":"%s"}' "\${INSTALLED_VERSION:-\${3#npm:pm-github@}}" > "$PM_SYNC_TRACKER/extensions/pm-github/package.json" +if [[ "$1 $2" == 'package install' ]]; then + [[ "$3" == npm:pm-github@* && "$4" == --project ]] || exit 95 + printf '%s\\n' "$3" > receipt + exec "$REAL_PM" package install "$FIXTURE_PACKAGES/\${3#npm:pm-github@}" --project +fi +exec "$REAL_PM" "$@" `, { mode: 0o755 }); - return { WORKFLOW_SHA: workflowSha, REMOTE_TAGS: "", REMOTE_STATUS: "0", INSTALLED_VERSION: "", - REQUESTED_PM_GITHUB_VERSION: "", GITHUB_ENV: join(cwd, "github-env"), - PM_SYNC_TRACKER: ".agents/pm", PATH: join(cwd, "bin") + ":" + process.env.PATH }; + return { WORKFLOW_SHA: workflowSha, WORKFLOW_MANIFEST: JSON.stringify({ name: "pm-github", version }), + REQUESTED_PM_GITHUB_VERSION: "", GITHUB_ENV: join(cwd, "github-env"), REAL_PM: cli, + PM_SYNC_TRACKER: ".agents/pm", FIXTURE_PACKAGES: join(cwd, "packages"), RUNNER_TEMP: cwd, + PATH: join(cwd, "bin") + ":" + process.env.PATH }; +} + +/** Supply a minimal real local extension, avoiding npm downloads while retaining pm installation. */ +function packageFixture(cwd: string, selected: string): void { + const directory = join(cwd, "packages", selected); + mkdirSync(directory, { recursive: true }); + writeFileSync(join(directory, "package.json"), JSON.stringify({ name: "pm-github", version: selected, type: "module" })); + writeFileSync(join(directory, "manifest.json"), JSON.stringify({ name: "pm-github", version: selected, + description: "Synthetic workflow installation fixture", entry: "./index.js", capabilities: [] })); + writeFileSync(join(directory, "index.js"), "export default function () {}\n"); +} + +interface Scenario { + name: string; + manifest?: string; + input?: string; + expected?: string; + warning?: boolean; + env?: NodeJS.ProcessEnv; + error?: RegExp; } for (const file of files) { const source = readFileSync(join(root, file), "utf8"); const kind = file.includes("closeout") ? "closeout" : "sync"; - const scenarios = [ - { name: "lightweight tag", tags: `${workflowSha}\trefs/tags/v${version}`, expected: version }, - { name: "peeled annotated tag", tags: `${otherSha}\trefs/tags/v${version}\n${workflowSha}\trefs/tags/v${version}^{}`, expected: version }, - { name: "peeled tag before object", tags: `${workflowSha}\trefs/tags/v${version}^{}\n${otherSha}\trefs/tags/v${version}`, expected: version }, - { name: "unrelated and malformed tags ignored", tags: `${otherSha}\trefs/tags/v2020.1.1\n${workflowSha}\trefs/tags/vlatest\n${workflowSha}\trefs/tags/v${version}`, expected: version }, - { name: "numeric version without suffix", tags: `${workflowSha}\trefs/tags/v2027.1.2`, expected: "2027.1.2" }, - { name: "zero-padded tag resolves to the published unpadded version", tags: `${workflowSha}\trefs/tags/v2026.11.02`, expected: "2026.11.2" }, - { name: "zero-padded tag with suffix keeps the suffix", tags: `${workflowSha}\trefs/tags/v2027.01.01-2`, expected: "2027.1.1-2" }, - { name: "a component past 64-bit range normalizes as text without wrapping", tags: `${workflowSha}\trefs/tags/v18446744073709551616.11.02`, expected: "18446744073709551616.11.2" }, - { name: "an all-zero component normalizes to 0", tags: `${workflowSha}\trefs/tags/v2026.00.000`, expected: "2026.0.0" }, - { name: "zero-padded legacy input normalizes like a tag", tags: `${otherSha}\trefs/tags/v2020.1.1`, input: "2026.11.02", expected: "2026.11.2", warning: true }, - { name: "padded tag matches a normalized input without warning", tags: `${workflowSha}\trefs/tags/v2026.11.02`, input: "2026.11.2", expected: "2026.11.2" }, - { name: "two padded spellings of one version are not ambiguous", tags: `${workflowSha}\trefs/tags/v2026.11.02\n${workflowSha}\trefs/tags/v2026.11.2`, expected: "2026.11.2" }, - { name: "no match with legacy input", tags: `${otherSha}\trefs/tags/v2020.1.1`, input: "2020.1.1", expected: "2020.1.1", warning: true }, - { name: "tag overrides mismatched input", tags: `${workflowSha}\trefs/tags/v${version}`, input: "2020.1.1", expected: version, warning: true }, - { name: "tag overrides malicious input safely", tags: `${workflowSha}\trefs/tags/v${version}`, input: "$(touch injected)\n::error::injected", expected: version, warning: true }, - { name: "matching legacy input", tags: `${workflowSha}\trefs/tags/v${version}`, input: version, expected: version }, - { name: "no match without input", tags: `${otherSha}\trefs/tags/v${version}`, error: /Pin a release SHA/ }, - { name: "tag object is not a commit match", tags: `${workflowSha}\trefs/tags/v${version}\n${otherSha}\trefs/tags/v${version}^{}`, error: /Pin a release SHA/ }, - { name: "multiline fallback refused as a whole value", tags: "", input: "2027.1.2\ninvalid", error: /exact numeric version/ }, - { name: "valid first line cannot hide a second version", tags: "", input: "2027.1.2\n2027.2.3", error: /exact numeric version/ }, - { name: "tag key is data rather than executable text", tags: `${workflowSha}\trefs/tags/v$(touch injected)`, input: "2027.1.2", expected: "2027.1.2", warning: true }, - { name: "floating fallback refused", tags: "", input: "latest", error: /exact numeric version/ }, - { name: "malicious fallback refused safely", tags: "", input: "$(touch injected)\n::error::injected", error: /exact numeric version/ }, - { name: "remote failure cannot use fallback", tags: "", input: "2020.1.1", remoteStatus: "128", error: /Cannot read pm-github release tags/ }, - { name: "missing workflow identity refused", tags: "", sha: "", input: "2020.1.1", error: /job.workflow_sha/ }, - { name: "ambiguous release versions refused", tags: `${workflowSha}\trefs/tags/v2020.1.1\n${workflowSha}\trefs/tags/v${version}`, error: /Multiple release versions/ }, + const scenarios: Scenario[] = [ + { name: "omitted input follows manifest at SHA without release tags", expected: version }, + { name: "zero-padded release normalizes", manifest: "2026.11.04", expected: "2026.11.4" }, + { name: "zero-padded suffix release normalizes", manifest: "2027.01.01-2", expected: "2027.1.1-2" }, + { name: "large numeric component normalizes without arithmetic overflow", manifest: "18446744073709551616.11.02", expected: "18446744073709551616.11.2" }, + { name: "all-zero component stays zero", manifest: "2026.00.000", expected: "2026.0.0" }, + { name: "older explicit hold installs the hold and warns with both versions", input: "2026.10.16", expected: "2026.10.16", warning: true }, + { name: "newer explicit hold remains explicit", input: "2028.1.1", expected: "2028.1.1", warning: true }, + { name: "matching explicit hold needs no warning", input: version, expected: version }, + { name: "both sides normalize before comparison", manifest: "2026.11.04", input: "2026.011.004", expected: "2026.11.4" }, + { name: "non-existent version fails before tracker write", input: "2099.1.1", env: { MISSING_VERSION: "2099.1.1" }, error: /unavailable; refusing tracker writes/ }, + { name: "non-existent derived release fails before tracker write", env: { MISSING_VERSION: version }, error: /unavailable; refusing tracker writes/ }, + { name: "registry outage fails closed", env: { REGISTRY_STATUS: "28" }, error: /unavailable; refusing tracker writes/ }, + { name: "registry wrong version fails closed", env: { REGISTRY_METADATA: '{"name":"pm-github","version":"1999.1.1"}' }, error: /Registry metadata/ }, + { name: "registry wrong package fails closed", env: { REGISTRY_METADATA: `{"name":"other","version":"${version}"}` }, error: /Registry metadata/ }, + { name: "registry malformed JSON fails closed", env: { REGISTRY_METADATA: 'invalid"private-metadata' }, error: /Registry metadata/ }, + { name: "manifest transport failure cannot use explicit fallback", input: "2026.10.16", env: { MANIFEST_STATUS: "22" }, error: /Cannot read pm-github package.json/ }, + { name: "manifest malformed JSON fails closed", env: { WORKFLOW_MANIFEST: 'invalid"private-metadata' }, error: /Invalid pm-github workflow package metadata/ }, + { name: "manifest wrong package fails closed", env: { WORKFLOW_MANIFEST: '{"name":"caller","version":"1999.1.1"}' }, error: /Invalid pm-github workflow package metadata/ }, + { name: "manifest missing version fails closed", env: { WORKFLOW_MANIFEST: '{"name":"pm-github"}' }, error: /Invalid pm-github workflow package metadata/ }, + { name: "manifest version with a trailing newline refused", manifest: "2026.11.4\n", error: /Invalid pm-github workflow package metadata/ }, + { name: "manifest JSON stream refused", env: { WORKFLOW_MANIFEST: '{"name":"pm-github","version":"2026.11.4"} {"name":"other"}' }, error: /Invalid pm-github workflow package metadata/ }, + { name: "registry JSON stream refused", env: { REGISTRY_METADATA: '{"name":"other"} {"name":"pm-github","version":"2027.1.2-3"}' }, error: /Registry metadata/ }, + { name: "manifest floating version refused", manifest: "latest", error: /Workflow package version/ }, + { name: "missing job workflow SHA refused", env: { WORKFLOW_SHA: "" }, error: /job.workflow_sha/ }, + { name: "tag ref cannot substitute for resolved SHA", env: { WORKFLOW_SHA: "refs/tags/v2026.11.04" }, error: /job.workflow_sha/ }, + { name: "floating explicit input refused", input: "latest", error: /exact numeric version/ }, + { name: "multiline explicit input refused as a whole", input: "2026.11.4\n2026.11.5", error: /exact numeric version/ }, + { name: "malicious explicit input refused without execution or disclosure", input: "$(touch injected)\n::error::injected", error: /exact numeric version/ }, ]; for (const scenario of scenarios) { test(`${kind} resolves ${scenario.name}`, () => fixture(cwd => { - const env: NodeJS.ProcessEnv = { ...setup(cwd), REMOTE_TAGS: scenario.tags, REQUESTED_PM_GITHUB_VERSION: scenario.input ?? "", - WORKFLOW_SHA: scenario.sha ?? workflowSha, REMOTE_STATUS: scenario.remoteStatus ?? "0", RUNNER_TEMP: cwd }; - const scope = shell(source, "Resolve sync commit scope").split("<<'SCOPE'\n")[1]!.split("\nSCOPE")[0]!; - writeFileSync(join(cwd, "pm-github-sync-scope.sh"), scope); - const script = shell(source, "Resolve pm-github release version"); - const result = bash(cwd, script, env); + const env: NodeJS.ProcessEnv = { ...setup(cwd), WORKFLOW_MANIFEST: JSON.stringify({ name: "pm-github", version: scenario.manifest ?? version }), + REQUESTED_PM_GITHUB_VERSION: scenario.input ?? "", ...scenario.env }; + const before = snapshot(join(cwd, ".agents/pm")); + // A real tracker mutation immediately after the resolver must remain unreachable on refusal. + const result = bash(cwd, 'set -euo pipefail\n' + shell(source, "Resolve pm-github release version") + + '\npm update fixture-curated --description "Reached tracker write"', env); const output = result.stdout + result.stderr; - assert.doesNotMatch(output, /::error::injected/); - assert.notEqual(bash(cwd, "test -e injected").status, 0); + assert.doesNotMatch(output, /::error::injected|private-metadata/); + assert.equal(existsSync(join(cwd, "injected")), false); if (scenario.error) { assert.notEqual(result.status, 0, output); assert.match(output, scenario.error); - assert.notEqual(bash(cwd, 'test -s "$GITHUB_ENV"', env).status, 0); + assert.equal(existsSync(env.GITHUB_ENV!), false); + assert.deepEqual(snapshot(join(cwd, ".agents/pm")), before, "all real tracker bytes must remain unchanged"); + assert.equal(existsSync(join(cwd, "receipt")), false); } else { assert.equal(result.status, 0, output); + assert.notDeepEqual(snapshot(join(cwd, ".agents/pm")), before, "control mutation proves the real tracker write is reachable"); assert.equal(readFileSync(env.GITHUB_ENV!, "utf8"), `PM_GITHUB_VERSION=${scenario.expected}\n`); assert.equal(output.includes("::warning::pm-github-version is deprecated"), !!scenario.warning, output); - // Consume the real GITHUB_ENV file as the runner does for the install step. - const install = 'set -a; source "$GITHUB_ENV"; set +a\n' + shell(source, "Install pinned pm-github extension"); - const installed = bash(cwd, install, env); + if (scenario.warning) assert.match(output, new RegExp(`workflow ${version} installs extension ${scenario.expected}`.replaceAll(".", "\\."))); + assert.match(readFileSync(join(cwd, "http-receipt"), "utf8"), new RegExp(`/pm-github/${scenario.expected}\\n$`.replaceAll(".", "\\."))); + packageFixture(cwd, scenario.expected!); + const scope = shell(source, "Resolve sync commit scope").split("<<'SCOPE'\n")[1]!.split("\nSCOPE")[0]!; + writeFileSync(join(cwd, "pm-github-sync-scope.sh"), scope); + const installed = bash(cwd, 'set -a; source "$GITHUB_ENV"; set +a\n' + shell(source, "Install pinned pm-github extension"), env); assert.equal(installed.status, 0, installed.stderr + installed.stdout); assert.equal(readFileSync(join(cwd, "receipt"), "utf8").trim(), `npm:pm-github@${scenario.expected}`); - const wrong = bash(cwd, install, { ...env, INSTALLED_VERSION: "1999.1.1" }); - assert.notEqual(wrong.status, 0, "installed-version verification remains fail closed"); - assert.ok((wrong.stdout + wrong.stderr).includes(`::error::Installed pm-github version 1999.1.1 differs from resolved workflow version ${scenario.expected}.`)); + const metadata = JSON.parse(readFileSync(join(cwd, ".agents/pm/extensions/pm-github/package.json"), "utf8")) as { version: string }; + assert.equal(metadata.version, scenario.expected); } })); } - test(`${kind} install verification explains invalid metadata without disclosing its contents`, () => fixture(cwd => { - const env = { ...setup(cwd), RUNNER_TEMP: cwd, PM_GITHUB_VERSION: version }; + test(`${kind} install verification explains invalid metadata without disclosure`, () => fixture(cwd => { + const env = { ...setup(cwd), PM_GITHUB_VERSION: version }; const scope = shell(source, "Resolve sync commit scope").split("<<'SCOPE'\n")[1]!.split("\nSCOPE")[0]!; writeFileSync(join(cwd, "pm-github-sync-scope.sh"), scope); const secret = "ghp_" + "A".repeat(36); - for (const installed of [secret, 'invalid"metadata', 'broken"source\\suffix']) { - const result = bash(cwd, shell(source, "Install pinned pm-github extension"), { ...env, INSTALLED_VERSION: installed }); + for (const installed of ["1999.1.1", secret, 'invalid"metadata', 'broken"source\\suffix']) { + // Model damaged installed metadata at the transport boundary, retaining the real verifier. + mkdirSync(join(cwd, ".agents/pm/extensions/pm-github"), { recursive: true }); + writeFileSync(join(cwd, ".agents/pm/extensions/pm-github/package.json"), `{"version":"${installed}"}`); + writeFileSync(join(cwd, "bin/pm"), "#!/usr/bin/env bash\nexit 0\n", { mode: 0o755 }); + const result = bash(cwd, shell(source, "Install pinned pm-github extension"), env); assert.notEqual(result.status, 0); const output = result.stdout + result.stderr; - assert.ok(output.includes(installed === secret - ? `::error::Installed pm-github version differs from resolved workflow version ${version}.` - : "::error::Cannot read installed pm-github version")); + assert.ok(output.includes(installed === "1999.1.1" + ? `::error::Installed pm-github version 1999.1.1 differs from resolved workflow version ${version}.` + : installed === secret ? `::error::Installed pm-github version differs from resolved workflow version ${version}.` + : "::error::Cannot read installed pm-github version")); assert.ok(!output.includes(secret)); - for (const representation of [installed, JSON.stringify(installed).slice(1, -1)]) { - assert.ok(!output.includes(representation), "every rejected metadata value and its serialized form must remain private"); - } - assert.ok(!(result.stdout + result.stderr).includes(cwd), "module/parser diagnostics must not expose the tracker path"); + assert.ok(!output.includes(cwd), "parser diagnostics must not expose the tracker path"); } })); - test(`${kind} binds documented job identity and omits deprecated template input`, () => { + test(`${kind} binds job identity, preflights before writes, and omits template input`, () => { assert.match(source, /WORKFLOW_SHA: \$\{\{ job.workflow_sha \}\}/); assert.match(source, /REQUESTED_PM_GITHUB_VERSION: \$\{\{ inputs.pm-github-version \}\}/); assert.match(source, /pm-github-version:\n description: [^\n]+\n type: string\n required: false\n default: ""/); + assert.ok(source.indexOf("- name: Resolve pm-github release version") < source.indexOf("- name: Checkout caller default branch")); assert.doesNotMatch(shell(source, "Resolve pm-github release version"), /\$\{\{/); const caller = readFileSync(join(root, `docs/${kind}-workflow-caller.yml`), "utf8"); assert.doesNotMatch(caller, /pm-github-version/); @@ -151,44 +219,18 @@ test("both reusable workflows execute identical version resolution", () => { shell(readFileSync(join(root, files[1]), "utf8"), "Resolve pm-github release version")); }); -/** Every tag the release plan can emit, including zero-padded dates and -N suffixes. */ -const plannedTodays = ["2026.11.02", "2026.09.01", "2026.10.19", "2027.01.01-2"]; - -/** Resolve one planned tag through a real workflow step and return its exported version. */ -function resolvePlannedTag(cwd: string, file: string, tag: string): string { - const env: NodeJS.ProcessEnv = { ...setup(cwd), REMOTE_TAGS: `${workflowSha}\trefs/tags/${tag}`, - REQUESTED_PM_GITHUB_VERSION: "", WORKFLOW_SHA: workflowSha, REMOTE_STATUS: "0", RUNNER_TEMP: cwd }; - const script = shell(readFileSync(join(root, file), "utf8"), "Resolve pm-github release version"); - const result = bash(cwd, script, env); - assert.equal(result.status, 0, result.stdout + result.stderr); - return readFileSync(env.GITHUB_ENV!, "utf8"); -} - -test("workflow resolution equals the release plan's npm version for every planned tag", () => { - for (const today of plannedTodays) { +test("workflow manifest normalization equals the release plan npm version", () => { + for (const today of ["2026.11.04", "2026.09.01", "2026.10.19", "2027.01.01-2"]) { const plan = decideDailyRelease({ existingPrs: [], mainSha: "new-main", lastRelease: null, today }); - for (const file of files) { - fixture(cwd => { - assert.equal(resolvePlannedTag(cwd, file, plan.tag), `PM_GITHUB_VERSION=${plan.npm_version}\n`, - `${plan.tag} must resolve to the npm version the release plan publishes`); - }); - } + for (const file of files) fixture(cwd => { + const env: NodeJS.ProcessEnv = { ...setup(cwd), WORKFLOW_MANIFEST: JSON.stringify({ name: "pm-github", version: plan.tag.slice(1) }) }; + const result = bash(cwd, shell(readFileSync(join(root, file), "utf8"), "Resolve pm-github release version"), env); + assert.equal(result.status, 0, result.stdout + result.stderr); + assert.equal(readFileSync(env.GITHUB_ENV!, "utf8"), `PM_GITHUB_VERSION=${plan.npm_version}\n`); + }); } }); -test("the padded-tag rows fail when the tag normalization is removed", () => fixture(cwd => { - const original = shell(readFileSync(join(root, files[0]), "utf8"), "Resolve pm-github release version"); - const unnormalized = original.replace('candidate="$(normalize_pm_github_version "${candidate}")"', - 'candidate="${candidate}"'); - assert.notEqual(unnormalized, original, "the normalization step must exist to be removed"); - const env: NodeJS.ProcessEnv = { ...setup(cwd), REMOTE_TAGS: `${workflowSha}\trefs/tags/v2026.11.02`, - REQUESTED_PM_GITHUB_VERSION: "", WORKFLOW_SHA: workflowSha, REMOTE_STATUS: "0", RUNNER_TEMP: cwd }; - const result = bash(cwd, unnormalized, env); - assert.equal(result.status, 0, result.stdout + result.stderr); - assert.equal(readFileSync(env.GITHUB_ENV!, "utf8"), "PM_GITHUB_VERSION=2026.11.02\n", - "without normalization the padded tag resolves to the padded version, failing the row that expects 2026.11.2"); -})); - test("release retains token behavior and never stamps or commits reusable workflows", () => { assert.doesNotMatch(release, /release_token|RELEASE_APP_|release-stamped|Stamp the workflow/); assert.match(release, /GH_TOKEN: \$\{\{ github.token \}\}/); @@ -197,7 +239,6 @@ test("release retains token behavior and never stamps or commits reusable workfl } }); -/** Keep the two independent version-discovery requirements explicit in caller documentation. */ test("version discovery documents App-free lookup and no release-time stamping", () => { const readme = readFileSync(join(root, "README.md"), "utf8").replace(/\s+/g, " "); assert.match(readme, /Release version discovery needs no GitHub App, and no release-time workflow stamping is required\./);