diff --git a/.agents/pm/history/pm-github-7c95.jsonl b/.agents/pm/history/pm-github-7c95.jsonl new file mode 100644 index 00000000..943367da --- /dev/null +++ b/.agents/pm/history/pm-github-7c95.jsonl @@ -0,0 +1,20 @@ +{"hash_algorithm":"sha256","ts":"2026-10-17T09:29:26.509Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","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-7c95"},{"op":"add","path":"/metadata/title","value":"Keep reusable workflow operands out of executable positions in attestation scans"},{"op":"add","path":"/metadata/description","value":"Dependabot PR 135 exposes unsupported Bash forms in the canonical strict attestation scanner: array element assignments, split compound tests and dynamic case patterns are misread as executables."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":1},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-10-17T09:29:26.509Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-17T09:29:26.509Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"Equivalent command-argument forms retain workflow safety and pass canonical attestation before and after the exact Dependabot toolchain diff, without pinning or filtering auditor findings."}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"7cc12a2b0c99e4b2591dea2642aec4616f2950ce4dfbdcb85553dfefa7ced2fd","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":"ea0c68067a4e8e3d5b9b4e71d64126402e57119af31e1d8f6e0c77abe81ec5c3"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:30:43.136Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:30:43.136Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"7cc12a2b0c99e4b2591dea2642aec4616f2950ce4dfbdcb85553dfefa7ced2fd","after_hash":"1994d23f0590ce9e37d8dc3a6aaf5b55a252e2179ea987ad8143162bf344b564","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":"25f077fd46ce9df32bcd3a33efd5b0055d797a6eea7327c6136eed65213687ab"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:30:51.392Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:30:51.392Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"1994d23f0590ce9e37d8dc3a6aaf5b55a252e2179ea987ad8143162bf344b564","after_hash":"7178dd3f675b7e12de2a501a4078742583dab5fbb2bb54bf97703bc0444c185a","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":"71e0c486493a464a49fa6911d3e577a76e30e60c74c2fbb483c8b86508466722"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:31:04.941Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:31:04.941Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-17T09:31:04.941Z","author":"codex-agent","text":"PR 135 job 117228846323 fails this repository own workflows attestation test, not coverage self-tests. The newer canonical auditor reads Bash element assignments, multiline array data, multiline compound operands and dynamic case patterns as unresolved executable positions. Preserve the canonical auditor identity and findings: express dynamic values as arguments to declare, printf/mapfile and grep, keep compound tests on one line and use conditional path tests. No gate filter, exemption or dependency pin workaround."}]}],"before_hash":"7178dd3f675b7e12de2a501a4078742583dab5fbb2bb54bf97703bc0444c185a","after_hash":"a3513e9700cb2bfc7d047984f09188ba119feb8bafc50d44762e5eee7cdb8e43","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":"4a6f45a002c5bbbc03dfc880f5d7344fefa1f6b5917302c7532f76d984af2c43"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:37:40.163Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-17T09:37:40.162Z","author":"codex-agent","text":"Exact Dependabot PR 135 diff applied temporarily over this branch, including its launcher refresh; npm ci installs pm-ops 2026.10.12. Fixed workflows produce zero unresolved findings with the unchanged canonical auditor. Release-tag safety is 42/42 and literal-scope tests are 4/4. Validate all tag text before associative-array indexing; reject multiline versions as whole values. Revert proof targets only workflow shell under the dependency overlay; dependency changes are not part of the final fix."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:37:40.163Z"}],"before_hash":"a3513e9700cb2bfc7d047984f09188ba119feb8bafc50d44762e5eee7cdb8e43","after_hash":"4b2d99e2c9ef701b866e7e2ba0bbc5be92aa87c2d801d92f9a92b4d375154946","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":"85e51dccb0b20f45b366af175498437478557026166b65b3c8aa56ad612cf1b8"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:37:46.073Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:37:46.073Z"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/pm-github-sync.yml","scope":"project"}]}],"before_hash":"4b2d99e2c9ef701b866e7e2ba0bbc5be92aa87c2d801d92f9a92b4d375154946","after_hash":"fd9e8cae6e90f79c0376c93a623a01eb918e1065f39fcf11a7fb85dd0bd68c89","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":"4367780d3bc5d24655c3b1a73bb28f7daaa0cd3ea4ee8c663fcd3b2601a62aec"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:37:52.567Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","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/0/path","value":".github/workflows/pm-github-closeout.yml"},{"op":"add","path":"/metadata/files/1","value":{"path":".github/workflows/pm-github-sync.yml","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:37:52.567Z"}],"before_hash":"fd9e8cae6e90f79c0376c93a623a01eb918e1065f39fcf11a7fb85dd0bd68c89","after_hash":"0d62f70e5fe8b269488284a8d62b9b357eaf771b193084cd44af292da34fbe52","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":"0310ca74fa9002f383b810fd5aeeefbefc47e5ff7d0dd52ced23493cee2c8299"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:37:58.112Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","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/2","value":{"path":"test/attestation-workflow-operands.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:37:58.112Z"}],"before_hash":"0d62f70e5fe8b269488284a8d62b9b357eaf771b193084cd44af292da34fbe52","after_hash":"fa18522d50a7d733aeecde0922e83c9d159d5e43889f385f74890ee97fb947f7","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":"02af0b1b14adc11ebb3972a04d8176a57ab6bcf07e34c9d0e0f6fad4bc84c74b"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:38:05.786Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","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/3","value":{"path":"test/self-versioned-workflows.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:38:05.786Z"}],"before_hash":"fa18522d50a7d733aeecde0922e83c9d159d5e43889f385f74890ee97fb947f7","after_hash":"d708e5f9abca36d67a7329c218a96924e90ba786f1077620599e9bb265fbfa7d","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":"9a4ae90eeb04842c63f195e26fd9b2910a60096526238624f1f97b2e0cbb6a6b"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:38:13.927Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:38:13.927Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/attestation-workflow-operands.md","scope":"project"}]}],"before_hash":"d708e5f9abca36d67a7329c218a96924e90ba786f1077620599e9bb265fbfa7d","after_hash":"e2316c47d459bbeaffaf889267d260593756952079994ab4d0172cc9471f837f","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":"33f1284e5fb124ca5b8095638b511802360d567c2a0d23e11daed2fda66a06a6"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:38:20.765Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:38:20.765Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/attestation-workflow-operands.test.ts test/self-versioned-workflows.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-17T09:38:20.576Z","source_kind":"local_mutation","source_ref":"fix/reusable-shell-attestation-compatibility"}}]}],"before_hash":"e2316c47d459bbeaffaf889267d260593756952079994ab4d0172cc9471f837f","after_hash":"9b19b7779844187e694394e29b30381a12973b1ac8c0cd70106c1b4190616e16","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":"1d443bc201609ad37065409e72b9efb03c72f2d1debb4953ea6e71a41c7fb29e"} +{"hash_algorithm":"sha256","ts":"2026-10-17T09:45:59.992Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-17T09:45:59.992Z","author":"codex-agent","text":"Behavioural revert proof with the exact Dependabot overlay installed: restoring only the two reusable workflows to origin/main makes the repository attestation regression fail with 38 unresolved executable findings; restoring the fix passes 1/1 with zero skips. The unchanged canonical auditor is used throughout. The focused version/scope suites pass 42/42 and 4/4. Full npm and Bun CI-identity release gates are running with the overlay."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T09:45:59.992Z"}],"before_hash":"9b19b7779844187e694394e29b30381a12973b1ac8c0cd70106c1b4190616e16","after_hash":"4234405eb04d8f4552295cc4b0a54efb0ce330f15a637a28a98a0c4089e539f8","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":"96ebd591e74185ea5f940279755132ba189d760b0d66d90c07b146447a8b07fa"} +{"hash_algorithm":"sha256","ts":"2026-10-17T10:04:45.270Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-17T10:04:45.270Z","author":"codex-agent","text":"The first full overlay gate attempt was stopped after two existing timing probes failed under shared-host contention: marker extraction exceeded its unchanged 10-second limit and the watchdog child missed readiness before its unchanged 500ms deadline (related existing item pm-github-sah1). The parallel bot-PR run passed both. Retrying the complete npm/Bun gates with CPU affinity limiting Node test-worker fan-out; no assertions, thresholds, sources or tests are removed or relaxed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T10:04:45.270Z"}],"before_hash":"4234405eb04d8f4552295cc4b0a54efb0ce330f15a637a28a98a0c4089e539f8","after_hash":"8e86c4cfaf49dcc7d026d8e461c3ffd26f6ce8959694e881e0184f8d0ca208b1","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":"d65a2adcc394664480c4c6e89864e3fc970986231a39e823ee0b925fe026c586"} +{"hash_algorithm":"sha256","ts":"2026-10-17T12:28:19.345Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","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/prepare-merge-driver.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-10-17T12:28:19.345Z"}],"before_hash":"8e86c4cfaf49dcc7d026d8e461c3ffd26f6ce8959694e881e0184f8d0ca208b1","after_hash":"461858ad7aeadbf70964350e69049c18a9c0949877fc3c329a4e1c7bad70369b","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":"546580a4acc9bb8feb88b27d7a1b6d86208187dbad60d5cad5c1eaabe3d364ac"} +{"hash_algorithm":"sha256","ts":"2026-10-17T12:28:27.699Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-10-17T12:28:27.698Z","author":"codex-agent","text":"Exact Dependabot overlay suite passed 764/764 with zero skips, but the unchanged 100% coverage gate correctly found its refreshed prepare launcher artifact-time branch unexercised (99.99% statements/lines, 99.96% branches). Add real Git pack/publish omission-path regressions: absent development tools must produce one notice and leave Git config/attributes unchanged. This property supports both canonical launcher generations without version pin assertions, exemptions, or threshold changes. Repeat complete npm and Bun gates after the test addition."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T12:28:27.699Z"}],"before_hash":"461858ad7aeadbf70964350e69049c18a9c0949877fc3c329a4e1c7bad70369b","after_hash":"e22b522c2a253822929d1a8de086848dd9e1cb02219f27a3269f7795b32afe2b","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":"c5f718976f8a5edaf635b2fef7ac1774539c3999ff53544556f2a641e5b236c7"} +{"hash_algorithm":"sha256","ts":"2026-10-17T12:34:20.667Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","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/4/path","value":"test/prepare-merge-driver.test.ts"},{"op":"replace","path":"/metadata/files/3/path","value":"test/import-gate.test.ts"},{"op":"add","path":"/metadata/files/5","value":{"path":"test/self-versioned-workflows.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T12:34:20.667Z"}],"before_hash":"e22b522c2a253822929d1a8de086848dd9e1cb02219f27a3269f7795b32afe2b","after_hash":"11b15a5f47e10273c6a52b71e26483cc82f4e1f74ab0244467ab4ea9ad9b2bc6","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":"2b4f0cf0304d2c71b1db8c013dd3d0634fcd240ca48fd3ad53e5cfd160a14096"} +{"hash_algorithm":"sha256","ts":"2026-10-17T12:34:27.943Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","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/prepare-merge-driver.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-17T12:34:27.650Z","source_kind":"local_mutation","source_ref":"fix/reusable-shell-attestation-compatibility"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T12:34:27.943Z"}],"before_hash":"11b15a5f47e10273c6a52b71e26483cc82f4e1f74ab0244467ab4ea9ad9b2bc6","after_hash":"01b3cc1d27b119a28833d1d4984ba5d1b2d46c3254de45385dd0455278121c65","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":"1864702e8e9da06bfb99bf72f71d80cc6434b0bd732eaf1bfc0cfb2e438abc32"} +{"hash_algorithm":"sha256","ts":"2026-10-17T12:34:35.866Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-10-17T12:34:35.866Z","author":"codex-agent","text":"Focused launcher suite passes 10/10 with zero skips and measures exactly 100% statements, branches, functions and lines under the complete Dependabot overlay. The two artifact omission properties also pass 2/2 against the original canonical launcher. Keep both canonical-copy identity and all integrity/install-failure checks; this closes the overlay coverage gap without a dependency/version workaround."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T12:34:35.866Z"}],"before_hash":"01b3cc1d27b119a28833d1d4984ba5d1b2d46c3254de45385dd0455278121c65","after_hash":"75c33b54b681354b0e4345695caca6951c36343ecdf1c4f3eb40e3a9c73a9c22","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":"60999f33208a62cf5501bcdd8c1df939994a6e097ec5bba324d23caa1cec61ea"} +{"hash_algorithm":"sha256","ts":"2026-10-17T14:17:09.070Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-10-17T14:17:09.070Z","author":"codex-agent","text":"Final verification PASS: exact complete Dependabot 135 diff (SHA-256 c72710900b72ae73365b959aaf184821d1d49417f64434509a0f120d31af3aea) applied byte-identically. npm run release:check and bun run release:check each exit 0 with isolated CI identity, 766/766 tests, zero failures/skips, all 13 authored modules at exact 100% statements/branches/functions/lines. Linked pm tests pass 46/46 workflow operands/tags plus 10/10 launcher cases. Reverting only original workflows under the overlay reproduces 38 canonical findings and fails 1/1; fixed passes 1/1. Temporary dependency/launcher changes are now restored, npm ci succeeds with original pins, and baseline-focused compatibility passes 64/64. No merge, release, or caller dispatch; PR 142 remains separate."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T14:17:09.070Z"}],"before_hash":"75c33b54b681354b0e4345695caca6951c36343ecdf1c4f3eb40e3a9c73a9c22","after_hash":"347b9de59f64c9eb55d78086e2900535ef1cec363e15c01aa395594c047cd27a","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":"57e31278d6d241cb90d59cfe378979f2755db22b04580a93397867f52752e80d"} +{"hash_algorithm":"sha256","ts":"2026-10-17T14:17:09.775Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e6c75eb6477900f1d6cb8502","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-17T14:17:09.775Z"}],"before_hash":"347b9de59f64c9eb55d78086e2900535ef1cec363e15c01aa395594c047cd27a","after_hash":"11428f54c3b51f5484ad60b08a539ca8f458e5fc4f0f555b6893a7df849418ca","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":"984e8c27275a9780f63d1cda8eb8eb3b513cd196886ab7613d46cda31aaeca8a"} diff --git a/.agents/pm/issues/pm-github-7c95.toon b/.agents/pm/issues/pm-github-7c95.toon new file mode 100644 index 00000000..09e2a549 --- /dev/null +++ b/.agents/pm/issues/pm-github-7c95.toon @@ -0,0 +1,32 @@ +id: pm-github-7c95 +title: Keep reusable workflow operands out of executable positions in attestation scans +description: "Dependabot PR 135 exposes unsupported Bash forms in the canonical strict attestation scanner: array element assignments, split compound tests and dynamic case patterns are misread as executables." +type: Issue +status: in_progress +priority: 1 +tags: [] +created_at: "2026-10-17T09:29:26.509Z" +updated_at: "2026-10-17T14:17:09.775Z" +author: codex-agent +acceptance_criteria: "Equivalent command-argument forms retain workflow safety and pass canonical attestation before and after the exact Dependabot toolchain diff, without pinning or filtering auditor findings." +comments[7]{created_at,author,text}: + "2026-10-17T09:31:04.941Z",codex-agent,"PR 135 job 117228846323 fails this repository own workflows attestation test, not coverage self-tests. The newer canonical auditor reads Bash element assignments, multiline array data, multiline compound operands and dynamic case patterns as unresolved executable positions. Preserve the canonical auditor identity and findings: express dynamic values as arguments to declare, printf/mapfile and grep, keep compound tests on one line and use conditional path tests. No gate filter, exemption or dependency pin workaround." + "2026-10-17T09:37:40.162Z",codex-agent,"Exact Dependabot PR 135 diff applied temporarily over this branch, including its launcher refresh; npm ci installs pm-ops 2026.10.12. Fixed workflows produce zero unresolved findings with the unchanged canonical auditor. Release-tag safety is 42/42 and literal-scope tests are 4/4. Validate all tag text before associative-array indexing; reject multiline versions as whole values. Revert proof targets only workflow shell under the dependency overlay; dependency changes are not part of the final fix." + "2026-10-17T09:45:59.992Z",codex-agent,"Behavioural revert proof with the exact Dependabot overlay installed: restoring only the two reusable workflows to origin/main makes the repository attestation regression fail with 38 unresolved executable findings; restoring the fix passes 1/1 with zero skips. The unchanged canonical auditor is used throughout. The focused version/scope suites pass 42/42 and 4/4. Full npm and Bun CI-identity release gates are running with the overlay." + "2026-10-17T10:04:45.270Z",codex-agent,"The first full overlay gate attempt was stopped after two existing timing probes failed under shared-host contention: marker extraction exceeded its unchanged 10-second limit and the watchdog child missed readiness before its unchanged 500ms deadline (related existing item pm-github-sah1). The parallel bot-PR run passed both. Retrying the complete npm/Bun gates with CPU affinity limiting Node test-worker fan-out; no assertions, thresholds, sources or tests are removed or relaxed." + "2026-10-17T12:28:27.698Z",codex-agent,"Exact Dependabot overlay suite passed 764/764 with zero skips, but the unchanged 100% coverage gate correctly found its refreshed prepare launcher artifact-time branch unexercised (99.99% statements/lines, 99.96% branches). Add real Git pack/publish omission-path regressions: absent development tools must produce one notice and leave Git config/attributes unchanged. This property supports both canonical launcher generations without version pin assertions, exemptions, or threshold changes. Repeat complete npm and Bun gates after the test addition." + "2026-10-17T12:34:35.866Z",codex-agent,"Focused launcher suite passes 10/10 with zero skips and measures exactly 100% statements, branches, functions and lines under the complete Dependabot overlay. The two artifact omission properties also pass 2/2 against the original canonical launcher. Keep both canonical-copy identity and all integrity/install-failure checks; this closes the overlay coverage gap without a dependency/version workaround." + "2026-10-17T14:17:09.070Z",codex-agent,"Final verification PASS: exact complete Dependabot 135 diff (SHA-256 c72710900b72ae73365b959aaf184821d1d49417f64434509a0f120d31af3aea) applied byte-identically. npm run release:check and bun run release:check each exit 0 with isolated CI identity, 766/766 tests, zero failures/skips, all 13 authored modules at exact 100% statements/branches/functions/lines. Linked pm tests pass 46/46 workflow operands/tags plus 10/10 launcher cases. Reverting only original workflows under the overlay reproduces 38 canonical findings and fails 1/1; fixed passes 1/1. Temporary dependency/launcher changes are now restored, npm ci succeeds with original pins, and baseline-focused compatibility passes 64/64. No merge, release, or caller dispatch; PR 142 remains separate." +files[6]{path,scope}: + .github/workflows/pm-github-closeout.yml,project + .github/workflows/pm-github-sync.yml,project + test/attestation-workflow-operands.test.ts,project + test/import-gate.test.ts,project + test/prepare-merge-driver.test.ts,project + test/self-versioned-workflows.test.ts,project +tests[2]{command,scope,provenance{author,created_at,source_kind,source_ref}}: + node --test test/attestation-workflow-operands.test.ts test/self-versioned-workflows.test.ts,project,codex-agent,"2026-10-17T09:38:20.576Z",local_mutation,fix/reusable-shell-attestation-compatibility + node --test test/prepare-merge-driver.test.ts,project,codex-agent,"2026-10-17T12:34:27.650Z",local_mutation,fix/reusable-shell-attestation-compatibility +docs[1]{path,scope}: + docs/attestation-workflow-operands.md,project +body: "" diff --git a/.github/workflows/pm-github-closeout.yml b/.github/workflows/pm-github-closeout.yml index 852ae4e9..e8e55c6a 100644 --- a/.github/workflows/pm-github-closeout.yml +++ b/.github/workflows/pm-github-closeout.yml @@ -104,10 +104,13 @@ jobs: 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 - peeled_commits["${ref%'^{}'}"]="${sha}" + declare "peeled_commits[${ref%'^{}'}]=${sha}" elif [[ "${ref}" == refs/tags/v* ]]; then - tag_commits["${ref}"]="${sha}" + declare "tag_commits[${ref}]=${sha}" fi done <<< "${remote_tags}" PM_GITHUB_VERSION="" @@ -115,7 +118,7 @@ jobs: # 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}" - [[ "${candidate}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]] || continue + grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${candidate}") || continue 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 @@ -132,7 +135,7 @@ jobs: elif [[ -n "${REQUESTED_PM_GITHUB_VERSION}" && "${REQUESTED_PM_GITHUB_VERSION}" != "${PM_GITHUB_VERSION}" ]]; then echo "::warning::pm-github-version is deprecated and differs from the workflow release tag; using the tag version." fi - [[ "${PM_GITHUB_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]] || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } + 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; } printf 'PM_GITHUB_VERSION=%s\n' "${PM_GITHUB_VERSION}" >> "${GITHUB_ENV}" - name: Validate closeout inputs @@ -140,7 +143,7 @@ jobs: run: | set -euo pipefail [[ "${REPOSITORY}" == "${CALLING_REPOSITORY}" ]] || { echo "::error::repository must equal the calling repository (${CALLING_REPOSITORY})."; exit 1; } - [[ "${PM_GITHUB_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]] || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } + 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; } [[ "${SYNC_BRANCH}" == pm-github/closeout ]] || { echo "::error::Closeout branch must be pm-github/closeout."; exit 1; } git check-ref-format "refs/heads/${SYNC_BRANCH}" [[ "${SYNC_BRANCH}" != "${DEFAULT_BRANCH}" ]] || { echo "::error::The closeout bot branch cannot be the caller default branch."; exit 1; } @@ -211,11 +214,13 @@ jobs: export PM_SYNC_TRACKER printf 'PM_SYNC_TRACKER=%s\n' "${PM_SYNC_TRACKER}" >> "${GITHUB_ENV}" cat > "${RUNNER_TEMP}/pm-github-sync-scope.sh" <<'SCOPE' - sync_scope=(":(top,literal)${PM_SYNC_TRACKER}" ':(top,literal)CHANGELOG.md' - ":(top,exclude,literal)${PM_SYNC_TRACKER}/extensions" - ":(top,exclude,literal)${PM_SYNC_TRACKER}/locks" - ":(top,exclude,literal)${PM_SYNC_TRACKER}/checkpoints" - ":(top,exclude,literal)${PM_SYNC_TRACKER}/settings.json" + mapfile -t sync_scope < <(printf '%s\n' \ + ":(top,literal)${PM_SYNC_TRACKER}" \ + ':(top,literal)CHANGELOG.md' \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/extensions" \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/locks" \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/checkpoints" \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/settings.json" \ ":(top,exclude,literal)${PM_SYNC_TRACKER}/history/_workspace.jsonl") restore_sync_setup() { for setup_path in "${PM_SYNC_TRACKER}/settings.json" "${PM_SYNC_TRACKER}/history/_workspace.jsonl"; do @@ -242,10 +247,13 @@ jobs: return "${failed}" } check_sync_path() { - case "$1" in - "${PM_SYNC_TRACKER}"/extensions|"${PM_SYNC_TRACKER}"/extensions/*|"${PM_SYNC_TRACKER}"/locks|"${PM_SYNC_TRACKER}"/locks/*|"${PM_SYNC_TRACKER}"/checkpoints|"${PM_SYNC_TRACKER}"/checkpoints/*) ;; - "${PM_SYNC_TRACKER}"/*|CHANGELOG.md) return 0 ;; - esac + if [[ "$1" == "${PM_SYNC_TRACKER}/extensions" || "$1" == "${PM_SYNC_TRACKER}/extensions/"* || \ + "$1" == "${PM_SYNC_TRACKER}/locks" || "$1" == "${PM_SYNC_TRACKER}/locks/"* || \ + "$1" == "${PM_SYNC_TRACKER}/checkpoints" || "$1" == "${PM_SYNC_TRACKER}/checkpoints/"* ]]; then + : # Runtime paths are deliberately outside the publication allowlist. + elif [[ "$1" == "${PM_SYNC_TRACKER}/"* || "$1" == CHANGELOG.md ]]; then + return 0 + fi printf '::error::Changed path outside sync commit scope: %q\n' "$1" return 1 } @@ -381,15 +389,13 @@ jobs: links_file="${RUNNER_TEMP}/pm-github-sync-links.md" : > "${links_file}" while IFS= read -r changed; do - case "${changed}" in - "${PM_SYNC_TRACKER}"/*/*.toon) - item_id="${changed##*/}" - item_id="${item_id%.toon}" - # The default-branch link is the permanent one after merge; the branch link - # shows the proposed item while the review PR is open. - printf -- '- [%s](https://github.com/%s/blob/%s/%s) ([proposed](https://github.com/%s/blob/%s/%s))\n' "${item_id}" "${REPOSITORY}" "${DEFAULT_BRANCH}" "${changed}" "${REPOSITORY}" "${SYNC_BRANCH}" "${changed}" >> "${links_file}" - ;; - esac + if [[ "${changed}" == "${PM_SYNC_TRACKER}/"*/*.toon ]]; then + item_id="${changed##*/}" + item_id="${item_id%.toon}" + # The default-branch link is the permanent one after merge; the branch link + # shows the proposed item while the review PR is open. + printf -- '- [%s](https://github.com/%s/blob/%s/%s) ([proposed](https://github.com/%s/blob/%s/%s))\n' "${item_id}" "${REPOSITORY}" "${DEFAULT_BRANCH}" "${changed}" "${REPOSITORY}" "${SYNC_BRANCH}" "${changed}" >> "${links_file}" + fi done < <(git diff --cached --name-only -- "${sync_scope[0]}" "${sync_scope[@]:2}") if [[ ! -s "${links_file}" ]]; then echo "::error::Closeout changed tracker state but no item link could be generated." diff --git a/.github/workflows/pm-github-sync.yml b/.github/workflows/pm-github-sync.yml index 96de84ef..5f33ea2e 100644 --- a/.github/workflows/pm-github-sync.yml +++ b/.github/workflows/pm-github-sync.yml @@ -115,10 +115,13 @@ jobs: 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 - peeled_commits["${ref%'^{}'}"]="${sha}" + declare "peeled_commits[${ref%'^{}'}]=${sha}" elif [[ "${ref}" == refs/tags/v* ]]; then - tag_commits["${ref}"]="${sha}" + declare "tag_commits[${ref}]=${sha}" fi done <<< "${remote_tags}" PM_GITHUB_VERSION="" @@ -126,7 +129,7 @@ jobs: # 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}" - [[ "${candidate}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]] || continue + grep -zEq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' < <(printf '%s\0' "${candidate}") || continue 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 @@ -143,7 +146,7 @@ jobs: elif [[ -n "${REQUESTED_PM_GITHUB_VERSION}" && "${REQUESTED_PM_GITHUB_VERSION}" != "${PM_GITHUB_VERSION}" ]]; then echo "::warning::pm-github-version is deprecated and differs from the workflow release tag; using the tag version." fi - [[ "${PM_GITHUB_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]] || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } + 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; } printf 'PM_GITHUB_VERSION=%s\n' "${PM_GITHUB_VERSION}" >> "${GITHUB_ENV}" - name: Validate sync inputs @@ -151,7 +154,7 @@ jobs: run: | set -euo pipefail [[ "${REPOSITORY}" == "${CALLING_REPOSITORY}" ]] || { echo "::error::repository must equal the calling repository (${CALLING_REPOSITORY})."; exit 1; } - [[ "${PM_GITHUB_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]] || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } + 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; } [[ "${SYNC_BRANCH}" == automation/* ]] || { echo "::error::sync-branch must start with automation/."; exit 1; } git check-ref-format "refs/heads/${SYNC_BRANCH}" [[ -z "${POST_IMPORT_SCRIPT:-}" || "${POST_IMPORT_SCRIPT:-}" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.:-]*$ ]] || { echo "::error::post-import-script must be an npm script name, not a shell command."; exit 1; } @@ -221,11 +224,13 @@ jobs: export PM_SYNC_TRACKER printf 'PM_SYNC_TRACKER=%s\n' "${PM_SYNC_TRACKER}" >> "${GITHUB_ENV}" cat > "${RUNNER_TEMP}/pm-github-sync-scope.sh" <<'SCOPE' - sync_scope=(":(top,literal)${PM_SYNC_TRACKER}" ':(top,literal)CHANGELOG.md' - ":(top,exclude,literal)${PM_SYNC_TRACKER}/extensions" - ":(top,exclude,literal)${PM_SYNC_TRACKER}/locks" - ":(top,exclude,literal)${PM_SYNC_TRACKER}/checkpoints" - ":(top,exclude,literal)${PM_SYNC_TRACKER}/settings.json" + mapfile -t sync_scope < <(printf '%s\n' \ + ":(top,literal)${PM_SYNC_TRACKER}" \ + ':(top,literal)CHANGELOG.md' \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/extensions" \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/locks" \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/checkpoints" \ + ":(top,exclude,literal)${PM_SYNC_TRACKER}/settings.json" \ ":(top,exclude,literal)${PM_SYNC_TRACKER}/history/_workspace.jsonl") restore_sync_setup() { for setup_path in "${PM_SYNC_TRACKER}/settings.json" "${PM_SYNC_TRACKER}/history/_workspace.jsonl"; do @@ -252,10 +257,13 @@ jobs: return "${failed}" } check_sync_path() { - case "$1" in - "${PM_SYNC_TRACKER}"/extensions|"${PM_SYNC_TRACKER}"/extensions/*|"${PM_SYNC_TRACKER}"/locks|"${PM_SYNC_TRACKER}"/locks/*|"${PM_SYNC_TRACKER}"/checkpoints|"${PM_SYNC_TRACKER}"/checkpoints/*) ;; - "${PM_SYNC_TRACKER}"/*|CHANGELOG.md) return 0 ;; - esac + if [[ "$1" == "${PM_SYNC_TRACKER}/extensions" || "$1" == "${PM_SYNC_TRACKER}/extensions/"* || \ + "$1" == "${PM_SYNC_TRACKER}/locks" || "$1" == "${PM_SYNC_TRACKER}/locks/"* || \ + "$1" == "${PM_SYNC_TRACKER}/checkpoints" || "$1" == "${PM_SYNC_TRACKER}/checkpoints/"* ]]; then + : # Runtime paths are deliberately outside the publication allowlist. + elif [[ "$1" == "${PM_SYNC_TRACKER}/"* || "$1" == CHANGELOG.md ]]; then + return 0 + fi printf '::error::Changed path outside sync commit scope: %q\n' "$1" return 1 } @@ -409,15 +417,13 @@ jobs: links_file="${RUNNER_TEMP}/pm-github-sync-links.md" : > "${links_file}" while IFS= read -r changed; do - case "${changed}" in - "${PM_SYNC_TRACKER}"/*/*.toon) - item_id="${changed##*/}" - item_id="${item_id%.toon}" - # The default-branch link is the permanent one after merge; the branch link - # shows the proposed item while the review PR is open. - printf -- '- [%s](https://github.com/%s/blob/%s/%s) ([proposed](https://github.com/%s/blob/%s/%s))\n' "${item_id}" "${REPOSITORY}" "${DEFAULT_BRANCH}" "${changed}" "${REPOSITORY}" "${SYNC_BRANCH}" "${changed}" >> "${links_file}" - ;; - esac + if [[ "${changed}" == "${PM_SYNC_TRACKER}/"*/*.toon ]]; then + item_id="${changed##*/}" + item_id="${item_id%.toon}" + # The default-branch link is the permanent one after merge; the branch link + # shows the proposed item while the review PR is open. + printf -- '- [%s](https://github.com/%s/blob/%s/%s) ([proposed](https://github.com/%s/blob/%s/%s))\n' "${item_id}" "${REPOSITORY}" "${DEFAULT_BRANCH}" "${changed}" "${REPOSITORY}" "${SYNC_BRANCH}" "${changed}" >> "${links_file}" + fi done < <(git diff --cached --name-only -- "${sync_scope[0]}" "${sync_scope[@]:2}") if [[ ! -s "${links_file}" ]]; then echo "::error::The sync changed tracker state but no item link could be generated." diff --git a/docs/attestation-workflow-operands.md b/docs/attestation-workflow-operands.md new file mode 100644 index 00000000..83ec4ec5 --- /dev/null +++ b/docs/attestation-workflow-operands.md @@ -0,0 +1,34 @@ +# Reusable workflow attestation compatibility + +Dependabot PR #135 updates pm-ops and pm-changelog. Its actual failing test is +`this repository's own workflows pass the gate` in +`test/verify-release-publish-attestation.test.ts`, not the expected coverage-gate +self-test diagnostics. The canonical auditor reports 38 unresolved executable +positions in the reusable workflows: associative-array element assignments, +regular expressions containing shell delimiters, multiline array literals and +dynamic case-arm patterns. + +Keep the canonical auditor and every finding intact. The workflows now express +those dynamic values as operands to literal commands: declare for array element +bindings, printf/mapfile for pathspec data, grep for the full version ERE, and +conditional tests for path classification. The property is that dynamic data +has a provable argument boundary, rather than depending on a particular version +or path string. NUL-delimited grep rejects multiline values as a whole. + +The publication allowlist, privacy scans, exact release-tag resolution, lease, +no-op guard and protected branches retain their existing behavior. This does not +vendor, filter, exempt or weaken the shared attestation scanner. The dependency +pins remain owned by Dependabot. PR #142 and its branch are outside this change. + +Verification uses the exact PR #135 diff over the fix, including its launcher +refresh, then npm ci and the npm/Bun release gates with CI identity. Reverting +only the changed workflow shell under that overlay must fail the repository +attestation regression. The workflow tests also exercise release-tag ordering, +annotated tags, invalid versions, literal tracker names and rejected runtime +paths. The final fix does not commit the temporary dependency overlay. + +The exact Dependabot overlay also refreshes the canonical prepare launcher. +Artifact-time omission tests exercise pack and publish without development tools, +requiring one explicit notice and unchanged Git config and attributes. They +support both canonical launcher generations without version assertions; the +canonical-copy identity check and all install-failure checks remain enforced. diff --git a/test/attestation-workflow-operands.test.ts b/test/attestation-workflow-operands.test.ts new file mode 100644 index 00000000..a7d94092 --- /dev/null +++ b/test/attestation-workflow-operands.test.ts @@ -0,0 +1,32 @@ +/** Check literal path operands in the exact helper body shipped by both workflows. */ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import test from "node:test"; + +for (const kind of ["sync", "closeout"]) { + const source = readFileSync(resolve(import.meta.dirname, `../.github/workflows/pm-github-${kind}.yml`), "utf8"); + const helper = source.split("<<'SCOPE'\n")[1]!.split("\n SCOPE")[0]!.split("\n").map(line => line.slice(10)).join("\n"); + test(`${kind}: pathspec operands retain literal tracker data and all exclusions`, () => { + for (const tracker of [".agents/pm", "tracker with spaces/[literal]", "tracker$DATA"]) { + const result = spawnSync("bash", ["-c", helper + '\nprintf "%s\\0" "${sync_scope[@]}"'], { + env: { ...process.env, PM_SYNC_TRACKER: tracker }, encoding: "utf8", + }); + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(result.stdout.split("\0").slice(0, -1), [`:(top,literal)${tracker}`, ":(top,literal)CHANGELOG.md", + ...["extensions", "locks", "checkpoints", "settings.json", "history/_workspace.jsonl"].map(path => `:(top,exclude,literal)${tracker}/${path}`)]); + } + }); + test(`${kind}: scope allows only tracker and changelog, refusing runtime data and adjacent prefixes`, () => { + const tracker = "tracker [literal]"; + for (const [path, status] of [[`${tracker}/issues/item.toon`, 0], ["CHANGELOG.md", 0], + [`${tracker}/extensions`, 1], [`${tracker}/extensions/data`, 1], [`${tracker}/locks/file`, 1], + [`${tracker}/checkpoints/file`, 1], [`${tracker}-foreign/item.toon`, 1], ["other.ts", 1]] as const) { + const result = spawnSync("bash", ["-c", helper + '\ncheck_sync_path "$CANDIDATE"'], { + env: { ...process.env, PM_SYNC_TRACKER: tracker, CANDIDATE: path }, encoding: "utf8", + }); + assert.equal(result.status, status, path + result.stderr); + } + }); +} diff --git a/test/import-gate.test.ts b/test/import-gate.test.ts index 9ea08d7b..f9a16329 100644 --- a/test/import-gate.test.ts +++ b/test/import-gate.test.ts @@ -751,7 +751,7 @@ test("the caller default branch drives checkout, reset, PR bases and permanent i /https:\/\/github\.com\/%s\/blob\/%s\/%s/, "the link template accepts the resolved default branch", ); - assert.match(workflow, /"\$\{PM_SYNC_TRACKER\}"\/\*\/\*\.toon/); + assert.match(workflow, /"\$\{PM_SYNC_TRACKER\}\/"\*\/\*\.toon/); assert.match(workflow, /gh pr create/); assert.match(workflow, /gh pr edit/); assert.doesNotMatch(workflow, /\bmain\b/, "no operation or documentation in the reusable workflow hard-codes main"); diff --git a/test/prepare-merge-driver.test.ts b/test/prepare-merge-driver.test.ts index 06b5654e..632e0285 100644 --- a/test/prepare-merge-driver.test.ts +++ b/test/prepare-merge-driver.test.ts @@ -71,9 +71,9 @@ function stubPm(name: string, status: number, body = ""): string { return bin; } -/** Run the launcher as npm's `prepare` hook would: as the entry point, from `cwd`, with `path` as PATH. */ -function prepare(cwd: string, path: string): SpawnSyncReturns { - return spawnSync(nodeExecutable, [launcher], { cwd, encoding: "utf8", env: { ...process.env, PATH: path, NODE_PATH: "" } }); +/** Run npm's prepare entry point with an explicit lifecycle command and PATH. */ +function prepare(cwd: string, path: string, command = "ci"): SpawnSyncReturns { + return spawnSync(nodeExecutable, [launcher], { cwd, encoding: "utf8", env: { ...process.env, npm_command: command, PATH: path, NODE_PATH: "" } }); } /** The merge drivers registered in a checkout's LOCAL Git config, by name. */ @@ -149,3 +149,24 @@ test("an installer killed by a signal fails the install instead of reporting suc const result = prepare(checkout("killed", "pinned"), stubPm("killed", 0, "kill -9 $PPID")); assert.equal(result.status, 1, result.stderr); }); + +for (const command of ["pack", "publish"]) { + test(`${command} without development tools leaves Git configuration untouched and reports the omission`, () => { + const directory = checkout(`artifact-${command}`, "absent"); + /** Read the entire local config so any artifact-time mutation is visible. */ + const config = () => spawnSync("git", ["config", "--local", "--list"], { cwd: directory, encoding: "utf8" }); + const before = config(); + assert.equal(before.status, 0, before.stderr); + const attributes = readFileSync(join(directory, ".gitattributes"), "utf8"); + const result = prepare(directory, hostPath, command); + assert.equal(result.status, 0, result.stderr); + assert.equal(result.stdout, ""); + assert.match(result.stderr, /skipping merge-driver install/); + assert.equal(result.stderr.trim().split("\n").length, 1, "one explicit omission notice"); + const after = config(); + assert.equal(after.status, 0, after.stderr); + assert.equal(after.stdout, before.stdout); + assert.equal(readFileSync(join(directory, ".gitattributes"), "utf8"), attributes); + assert.deepEqual(registeredDrivers(directory), []); + }); +} diff --git a/test/self-versioned-workflows.test.ts b/test/self-versioned-workflows.test.ts index f5dc0aea..b7f5e9dd 100644 --- a/test/self-versioned-workflows.test.ts +++ b/test/self-versioned-workflows.test.ts @@ -69,6 +69,9 @@ for (const file of files) { { 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/ },