From dd46efa03b1eac45996b6b03aa8ae952b4b25ad8 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 01:41:21 +0100 Subject: [PATCH 01/19] Add reusable Dependabot merge workflow with commit provenance guards --- .../features/pm-github-dependabot-merge.toon | 28 ++ .../history/pm-github-dependabot-merge.jsonl | 10 + .github/workflows/dependabot-auto-merge.yml | 31 +- .../workflows/pm-github-dependabot-merge.yml | 120 ++++++++ CHANGELOG.md | 6 + README.md | 82 ++++- scripts/dependabot-merge.ts | 113 +++++++ test/dependabot-merge.test.ts | 281 ++++++++++++++++++ 8 files changed, 646 insertions(+), 25 deletions(-) create mode 100644 .agents/pm/features/pm-github-dependabot-merge.toon create mode 100644 .agents/pm/history/pm-github-dependabot-merge.jsonl create mode 100644 .github/workflows/pm-github-dependabot-merge.yml create mode 100644 scripts/dependabot-merge.ts create mode 100644 test/dependabot-merge.test.ts diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon new file mode 100644 index 00000000..e325452e --- /dev/null +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -0,0 +1,28 @@ +id: pm-github-dependabot-merge +title: Reusable Dependabot merge workflow with complete commit provenance +description: "Replace copied auto-merge logic with a reusable workflow that revokes stale requests and only arms verified Dependabot-only PRs. Companion: pm-cli-website-wis6." +type: Feature +status: in_progress +priority: 2 +tags[5]: changelog-unreleased,ci,dependabot,security,workflows +created_at: "2026-11-05T00:19:21.862Z" +updated_at: "2026-11-05T00:41:20.832Z" +assignee: codex-agent +claim_principal: codex-agent +author: codex-agent +acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." +comments[4]{created_at,author,text}: + "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." + "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." + "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." + "2026-11-05T00:41:20.832Z",codex-agent,"Additional review: --match-head-commit is an expected-head check, not a durable server-side lease; protected-branch requirements remain necessary for a literal future-push guarantee. Proposed diff scan through the current source runTrackerGate passed: 8 files, 644 added lines, zero findings, zero allowlist entries. The installed pm github gate command instead returned one absolute-host-path finding without an offset for the same diff; current source scanning finds none. This installed-command/source divergence is a PM extension UX gap to report, not changed here." +files[4]{path,scope}: + .github/workflows/dependabot-auto-merge.yml,project + .github/workflows/pm-github-dependabot-merge.yml,project + scripts/dependabot-merge.ts,project + test/dependabot-merge.test.ts,project +tests[1]{command,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: + nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts,project,120,codex-agent,"2026-11-05T00:34:44.010Z",local_mutation,feat/reusable-dependabot-merge +docs[1]{path,scope,note}: + README.md,project,Caller migration token requirements and asynchronous auto-merge limits +body: "" diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl new file mode 100644 index 00000000..bcdf4e9a --- /dev/null +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -0,0 +1,10 @@ +{"hash_algorithm":"sha256","ts":"2026-11-05T00:19:21.862Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-dependabot-merge"},{"op":"add","path":"/metadata/title","value":"Reusable Dependabot merge workflow with complete commit provenance"},{"op":"add","path":"/metadata/description","value":"Replace copied auto-merge logic with a reusable workflow that revokes stale requests and only arms verified Dependabot-only PRs. Companion: pm-cli-website-wis6."},{"op":"add","path":"/metadata/type","value":"Feature"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["changelog-unreleased","ci","dependabot","security","workflows"]},{"op":"add","path":"/metadata/created_at","value":"2026-11-05T00:19:21.862Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-11-05T00:19:21.862Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates."}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"617ffca7861070085d9a8fe55613d95408b040395f507cbd4a0d833af3d5158c","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":"ca184285ced20af435b76b2a758d00ea57477ce1fbdf18640e127e7147eab36e"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:19:34.762Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:19:34.762Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"617ffca7861070085d9a8fe55613d95408b040395f507cbd4a0d833af3d5158c","after_hash":"25a216c9cd91424614187211a0f2bda33273018de443d90841ff20ee8afa72ff","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":"f060dbf7df60b14707c35e3b4edd5846033cbb3d8835d714489c5a57d0eed161"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:19:38.021Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:19:38.021Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"25a216c9cd91424614187211a0f2bda33273018de443d90841ff20ee8afa72ff","after_hash":"a4ae40785f554c5f8843e3370cbca35d962c73baeb6a7090aee223007e242f86","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":"d5c37ee8abfdcbf415a39b61a177f170cfba9b7c5a173e6d876ad7f3531c6f71"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:19:41.703Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:19:41.703Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-11-05T00:19:41.701Z","author":"codex-agent","text":"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes."}]}],"before_hash":"a4ae40785f554c5f8843e3370cbca35d962c73baeb6a7090aee223007e242f86","after_hash":"510deeaa6a1c37d1825e3fb2c2f6c145d58ed5c161d068bc7a46493ef4ad174d","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":"250a01b0473b4e4e5f902baf7cd47321a5b7dd12b3b90ba4d800383f979d451b"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:33:25.643Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:33:25.643Z"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/dependabot-auto-merge.yml","scope":"project"},{"path":".github/workflows/pm-github-dependabot-merge.yml","scope":"project"},{"path":"scripts/dependabot-merge.ts","scope":"project"},{"path":"test/dependabot-merge.test.ts","scope":"project"}]}],"before_hash":"510deeaa6a1c37d1825e3fb2c2f6c145d58ed5c161d068bc7a46493ef4ad174d","after_hash":"4807f79123f5f4c308e32d78659b73e67e9353e139347f1e6ee1ae25442b8d3f","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":"036d6d985b8d4839352109b5ec18e0215aa14d2f4526d52f797a012340aca4d9"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:33:29.092Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:33:29.092Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"README.md","scope":"project","note":"Caller migration token requirements and asynchronous auto-merge limits"}]}],"before_hash":"4807f79123f5f4c308e32d78659b73e67e9353e139347f1e6ee1ae25442b8d3f","after_hash":"aefa5fb37a19e5786c091da40dacdc19fead7d25094b86f4f322742f40d456e9","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":"8ca9a086a528da7912243fa3bb0136943a51b048b479bcdee584c512e7fd2857"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:33:47.236Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:33:47.214Z","author":"codex-agent","text":"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:33:47.236Z"}],"before_hash":"aefa5fb37a19e5786c091da40dacdc19fead7d25094b86f4f322742f40d456e9","after_hash":"ab4c129d3584f26e4425f95687a3999fdf48be033dccd042841950797e4aefe9","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":"307d7f40e5b6cdaa5f7c4a41cfdcf84382b8a11a79a5108935a5e38c79182ab8"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:34:44.112Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:34:44.112Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts","scope":"project","timeout_seconds":120,"provenance":{"author":"codex-agent","created_at":"2026-11-05T00:34:44.010Z","source_kind":"local_mutation","source_ref":"feat/reusable-dependabot-merge"}}]}],"before_hash":"ab4c129d3584f26e4425f95687a3999fdf48be033dccd042841950797e4aefe9","after_hash":"975f389779183754860165547d28086b22a9c2d07a5d534ac4ecf28561d47d8c","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":"96f82b71a1c1c50b28593859dd105f0f0fa409c6d1bbe07c38ca8ac5962ab511"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:39:54.553Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:39:54.549Z","author":"codex-agent","text":"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:39:54.553Z"}],"before_hash":"975f389779183754860165547d28086b22a9c2d07a5d534ac4ecf28561d47d8c","after_hash":"f42631513237f66b23bdc985e43a88df4894c2e71d337cf61d25b90d11aff9ab","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":"3617060234162c51d76393910a600ff5b3cfc993285f09cafd4ea04034253882"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:41:20.832Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:41:20.832Z","author":"codex-agent","text":"Additional review: --match-head-commit is an expected-head check, not a durable server-side lease; protected-branch requirements remain necessary for a literal future-push guarantee. Proposed diff scan through the current source runTrackerGate passed: 8 files, 644 added lines, zero findings, zero allowlist entries. The installed pm github gate command instead returned one absolute-host-path finding without an offset for the same diff; current source scanning finds none. This installed-command/source divergence is a PM extension UX gap to report, not changed here."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:41:20.832Z"}],"before_hash":"f42631513237f66b23bdc985e43a88df4894c2e71d337cf61d25b90d11aff9ab","after_hash":"68136f263196e2650f35dcd2d4485b30f3b00d0eac5a8d2e7ffdb19786c445af","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":"fbfb24c25c58491279a1f17f23666bfafcce8de795193e667f8b14b11319e662"} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 85d4c777..165a9ade 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -1,33 +1,16 @@ name: dependabot-auto-merge -# Version bumps are mechanical: every non-major Dependabot pull request gets -# GitHub auto-merge, so it lands the moment the required checks pass. Branch -# protection still gates the merge; a failing bump stays open as a defect. -on: pull_request +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review, edited, auto_merge_enabled] permissions: {} jobs: - enable-auto-merge: - if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository_owner == 'unbraind' - runs-on: ubuntu-latest - # `gh pr merge --auto` needs pull-requests: write to enable auto-merge - # and contents: write for the squash merge GitHub performs once the - # required checks pass. Nothing is checked out or executed from the PR. + verified-auto-merge: permissions: contents: write pull-requests: write - steps: - - id: metadata - uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 - # pm-toolchain is calendar-versioned (a new year reads as semver-major), - # so it always auto-merges. Anything else must be classified minor or - # patch; a major or unclassified update waits for a person. - - if: >- - steps.metadata.outputs.dependency-group == 'pm-toolchain' || - steps.metadata.outputs.update-type == 'version-update:semver-minor' || - steps.metadata.outputs.update-type == 'version-update:semver-patch' - run: gh pr merge --auto --squash "$PR_URL" - env: - PR_URL: ${{ github.event.pull_request.html_url }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/pm-github-dependabot-merge.yml + secrets: + merge_token: ${{ secrets.DEPENDABOT_MERGE_TOKEN }} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml new file mode 100644 index 00000000..d0f09e5a --- /dev/null +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -0,0 +1,120 @@ +name: Verified Dependabot squash auto-merge + +on: + workflow_call: + inputs: + extra-allowed-groups: + description: "Additional exact Dependabot groups, comma or whitespace separated" + type: string + required: false + default: "" + secrets: + merge_token: + description: "Optional write-capable token; also configure as a Dependabot secret for pull_request runs" + required: false + +permissions: {} + +concurrency: + group: pm-github-dependabot-merge-${{ github.repository }}-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + dependabot-merge: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + GH_TOKEN: ${{ secrets.merge_token || github.token }} + REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + EVENT_NAME: ${{ github.event_name }} + EVENT_ACTOR: ${{ github.actor }} + TRIGGERING_ACTOR: ${{ github.triggering_actor }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + EVENT_HEAD: ${{ github.event.pull_request.head.sha }} + EXTRA_GROUPS: ${{ inputs.extra-allowed-groups }} + steps: + # Revoke before even setting up Node or downloading policy. If the state + # read fails, attempt revocation anyway; inability to revoke must be visible. + - name: Revoke previous auto-merge before tooling + shell: bash + run: | + set -euo pipefail + [[ "${REPOSITORY}" =~ ^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$ && "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]] + armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.auto_merge != null' 2>/dev/null)" || armed=true + if [[ "${armed}" != false ]]; then + if ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then + echo '::error::Cannot revoke auto-merge; check token write permissions immediately.' + exit 1 + fi + fi + echo 'Previous auto-merge requests cleared before tooling.' >> "${GITHUB_STEP_SUMMARY}" + - name: Setup policy runtime + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "24" + # Never check out caller/PR code. Policy comes from this workflow's commit. + - name: Load trusted merge policy + id: policy + env: + WORKFLOW_SHA: ${{ job.workflow_sha }} + shell: bash + run: | + set -euo pipefail + policy_sha="${WORKFLOW_SHA}" + # A local-path pull_request call resolves workflow_sha from the PR. + # Its executable policy must instead come from the trusted default branch. + if [[ "${REPOSITORY}" == unbraind/pm-github ]]; then + policy_sha="$(gh api repos/unbraind/pm-github/git/ref/heads/main --jq '.object.sha')" || policy_sha="" + fi + if [[ ! "${policy_sha}" =~ ^[a-f0-9]{40}$ ]] || ! gh api "repos/unbraind/pm-github/contents/scripts/dependabot-merge.ts?ref=${policy_sha}" \ + -H 'Accept: application/vnd.github.raw+json' > "${RUNNER_TEMP}/dependabot-merge.ts"; then + armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.auto_merge != null' 2>/dev/null)" || armed=true + if [[ "${armed}" != false ]] && ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then + echo '::error::Cannot revoke auto-merge after policy load failure; check token write permissions immediately.' + exit 1 + fi + echo '::notice::Cannot load trusted merge policy; auto-merge not armed.' + echo 'Cannot load trusted merge policy; auto-merge not armed.' >> "${GITHUB_STEP_SUMMARY}" + printf 'loaded=false\n' >> "${GITHUB_OUTPUT}" + exit 0 + fi + cat > "${RUNNER_TEMP}/run-dependabot-merge.mts" <<'TS' + import { appendFileSync } from 'node:fs'; + import { runDependabotMerge } from './dependabot-merge.ts'; + const e = process.env; + const result = runDependabotMerge({ + repository: e.REPOSITORY!, number: e.PR_NUMBER!, event: e.EVENT_NAME!, + actor: e.EVENT_ACTOR!, triggeringActor: e.TRIGGERING_ACTOR!, author: e.PR_AUTHOR!, + head: e.EVENT_HEAD!, phase: e.PHASE!, metadataOutcome: e.METADATA_OUTCOME ?? '', + group: e.DEPENDENCY_GROUP ?? '', updateType: e.UPDATE_TYPE ?? '', extraGroups: e.EXTRA_GROUPS!, + }); + appendFileSync(e.GITHUB_STEP_SUMMARY!, `### Dependabot merge policy\n\n${result.message}\n`); + console.log(`::notice::${result.message.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A')}`); + TS + printf 'loaded=true\n' >> "${GITHUB_OUTPUT}" + + - name: Revoke previous auto-merge before metadata + if: steps.policy.outputs.loaded == 'true' + env: + PHASE: revoke + run: node "${RUNNER_TEMP}/run-dependabot-merge.mts" + + - name: Fetch Dependabot metadata + id: metadata + if: steps.policy.outputs.loaded == 'true' && github.event_name == 'pull_request' && github.actor == 'dependabot[bot]' && github.triggering_actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' + continue-on-error: true + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: ${{ env.GH_TOKEN }} + + - name: Audit all commits and apply merge policy + if: steps.policy.outputs.loaded == 'true' + env: + PHASE: evaluate + METADATA_OUTCOME: ${{ steps.metadata.outcome }} + DEPENDENCY_GROUP: ${{ steps.metadata.outputs.dependency-group }} + UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }} + run: node "${RUNNER_TEMP}/run-dependabot-merge.mts" diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d4820a..5a2620e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Security + +- Reusable Dependabot merge workflow with complete commit provenance ([pm-github-dependabot-merge](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-dependabot-merge.toon)) + ## 2026.11.4 - 2026-11-04 ### Fixed diff --git a/README.md b/README.md index b0d1daa2..c8d73199 100644 --- a/README.md +++ b/README.md @@ -832,9 +832,89 @@ The `gate` receipt reports the verdict, scanned file count, findings, and allowl With `--json`, successful reports go to stdout; failures use the host CLI's JSON error envelope on stderr, with the redacted gate report serialized in `detail` (`jq ' .detail | fromjson ' gate-error.json`). Unreadable inputs produce a nonzero JSON refusal. +## Verified Dependabot auto-merge + +Use [the reusable merge workflow](.github/workflows/pm-github-dependabot-merge.yml) +instead of copying merge logic into each repository. It clears existing +auto-merge before tooling or metadata, then requests squash auto-merge only for +`pull_request` events whose PR author, event actor and re-run actor are all +`dependabot[bot]`. It reads **every page** of the PR commits API and requires +each commit's API-linked author to be Dependabot and its committer to be either +Dependabot or `web-flow`. Every commit must also have GitHub verification +`verified: true, reason: valid`; account attribution alone can be forged through +Git email fields. +The verified `web-flow` case was checked against real fleet PR data, including +the Dependabot commit in `pm-context#211`. + +The commit list must match the PR's positive commit count, contain unique full +SHAs, end at its current head and remain stable across fresh API reads. Only +successful `dependabot/fetch-metadata` classification permits the `pm-toolchain` +group, extra explicitly allowed groups, or semver minor/patch updates. Major +updates outside those groups wait for review. The arm request includes +`--match-head-commit`; another read revokes it if the head changes while arming. + +```yaml +name: dependabot-auto-merge +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review, edited, auto_merge_enabled] +permissions: {} +jobs: + verified-auto-merge: + permissions: + contents: write + pull-requests: write + uses: unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@RELEASE_COMMIT_SHA # pm-github RELEASE_VERSION + with: + extra-allowed-groups: "" # Optional exact names, separated by commas or whitespace + secrets: + merge_token: ${{ secrets.DEPENDABOT_MERGE_TOKEN }} +``` + +Replace `RELEASE_COMMIT_SHA` with the full 40-character SHA of the reviewed +release **containing this workflow**, and `RELEASE_VERSION` with that release's +version. This workflow is new; earlier releases do not contain it. Configure +Dependabot's `github-actions` ecosystem to maintain the SHA and same-line +`# pm-github ` comment. No release is created by this change. + +GitHub documents [read-only tokens for Dependabot-triggered pull requests](https://docs.github.com/en/code-security/reference/supply-chain-security/troubleshoot-dependabot/dependabot-on-actions). +Provide `DEPENDABOT_MERGE_TOKEN` as both an Actions secret and a Dependabot +secret, restricted to the calling repository with Contents and Pull requests +write permissions. The optional `merge_token` falls back to `github.token`; +that fallback can arm or revoke only when repository/event policy actually +grants the necessary writes. Reusable workflows cannot elevate caller +permissions. Keep auto-merge enabled in repository settings and required checks +and review rules on the protected base branch. + +Foreign commits revoke auto-merge and produce a successful job, a summary and +`::notice` listing **short SHA and author login only**. Disallowed metadata, +unsupported events (including `pull_request_target`), API failures and incomplete +pagination also leave auto-merge unarmed with a clear notice. An inability to +revoke is an operational failure, rather than a false successful safety claim. +No PR files, Git author names/emails, commit messages or raw API errors are +printed. Actions are SHA-pinned and no PR code is checked out or executed. +The standalone policy is fetched from `job.workflow_sha` in pm-github; this +repository's local-path caller instead fetches policy from trusted `main`, so +a PR cannot supply the executable script. Before this change reaches `main`, +that self-call fails closed because the trusted policy file is absent. + +Migration: replace the **entire** copied `dependabot-auto-merge.yml` with the +caller above. Retain `synchronize` and omit author/actor job guards: the job +must run when a foreign actor pushes so it can revoke an earlier request. +Remove any other workflow that arms Dependabot auto-merge, and clear existing +requests when adopting the caller; installing a workflow does not retroactively +trigger it for every open PR. This repository uses the supported local path. + +GitHub auto-merge is asynchronous. This workflow verifies the head it arms and +revokes on subsequent events; it cannot atomically prevent GitHub merging a +later push before the next workflow runs. A literal guarantee for future +commits also needs server-enforced branch protection/review requirements that +remain unsatisfied for unreviewed commits. Do not treat an earlier successful +policy run as approval of a later head. + ## Self-versioned workflows and Dependabot -Both reusable workflows resolve their own **exact** pm-github release version at +The sync and closeout reusable workflows resolve their own **exact** pm-github release version at runtime and verify the installed package version. GitHub's documented [`job.workflow_sha`](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#job-context) is the commit of the workflow that defines the current job, including a reusable diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts new file mode 100644 index 00000000..50974864 --- /dev/null +++ b/scripts/dependabot-merge.ts @@ -0,0 +1,113 @@ +/** Standalone policy loaded from the reusable workflow SHA, never from a PR. */ +import { execFileSync } from "node:child_process"; + +/** Minimal GitHub PR fields needed to prove a stable, complete snapshot. */ +interface PullRequest { + state: string; + user: { login: string }; + head: { sha: string }; + commits: number; +} + +/** API-linked accounts and signature verification, never Git display names. */ +interface Commit { + sha: string; + author: { login: string } | null; + committer: { login: string } | null; + commit: { verification: { verified: boolean; reason: string } }; +} + +/** Event and metadata values enter through bindings, never shell source. */ +export interface MergeInput { + repository: string; + number: string; + event: string; + actor: string; + triggeringActor: string; + author: string; + head: string; + phase: string; + metadataOutcome: string; + group: string; + updateType: string; + extraGroups: string; +} + +/** Safe report containing policy reasons and short SHA/account pairs only. */ +export interface MergeResult { + armed: boolean; + message: string; +} + +/** Execute gh without a shell; failed API reads cannot become empty success. */ +export function githubCli(args: readonly string[], executable = "gh"): string { + return execFileSync(executable, args, { encoding: "utf8", maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }); +} + +const bot = "dependabot[bot]"; +const shaPattern = /^[a-f0-9]{40}$/; + +/** Reduce untrusted API account values to a safe single-line login. */ +function login(value: string | undefined): string { + return typeof value === "string" && /^[a-zA-Z0-9-]+(?:\[bot\])?$/.test(value) ? value : "unknown"; +} + +/** + * Revoke first and independently re-read provenance before a head-bound arm. + * Every refusal revokes even after an API failure. Inability to revoke throws: + * the job must not claim success with an unsafe request potentially still armed. + */ +export function runDependabotMerge(input: MergeInput, gh: (args: readonly string[]) => string = githubCli): MergeResult { + if (!/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(input.repository) || !/^[1-9][0-9]*$/.test(input.number)) { + throw new Error("Invalid caller repository or PR number; cannot safely address auto-merge."); + } + const target = [input.number, "--repo", input.repository]; + const endpoint = `repos/${input.repository}/pulls/${input.number}`; + const revoke = () => { + let armed = true; + try { armed = (JSON.parse(gh(["api", endpoint])) as { auto_merge: unknown }).auto_merge !== null; } catch { /* Unreadable state still owes revocation. */ } + if (armed) { + try { gh(["pr", "merge", ...target, "--disable-auto"]); } + catch { throw new Error("Auto-merge revocation denied or unavailable; check token write permissions immediately."); } + } + }; + revoke(); + if (input.phase === "revoke") return { armed: false, message: "Previous auto-merge request revoked before metadata." }; + let message: string; + try { + const read = () => JSON.parse(gh(["api", endpoint])) as PullRequest; + const before = read(); + // One JSON object per line works with both older and current gh releases. + // A nonzero status on any later page throws before any partial list is used. + const receipt = gh(["api", `${endpoint}/commits?per_page=100`, "--paginate", "--jq", ".[] | @json"]).trim(); + const commits = receipt === "" ? [] : receipt.split("\n").map(line => JSON.parse(line) as Commit); + const after = read(); + if (!Number.isInteger(before.commits) || before.commits < 1 || commits.length !== before.commits || + !commits.every(commit => shaPattern.test(commit?.sha)) || new Set(commits.map(commit => commit.sha)).size !== commits.length || + commits.at(-1)!.sha !== before.head.sha || after.head.sha !== before.head.sha || after.commits !== before.commits) { + throw new Error("Incomplete or changing PR snapshot"); + } + const foreign = commits.filter(commit => commit.author?.login !== bot || + !(commit.committer?.login === bot || commit.committer?.login === "web-flow") || + commit.commit?.verification?.verified !== true || commit.commit.verification.reason !== "valid"); + const identities = foreign.map(commit => `${commit.sha.slice(0, 12)} ${login(commit.author?.login)}`); + const allowedGroups = ["pm-toolchain", ...input.extraGroups.split(/[\s,]+/).filter(Boolean)]; + if (foreign.length > 0) message = `Foreign or unverified commits: ${identities.join(", ")}.`; + else if (input.event !== "pull_request") message = "Only pull_request callers are supported; pull_request_target is refused."; + else if (input.actor !== bot || input.triggeringActor !== bot || input.author !== bot || before.user.login !== bot || after.user.login !== bot) message = "PR author and event/re-run actors must all be dependabot[bot]."; + else if (before.state !== "open" || after.state !== "open" || !shaPattern.test(input.head) || input.head !== after.head.sha) message = "PR is closed or the event head is stale."; + else if (input.metadataOutcome !== "success") message = "Dependabot metadata unavailable or failed; auto-merge not armed."; + else if (!(input.group !== "" && allowedGroups.includes(input.group)) && + !["version-update:semver-minor", "version-update:semver-patch"].includes(input.updateType)) message = "Update is outside the allowed groups and semver minor/patch policy."; + else { + gh(["pr", "merge", ...target, "--auto", "--squash", "--match-head-commit", after.head.sha]); + const final = read(); + if (final.head.sha !== after.head.sha || final.commits !== after.commits || final.user.login !== bot) throw new Error("PR changed while arming"); + return { armed: true, message: `Squash auto-merge requested for verified Dependabot head ${after.head.sha.slice(0, 12)} (${commits.length} commits).` }; + } + } catch { + message = "API/provenance or merge request failed (including incomplete pagination or a changing head); auto-merge not armed. Check token permissions and retry."; + } + revoke(); + return { armed: false, message }; +} diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts new file mode 100644 index 00000000..14ab1441 --- /dev/null +++ b/test/dependabot-merge.test.ts @@ -0,0 +1,281 @@ +/** Execute the production merge policy against API receipts and its real workflow launcher. */ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, 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"; +import { githubCli, runDependabotMerge, type MergeInput } from "../scripts/dependabot-merge.ts"; + +const root = resolve(import.meta.dirname, ".."); +const bot = "dependabot[bot]"; +const head = "a".repeat(40); +const trusted = "b".repeat(40); + +/** Model only remote API responses and mutation receipts; execute the actual policy. */ +function fixture() { + const input: MergeInput = { repository: "acme/widgets", number: "7", event: "pull_request", actor: bot, + triggeringActor: bot, author: bot, head, phase: "evaluate", metadataOutcome: "success", + group: "", updateType: "version-update:semver-patch", extraGroups: "" }; + const commit = { sha: head, author: { login: bot }, committer: { login: "web-flow" }, commit: { verification: { verified: true, reason: "valid" } } }; + const state = { state: "open", user: { login: bot }, head: { sha: head }, commits: 1, auto_merge: {} as object | null }; + const pages: unknown[][] = [[commit]]; + const calls: string[][] = []; + let reads = 0; + const f = { input, commit, state, pages, calls, failCommits: false, failReads: false, failArm: false, failRevoke: false, + rawPages: undefined as string | undefined, onRead: (_read: number) => {}, + execute: () => runDependabotMerge(input, args => { + calls.push([...args]); + if (args[0] === "api" && args[1]!.includes("/commits?")) { + assert.ok(args.includes("--paginate")); assert.ok(args.includes("--jq")); + if (f.failCommits) throw new Error("private remote response"); + return f.rawPages ?? pages.flat().map(commit => JSON.stringify(commit)).join("\n"); + } + if (args[0] === "api") { + f.onRead(++reads); + if (f.failReads) throw new Error("unreadable"); + return JSON.stringify(state); + } + if (args.includes("--disable-auto")) { + if (f.failRevoke) throw new Error("revocation denied"); + state.auto_merge = null; + } else { + assert.ok(args.includes("--squash")); + assert.equal(args.at(-1), head); + if (f.failArm) throw new Error("expected head changed"); + state.auto_merge = {}; + } + return ""; + }) }; + return f; +} + +test("Dependabot opens a verified PR: revoke first, then arm squash at the checked head", () => { + const f = fixture(); + assert.equal(f.execute().armed, true); + assert.ok(f.calls[1]!.includes("--disable-auto")); + assert.ok(f.calls.find(args => args.includes("--match-head-commit"))); +}); + +test("Dependabot rebase with bot committer remains eligible", () => { + const f = fixture(); f.commit.committer.login = bot; + assert.equal(f.execute().armed, true); +}); + +for (const [name, mutate] of [ + ["foreign author pushes", (f: ReturnType) => { f.commit.author.login = "outside"; f.input.actor = "outside"; }], + ["foreign committer with bot author", (f: ReturnType) => { f.commit.committer.login = "outside"; }], + ["unsigned web-flow", (f: ReturnType) => { f.commit.commit.verification.verified = false; }], + ["unsigned bot account attribution", (f: ReturnType) => { f.commit.committer.login = bot; f.commit.commit.verification.verified = false; }], + ["web-flow verification reason invalid", (f: ReturnType) => { f.commit.commit.verification.reason = "unsigned"; }], + ["null author", (f: ReturnType) => { f.pages[0] = [{ ...f.commit, author: null }]; }], + ["null committer", (f: ReturnType) => { f.pages[0] = [{ ...f.commit, committer: null }]; }], + ["missing verification", (f: ReturnType) => { f.pages[0] = [{ ...f.commit, commit: {} }]; }], + ["missing commit", (f: ReturnType) => { f.pages[0] = [{ ...f.commit, commit: null }]; }], + ["unsafe account text", (f: ReturnType) => { f.commit.author.login = "bad\n::error::private"; }], +] as const) { + test(`${name}: disarm and name only the short SHA and author login`, () => { + const f = fixture(); mutate(f); const r = f.execute(); + assert.equal(r.armed, false); assert.equal(f.state.auto_merge, null); + assert.ok(!f.calls.some(args => args.includes("--auto"))); + assert.match(r.message, /Foreign or unverified commits: aaaaaaaaaaaa (outside|dependabot\[bot\]|unknown)\./); + assert.doesNotMatch(r.message, /private|web-flow/); + }); +} + +for (const changes of [ + { event: "pull_request_target" }, { event: "workflow_dispatch" }, { actor: "outside" }, + { triggeringActor: "outside" }, { author: "outside" }, { head: "c".repeat(40) }, { head: "invalid" }, + { metadataOutcome: "failure" }, { metadataOutcome: "skipped" }, + { updateType: "version-update:semver-major" }, { updateType: "" }, + { group: "pm-toolchain-extra", updateType: "version-update:semver-major" }, +]) { + test(`refusal succeeds without arming: ${JSON.stringify(changes)}`, () => { + const f = fixture(); Object.assign(f.input, changes); + assert.equal(f.execute().armed, false); assert.equal(f.state.auto_merge, null); + assert.ok(!f.calls.some(args => args.includes("--auto"))); + }); +} + +for (const changes of [ + { group: "pm-toolchain", updateType: "version-update:semver-major" }, + { group: "reviewed", extraGroups: "first, reviewed\nlast", updateType: "version-update:semver-major" }, + { updateType: "version-update:semver-minor" }, +]) { + test(`allowed metadata: ${JSON.stringify(changes)}`, () => { + const f = fixture(); Object.assign(f.input, changes); assert.equal(f.execute().armed, true); + }); +} + +for (const mutate of [ + (f: ReturnType) => { f.state.state = "closed"; }, + (f: ReturnType) => { f.onRead = n => { if (n === 3) f.state.state = "closed"; }; }, + (f: ReturnType) => { f.state.user.login = "outside"; }, + (f: ReturnType) => { f.onRead = n => { if (n === 3) f.state.user.login = "outside"; }; }, +]) { + test("live PR state and author must remain eligible", () => { const f = fixture(); mutate(f); assert.equal(f.execute().armed, false); }); +} + +test("all 101 commits are read across pages; foreign commit on page two disarms", () => { + const f = fixture(); + f.pages[0] = Array.from({ length: 100 }, (_, i) => ({ ...structuredClone(f.commit), sha: i.toString(16).padStart(40, "0") })); + f.pages.push([f.commit]); f.state.commits = 101; + assert.equal(f.execute().armed, true); + f.commit.committer.login = "outside"; + assert.equal(f.execute().armed, false); assert.equal(f.state.auto_merge, null); +}); + +for (const [name, mutate] of [ + ["API failure", (f: ReturnType) => { f.failCommits = true; }], + ["PR API failure", (f: ReturnType) => { f.failReads = true; }], + ["malformed JSON", (f: ReturnType) => { f.rawPages = "{"; }], + ["non-array pages", (f: ReturnType) => { f.rawPages = "{}"; }], + ["malformed page", (f: ReturnType) => { f.rawPages = "[{}]"; }], + ["empty list", (f: ReturnType) => { f.pages[0] = []; f.state.commits = 0; }], + ["missing page", (f: ReturnType) => { f.state.commits = 101; }], + ["bad count", (f: ReturnType) => { f.state.commits = 1.5; }], + ["bad SHA", (f: ReturnType) => { f.commit.sha = "invalid"; }], + ["null commit", (f: ReturnType) => { f.pages[0] = [null]; }], + ["duplicate commits", (f: ReturnType) => { f.pages[0]!.push(f.commit); f.state.commits = 2; }], + ["head differs from last commit", (f: ReturnType) => { f.state.head.sha = trusted; }], + ["head changes during read", (f: ReturnType) => { f.onRead = n => { if (n === 3) f.state.head.sha = trusted; }; }], + ["count changes during read", (f: ReturnType) => { f.onRead = n => { if (n === 3) f.state.commits = 2; }; }], + ["arm denied", (f: ReturnType) => { f.failArm = true; }], + ["head changes after arm", (f: ReturnType) => { f.onRead = n => { if (n === 4) f.state.head.sha = trusted; }; }], + ["count changes after arm", (f: ReturnType) => { f.onRead = n => { if (n === 4) f.state.commits = 2; }; }], + ["author changes after arm", (f: ReturnType) => { f.onRead = n => { if (n === 4) f.state.user.login = "outside"; }; }], +] as const) { + test(`${name}: fail closed, revoke and explain without remote response text`, () => { + const f = fixture(); mutate(f); const r = f.execute(); + assert.equal(r.armed, false); assert.equal(f.state.auto_merge, null); + assert.match(r.message, /API\/provenance.*auto-merge not armed/); assert.doesNotMatch(r.message, /private remote response/); + }); +} + +test("early revoke skips metadata and commit reads", () => { + const f = fixture(); f.input.phase = "revoke"; + assert.equal(f.execute().armed, false); assert.equal(f.calls.length, 2); +}); + +test("no existing auto-merge requires no disable mutation", () => { + const f = fixture(); f.state.auto_merge = null; f.input.phase = "revoke"; + f.execute(); assert.equal(f.calls.length, 1); +}); + +test("revocation denial surfaces an operational error instead of false success", () => { + const f = fixture(); f.failRevoke = true; assert.throws(f.execute, /revocation denied or unavailable/); +}); + +test("revocation denial after a concurrent arm is also an operational error", () => { + const f = fixture(); + f.onRead = n => { if (n === 4) { f.state.head.sha = trusted; f.failRevoke = true; } }; + assert.throws(f.execute, /revocation denied or unavailable/); +}); + +for (const change of [{ repository: "--bad" }, { number: "0" }]) { + test(`invalid target is refused before any side effect: ${JSON.stringify(change)}`, () => { + const f = fixture(); Object.assign(f.input, change); assert.throws(f.execute, /Invalid caller/); assert.equal(f.calls.length, 0); + }); +} + +/** Extract the exact loader shell, including its generated TypeScript launcher. */ +function loader(): string { + const workflow = readFileSync(join(root, ".github/workflows/pm-github-dependabot-merge.yml"), "utf8"); + return workflow.split(" - name: Load trusted merge policy\n")[1]!.split(" run: |\n")[1]!.split(" - name:")[0]!.split("\n") + .filter(line => line.startsWith(" ")).map(line => line.slice(10)).join("\n"); +} + +test("workflow policy: pinned actions, minimal grants, no PR checkout or interpolated shell", () => { + const source = readFileSync(join(root, ".github/workflows/pm-github-dependabot-merge.yml"), "utf8"); + const workflow = parse(source) as { on: Record; permissions: object; jobs: Record }; + assert.deepEqual(Object.keys(workflow.on), ["workflow_call"]); assert.deepEqual(workflow.permissions, {}); + assert.deepEqual(workflow.jobs["dependabot-merge"]!.permissions, { contents: "write", "pull-requests": "write" }); + for (const step of workflow.jobs["dependabot-merge"]!.steps) { + if (step.uses) assert.match(step.uses, /@[a-f0-9]{40}$/); + if (step.run) assert.doesNotMatch(step.run, /\$\{\{/); + } + assert.doesNotMatch(source, /actions\/checkout|skip-commit-verification|--admin/); + assert.match(source, /continue-on-error: true/); assert.match(source, /job\.workflow_sha/); + const caller = readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8"); + assert.match(caller, /uses: \.\/\.github\/workflows\/pm-github-dependabot-merge.yml/); + assert.match(caller, /synchronize/); assert.doesNotMatch(caller, /if:|pull_request_target/); +}); + +for (const mode of ["external", "local", "load-failed", "invalid-sha", "local-ref-failed"]) { + test(`real workflow loader/launcher: ${mode}`, () => { + const dir = mkdtempSync(join(tmpdir(), "dependabot-policy-")); + const env = { ...process.env, PATH: dir + ":" + process.env.PATH, RUNNER_TEMP: dir, + POLICY_SOURCE: join(root, "scripts/dependabot-merge.ts"), WORKFLOW_SHA: mode === "invalid-sha" ? "bad" : head, + REPOSITORY: mode.startsWith("local") ? "unbraind/pm-github" : "acme/widgets", PR_NUMBER: "7", MODE: mode, + GITHUB_STEP_SUMMARY: join(dir, "summary"), EVENT_NAME: "pull_request", EVENT_ACTOR: "outside", TRIGGERING_ACTOR: "outside", + GITHUB_OUTPUT: join(dir, "output"), + PR_AUTHOR: bot, EVENT_HEAD: head, EXTRA_GROUPS: "", PHASE: "evaluate", RECEIPTS: join(dir, "receipts") }; + try { + writeFileSync(join(dir, "gh"), `#!/bin/bash +printf '%s\\n' "$*" >> "$RECEIPTS" +case "$*" in + *git/ref/heads/main*) [[ "$MODE" != local-ref-failed ]] || exit 1; printf '%s' '${trusted}' ;; + *contents/scripts*) [[ "$MODE" != load-failed ]] || exit 1; cat "$POLICY_SOURCE" ;; + *'/commits?'*) printf '%s' '{"sha":"${head}","author":{"login":"outside"},"committer":{"login":"outside"}}' ;; + 'api '*) printf '%s' '{"state":"open","user":{"login":"${bot}"},"head":{"sha":"${head}"},"commits":1,"auto_merge":null}' ;; + 'pr merge '*) ;; + *) exit 2 ;; +esac +`, { mode: 0o755 }); + const loaded = spawnSync("bash", ["-c", loader()], { env, encoding: "utf8" }); + const failed = ["load-failed", "invalid-sha", "local-ref-failed"].includes(mode); + assert.equal(loaded.status, 0, loaded.stderr); + const receipts = readFileSync(env.RECEIPTS, "utf8"); + if (failed) { + assert.match(receipts, /--disable-auto/); + assert.match(loaded.stdout, /::notice::Cannot load trusted merge policy/); + assert.match(readFileSync(env.GITHUB_OUTPUT, "utf8"), /loaded=false/); + } + else { + assert.match(receipts, new RegExp(`ref=${mode === "local" ? trusted : head}`)); + const run = spawnSync(process.execPath, [join(dir, "run-dependabot-merge.mts")], { env, encoding: "utf8" }); + assert.equal(run.status, 0, run.stderr); + assert.match(run.stdout, /::notice::Foreign or unverified commits: aaaaaaaaaaaa outside/); + assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /aaaaaaaaaaaa outside/); + const old = process.env.PATH; + try { + process.env.PATH = env.PATH; + assert.equal(runDependabotMerge({ ...fixture().input, phase: "revoke" }).armed, false); + } finally { process.env.PATH = old; } + } + } finally { rmSync(dir, { recursive: true, force: true }); } + }); +} + +test("real CLI adapter passes argv without shell expansion and propagates failure", () => { + assert.equal(githubCli(["-p", "'literal $safe'"], process.execPath).trim(), "literal $safe"); + assert.throws(() => githubCli(["-e", "process.exit(3)"], process.execPath)); +}); + +for (const mode of ["armed", "clear", "api-error", "denied"]) { + test(`pre-tooling revocation shell: ${mode}`, () => { + const dir = mkdtempSync(join(tmpdir(), "dependabot-revoke-")); + try { + const workflow = readFileSync(join(root, ".github/workflows/pm-github-dependabot-merge.yml"), "utf8"); + const shell = workflow.split(" run: |\n")[1]!.split(" - name:")[0]!.split("\n") + .filter(line => line.startsWith(" ")).map(line => line.slice(10)).join("\n"); + writeFileSync(join(dir, "gh"), `#!/bin/bash +printf '%s\\n' "$*" >> "$RECEIPTS" +if [[ "$1" == api ]]; then + [[ "$MODE" != api-error ]] || exit 1 + if [[ "$MODE" == clear ]]; then echo false; else echo true; fi +else [[ "$MODE" != denied ]]; fi +`, { mode: 0o755 }); + const env = { ...process.env, PATH: dir + ":" + process.env.PATH, REPOSITORY: "acme/widgets", PR_NUMBER: "7", + MODE: mode, RECEIPTS: join(dir, "receipts"), GITHUB_STEP_SUMMARY: join(dir, "summary") }; + const result = spawnSync("bash", ["-c", shell], { env, encoding: "utf8" }); + assert.equal(result.status, mode === "denied" ? 1 : 0, result.stderr); + if (mode === "denied") assert.match(result.stdout, /Cannot revoke auto-merge/); + else assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /cleared before tooling/); + const receipts = readFileSync(env.RECEIPTS, "utf8"); + if (mode === "clear") assert.doesNotMatch(receipts, /--disable-auto/); + else assert.match(receipts, /--disable-auto/); + } finally { rmSync(dir, { recursive: true, force: true }); } + }); +} From aaffdb6da1a6a5cea010f125aa562fb5cb6fbd72 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 01:45:43 +0100 Subject: [PATCH 02/19] Distinguish active auto-merge from closed PR metadata --- .agents/pm/features/pm-github-dependabot-merge.toon | 5 +++-- .agents/pm/history/pm-github-dependabot-merge.jsonl | 1 + .github/workflows/pm-github-dependabot-merge.yml | 4 ++-- scripts/dependabot-merge.ts | 6 +++++- test/dependabot-merge.test.ts | 6 ++++++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index e325452e..ff0cffb3 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,16 +6,17 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T00:41:20.832Z" +updated_at: "2026-11-05T00:45:42.355Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[4]{created_at,author,text}: +comments[5]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." "2026-11-05T00:41:20.832Z",codex-agent,"Additional review: --match-head-commit is an expected-head check, not a durable server-side lease; protected-branch requirements remain necessary for a literal future-push guarantee. Proposed diff scan through the current source runTrackerGate passed: 8 files, 644 added lines, zero findings, zero allowlist entries. The installed pm github gate command instead returned one absolute-host-path finding without an offset for the same diff; current source scanning finds none. This installed-command/source divergence is a PM extension UX gap to report, not changed here." + "2026-11-05T00:45:42.353Z",codex-agent,"Final regression corpus: 68 focused tests passed through the pinned pm test runner. Repeated the behavioral revert against the final policy in a disposable copy: removing only the provenance filter caused 13 failures and 55 passes; the package worktree was never mutated. Added a real-API edge case: closed or merged PRs can retain historical auto_merge metadata, so only active requests are revoked. Current typecheck, test typecheck and lint pass. The full npm suite is still progressing through existing closeout integration cases; coverage and complete release gates remain pending." files[4]{path,scope}: .github/workflows/dependabot-auto-merge.yml,project .github/workflows/pm-github-dependabot-merge.yml,project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index bcdf4e9a..caf53f21 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -8,3 +8,4 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T00:34:44.112Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:34:44.112Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts","scope":"project","timeout_seconds":120,"provenance":{"author":"codex-agent","created_at":"2026-11-05T00:34:44.010Z","source_kind":"local_mutation","source_ref":"feat/reusable-dependabot-merge"}}]}],"before_hash":"ab4c129d3584f26e4425f95687a3999fdf48be033dccd042841950797e4aefe9","after_hash":"975f389779183754860165547d28086b22a9c2d07a5d534ac4ecf28561d47d8c","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":"96f82b71a1c1c50b28593859dd105f0f0fa409c6d1bbe07c38ca8ac5962ab511"} {"hash_algorithm":"sha256","ts":"2026-11-05T00:39:54.553Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:39:54.549Z","author":"codex-agent","text":"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:39:54.553Z"}],"before_hash":"975f389779183754860165547d28086b22a9c2d07a5d534ac4ecf28561d47d8c","after_hash":"f42631513237f66b23bdc985e43a88df4894c2e71d337cf61d25b90d11aff9ab","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":"3617060234162c51d76393910a600ff5b3cfc993285f09cafd4ea04034253882"} {"hash_algorithm":"sha256","ts":"2026-11-05T00:41:20.832Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:41:20.832Z","author":"codex-agent","text":"Additional review: --match-head-commit is an expected-head check, not a durable server-side lease; protected-branch requirements remain necessary for a literal future-push guarantee. Proposed diff scan through the current source runTrackerGate passed: 8 files, 644 added lines, zero findings, zero allowlist entries. The installed pm github gate command instead returned one absolute-host-path finding without an offset for the same diff; current source scanning finds none. This installed-command/source divergence is a PM extension UX gap to report, not changed here."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:41:20.832Z"}],"before_hash":"f42631513237f66b23bdc985e43a88df4894c2e71d337cf61d25b90d11aff9ab","after_hash":"68136f263196e2650f35dcd2d4485b30f3b00d0eac5a8d2e7ffdb19786c445af","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":"fbfb24c25c58491279a1f17f23666bfafcce8de795193e667f8b14b11319e662"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:45:42.355Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:45:42.353Z","author":"codex-agent","text":"Final regression corpus: 68 focused tests passed through the pinned pm test runner. Repeated the behavioral revert against the final policy in a disposable copy: removing only the provenance filter caused 13 failures and 55 passes; the package worktree was never mutated. Added a real-API edge case: closed or merged PRs can retain historical auto_merge metadata, so only active requests are revoked. Current typecheck, test typecheck and lint pass. The full npm suite is still progressing through existing closeout integration cases; coverage and complete release gates remain pending."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:45:42.355Z"}],"before_hash":"68136f263196e2650f35dcd2d4485b30f3b00d0eac5a8d2e7ffdb19786c445af","after_hash":"7ce2ce6f6cb55e3d5c07287f405ed5c798a52c404a0b3a9c5c6d550adcdd9feb","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":"da14e0c87dfd90c63ca39ddf2c71789998436a340416e2dee48a582d8dbc1138"} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml index d0f09e5a..bfae618b 100644 --- a/.github/workflows/pm-github-dependabot-merge.yml +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -43,7 +43,7 @@ jobs: run: | set -euo pipefail [[ "${REPOSITORY}" =~ ^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$ && "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]] - armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.auto_merge != null' 2>/dev/null)" || armed=true + armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.state != "closed" and .auto_merge != null' 2>/dev/null)" || armed=true if [[ "${armed}" != false ]]; then if ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then echo '::error::Cannot revoke auto-merge; check token write permissions immediately.' @@ -71,7 +71,7 @@ jobs: fi if [[ ! "${policy_sha}" =~ ^[a-f0-9]{40}$ ]] || ! gh api "repos/unbraind/pm-github/contents/scripts/dependabot-merge.ts?ref=${policy_sha}" \ -H 'Accept: application/vnd.github.raw+json' > "${RUNNER_TEMP}/dependabot-merge.ts"; then - armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.auto_merge != null' 2>/dev/null)" || armed=true + armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.state != "closed" and .auto_merge != null' 2>/dev/null)" || armed=true if [[ "${armed}" != false ]] && ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then echo '::error::Cannot revoke auto-merge after policy load failure; check token write permissions immediately.' exit 1 diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts index 50974864..790a77d8 100644 --- a/scripts/dependabot-merge.ts +++ b/scripts/dependabot-merge.ts @@ -65,7 +65,11 @@ export function runDependabotMerge(input: MergeInput, gh: (args: readonly string const endpoint = `repos/${input.repository}/pulls/${input.number}`; const revoke = () => { let armed = true; - try { armed = (JSON.parse(gh(["api", endpoint])) as { auto_merge: unknown }).auto_merge !== null; } catch { /* Unreadable state still owes revocation. */ } + try { + const current = JSON.parse(gh(["api", endpoint])) as { state?: string; auto_merge: unknown }; + // Merged PRs retain historical auto_merge metadata; it is no longer armed. + armed = current.state !== "closed" && current.auto_merge !== null; + } catch { /* Unreadable state still owes revocation. */ } if (armed) { try { gh(["pr", "merge", ...target, "--disable-auto"]); } catch { throw new Error("Auto-merge revocation denied or unavailable; check token write permissions immediately."); } diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 14ab1441..c63aa3ff 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -163,6 +163,12 @@ test("no existing auto-merge requires no disable mutation", () => { f.execute(); assert.equal(f.calls.length, 1); }); +test("closed PR historical auto_merge metadata is not an active request", () => { + const f = fixture(); f.state.state = "closed"; + assert.equal(f.execute().armed, false); + assert.ok(!f.calls.some(args => args[0] === "pr")); +}); + test("revocation denial surfaces an operational error instead of false success", () => { const f = fixture(); f.failRevoke = true; assert.throws(f.execute, /revocation denied or unavailable/); }); From d6c10bf58144b1a5b82fc261c4200c139d290e59 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 02:06:40 +0100 Subject: [PATCH 03/19] Cover fresh Dependabot requests and track guarded activation --- .../pm-github-dependabot-activation.toon | 15 ++++++++++++ .../features/pm-github-dependabot-merge.toon | 24 +++++++++++++------ .../pm-github-dependabot-activation.jsonl | 1 + .../history/pm-github-dependabot-merge.jsonl | 4 ++++ scripts/dependabot-merge.ts | 2 ++ test/dependabot-merge.test.ts | 9 ++++++- 6 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 .agents/pm/chores/pm-github-dependabot-activation.toon create mode 100644 .agents/pm/history/pm-github-dependabot-activation.jsonl diff --git a/.agents/pm/chores/pm-github-dependabot-activation.toon b/.agents/pm/chores/pm-github-dependabot-activation.toon new file mode 100644 index 00000000..16db214b --- /dev/null +++ b/.agents/pm/chores/pm-github-dependabot-activation.toon @@ -0,0 +1,15 @@ +id: pm-github-dependabot-activation +title: Activate the verified Dependabot merge caller after review and token provisioning +description: Read-only Actions API inspection found the existing dependabot-auto-merge.yml workflow is disabled_manually. Source migration alone does not activate it. Keep activation separate from the reusable workflow implementation and coordinate with companion pm-cli-website-wis6. +type: Chore +status: blocked +priority: 2 +tags[4]: ci,dependabot,follow-up,security +created_at: "2026-11-05T00:52:51.053Z" +updated_at: "2026-11-05T00:52:51.053Z" +author: codex-agent +acceptance_criteria: "After reviewed delivery and release, verify the selected caller pins a release containing the policy or uses the reviewed local path.; Provision a repository-scoped merge token as both Actions and Dependabot secrets when needed, and verify server-enforced protected-branch checks and review requirements.; With owner authorization enable the previously disabled workflow and record live bot-only arm plus foreign-commit revocation acceptance without permitting an unintended merge." +blocked_by: pm-github-dependabot-merge +dependencies[1]{id,kind,created_at,author,source_kind,author_source}: + pm-github-dependabot-merge,blocked_by,"2026-11-05T00:52:51.053Z",codex-agent,"cli:create:blocked_by",detected +body: "" diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index ff0cffb3..4bb1acda 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,22 +6,32 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T00:45:42.355Z" +updated_at: "2026-11-05T01:06:36.971Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[5]{created_at,author,text}: +comments[8]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." "2026-11-05T00:41:20.832Z",codex-agent,"Additional review: --match-head-commit is an expected-head check, not a durable server-side lease; protected-branch requirements remain necessary for a literal future-push guarantee. Proposed diff scan through the current source runTrackerGate passed: 8 files, 644 added lines, zero findings, zero allowlist entries. The installed pm github gate command instead returned one absolute-host-path finding without an offset for the same diff; current source scanning finds none. This installed-command/source divergence is a PM extension UX gap to report, not changed here." "2026-11-05T00:45:42.353Z",codex-agent,"Final regression corpus: 68 focused tests passed through the pinned pm test runner. Repeated the behavioral revert against the final policy in a disposable copy: removing only the provenance filter caused 13 failures and 55 passes; the package worktree was never mutated. Added a real-API edge case: closed or merged PRs can retain historical auto_merge metadata, so only active requests are revoked. Current typecheck, test typecheck and lint pass. The full npm suite is still progressing through existing closeout integration cases; coverage and complete release gates remain pending." -files[4]{path,scope}: - .github/workflows/dependabot-auto-merge.yml,project - .github/workflows/pm-github-dependabot-merge.yml,project - scripts/dependabot-merge.ts,project - test/dependabot-merge.test.ts,project + "2026-11-05T00:47:36.608Z",codex-agent,"PR opened: https://github.com/unbraind/pm-github/pull/221 at aaffdb6da1a6a5cea010f125aa562fb5cb6fbd72. Published title, feature-item link, plain-text companion reference and public-safe body were read back and verified. Code commits: dd46efa and aaffdb6. The full npm suite is still running; PR body explicitly marks complete release and coverage gates pending. No source changes are planned unless a gate finds a defect." + "2026-11-05T00:52:55.096Z",codex-agent,"Read-only Actions workflow API verification found dependabot-auto-merge.yml is disabled_manually. No workflow state was changed. Created follow-up pm-github-dependabot-activation for reviewed activation, token provisioning, protected-branch requirements and controlled live acceptance. This delivery migrates the source caller; it does not establish that repository automation is enabled. CodeQL passed on aaffdb6; the CI matrix is queued or starting." + "2026-11-05T01:06:36.970Z",codex-agent,"The focused suite now passes 69 tests, adding the exact fresh-open state with auto_merge=null alongside the existing armed-request revocation case. Test typecheck, ESLint and docstring gate pass after this addition. The first existing closeout workflow integration scenario passed; current long subprocesses are fixture package installs and workflow execution. Source policy behavior remains unchanged from aaffdb6; only helper documentation and the new acceptance case were added." +files[5]: + - path: .github/workflows/dependabot-auto-merge.yml + scope: project + - path: .github/workflows/pm-github-dependabot-merge.yml + scope: project + - path: CHANGELOG.md + scope: project + note: Regenerated unreleased security entry + - path: scripts/dependabot-merge.ts + scope: project + - path: test/dependabot-merge.test.ts + scope: project tests[1]{command,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts,project,120,codex-agent,"2026-11-05T00:34:44.010Z",local_mutation,feat/reusable-dependabot-merge docs[1]{path,scope,note}: diff --git a/.agents/pm/history/pm-github-dependabot-activation.jsonl b/.agents/pm/history/pm-github-dependabot-activation.jsonl new file mode 100644 index 00000000..16150fed --- /dev/null +++ b/.agents/pm/history/pm-github-dependabot-activation.jsonl @@ -0,0 +1 @@ +{"hash_algorithm":"sha256","ts":"2026-11-05T00:52:51.053Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-dependabot-activation"},{"op":"add","path":"/metadata/title","value":"Activate the verified Dependabot merge caller after review and token provisioning"},{"op":"add","path":"/metadata/description","value":"Read-only Actions API inspection found the existing dependabot-auto-merge.yml workflow is disabled_manually. Source migration alone does not activate it. Keep activation separate from the reusable workflow implementation and coordinate with companion pm-cli-website-wis6."},{"op":"add","path":"/metadata/type","value":"Chore"},{"op":"add","path":"/metadata/status","value":"blocked"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["ci","dependabot","follow-up","security"]},{"op":"add","path":"/metadata/created_at","value":"2026-11-05T00:52:51.053Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-11-05T00:52:51.053Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"After reviewed delivery and release, verify the selected caller pins a release containing the policy or uses the reviewed local path.; Provision a repository-scoped merge token as both Actions and Dependabot secrets when needed, and verify server-enforced protected-branch checks and review requirements.; With owner authorization enable the previously disabled workflow and record live bot-only arm plus foreign-commit revocation acceptance without permitting an unintended merge."},{"op":"add","path":"/metadata/blocked_by","value":"pm-github-dependabot-merge"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-github-dependabot-merge","kind":"blocked_by","created_at":"2026-11-05T00:52:51.053Z","author":"codex-agent","source_kind":"cli:create:blocked_by","author_source":"detected"}]}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"5f1e4235e95edabb65ffd2d44d511b21e074255ab28656811e7608d7b6e13b7c","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":"3b1bc39b31fdb456d21bcd0e24917e73c8e19a19f714eee50ee3460d33da7d80"} diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index caf53f21..6347011c 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -9,3 +9,7 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T00:39:54.553Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:39:54.549Z","author":"codex-agent","text":"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:39:54.553Z"}],"before_hash":"975f389779183754860165547d28086b22a9c2d07a5d534ac4ecf28561d47d8c","after_hash":"f42631513237f66b23bdc985e43a88df4894c2e71d337cf61d25b90d11aff9ab","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":"3617060234162c51d76393910a600ff5b3cfc993285f09cafd4ea04034253882"} {"hash_algorithm":"sha256","ts":"2026-11-05T00:41:20.832Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:41:20.832Z","author":"codex-agent","text":"Additional review: --match-head-commit is an expected-head check, not a durable server-side lease; protected-branch requirements remain necessary for a literal future-push guarantee. Proposed diff scan through the current source runTrackerGate passed: 8 files, 644 added lines, zero findings, zero allowlist entries. The installed pm github gate command instead returned one absolute-host-path finding without an offset for the same diff; current source scanning finds none. This installed-command/source divergence is a PM extension UX gap to report, not changed here."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:41:20.832Z"}],"before_hash":"f42631513237f66b23bdc985e43a88df4894c2e71d337cf61d25b90d11aff9ab","after_hash":"68136f263196e2650f35dcd2d4485b30f3b00d0eac5a8d2e7ffdb19786c445af","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":"fbfb24c25c58491279a1f17f23666bfafcce8de795193e667f8b14b11319e662"} {"hash_algorithm":"sha256","ts":"2026-11-05T00:45:42.355Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:45:42.353Z","author":"codex-agent","text":"Final regression corpus: 68 focused tests passed through the pinned pm test runner. Repeated the behavioral revert against the final policy in a disposable copy: removing only the provenance filter caused 13 failures and 55 passes; the package worktree was never mutated. Added a real-API edge case: closed or merged PRs can retain historical auto_merge metadata, so only active requests are revoked. Current typecheck, test typecheck and lint pass. The full npm suite is still progressing through existing closeout integration cases; coverage and complete release gates remain pending."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:45:42.355Z"}],"before_hash":"68136f263196e2650f35dcd2d4485b30f3b00d0eac5a8d2e7ffdb19786c445af","after_hash":"7ce2ce6f6cb55e3d5c07287f405ed5c798a52c404a0b3a9c5c6d550adcdd9feb","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":"da14e0c87dfd90c63ca39ddf2c71789998436a340416e2dee48a582d8dbc1138"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:47:36.613Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:47:36.608Z","author":"codex-agent","text":"PR opened: https://github.com/unbraind/pm-github/pull/221 at aaffdb6da1a6a5cea010f125aa562fb5cb6fbd72. Published title, feature-item link, plain-text companion reference and public-safe body were read back and verified. Code commits: dd46efa and aaffdb6. The full npm suite is still running; PR body explicitly marks complete release and coverage gates pending. No source changes are planned unless a gate finds a defect."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:47:36.613Z"}],"before_hash":"7ce2ce6f6cb55e3d5c07287f405ed5c798a52c404a0b3a9c5c6d550adcdd9feb","after_hash":"942dba6671d869444b46ad8074e97c52b328a984c8bdc7aa1a5a1c2010610715","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":"b53e0f4bf6fe53fcf9d3db4dd38d16b0c06dfc9fc0711a27cc24e9e29848a0b6"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:49:39.601Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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":"scripts/dependabot-merge.ts"},{"op":"add","path":"/metadata/files/2/note","value":"Regenerated unreleased security entry"},{"op":"replace","path":"/metadata/files/2/path","value":"CHANGELOG.md"},{"op":"add","path":"/metadata/files/4","value":{"path":"test/dependabot-merge.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:49:39.601Z"}],"before_hash":"942dba6671d869444b46ad8074e97c52b328a984c8bdc7aa1a5a1c2010610715","after_hash":"1bd412950fe2e7d45a4efcd2aa0e3b42f51c4dd3e52fd707195bd2a190694d36","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":"fac0c32bb7d4e321708b08ba5a4cba4481b64aba8410fd4d5c0ef0629db18d03"} +{"hash_algorithm":"sha256","ts":"2026-11-05T00:52:55.097Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:52:55.096Z","author":"codex-agent","text":"Read-only Actions workflow API verification found dependabot-auto-merge.yml is disabled_manually. No workflow state was changed. Created follow-up pm-github-dependabot-activation for reviewed activation, token provisioning, protected-branch requirements and controlled live acceptance. This delivery migrates the source caller; it does not establish that repository automation is enabled. CodeQL passed on aaffdb6; the CI matrix is queued or starting."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:52:55.097Z"}],"before_hash":"1bd412950fe2e7d45a4efcd2aa0e3b42f51c4dd3e52fd707195bd2a190694d36","after_hash":"e475464113cfdf8fe38a98b17bf4c8c6b7a56e286eb22899a72f109fb23ab52c","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":"16885035ec185b650be89cd511b2479c43f5030d61488efd1e90ff83bd51a957"} +{"hash_algorithm":"sha256","ts":"2026-11-05T01:06:36.971Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T01:06:36.970Z","author":"codex-agent","text":"The focused suite now passes 69 tests, adding the exact fresh-open state with auto_merge=null alongside the existing armed-request revocation case. Test typecheck, ESLint and docstring gate pass after this addition. The first existing closeout workflow integration scenario passed; current long subprocesses are fixture package installs and workflow execution. Source policy behavior remains unchanged from aaffdb6; only helper documentation and the new acceptance case were added."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:06:36.971Z"}],"before_hash":"e475464113cfdf8fe38a98b17bf4c8c6b7a56e286eb22899a72f109fb23ab52c","after_hash":"b427e901bedb86b6467e3651f2c23762a33c69b17d8464a79d13aff816327f51","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":"79c70c26b3a19888a6cd212bc7a0086b3a26023bb2517ec2925efeb6eead5a09"} diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts index 790a77d8..40e2a54a 100644 --- a/scripts/dependabot-merge.ts +++ b/scripts/dependabot-merge.ts @@ -63,6 +63,7 @@ export function runDependabotMerge(input: MergeInput, gh: (args: readonly string } const target = [input.number, "--repo", input.repository]; const endpoint = `repos/${input.repository}/pulls/${input.number}`; + /** Clear active requests, attempting revocation even when state cannot be read. */ const revoke = () => { let armed = true; try { @@ -79,6 +80,7 @@ export function runDependabotMerge(input: MergeInput, gh: (args: readonly string if (input.phase === "revoke") return { armed: false, message: "Previous auto-merge request revoked before metadata." }; let message: string; try { + /** Obtain independent snapshots around pagination and the arm request. */ const read = () => JSON.parse(gh(["api", endpoint])) as PullRequest; const before = read(); // One JSON object per line works with both older and current gh releases. diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index c63aa3ff..ab754a8e 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -51,7 +51,14 @@ function fixture() { return f; } -test("Dependabot opens a verified PR: revoke first, then arm squash at the checked head", () => { +test("Dependabot opens a verified PR with no existing request: arm squash", () => { + const f = fixture(); f.state.auto_merge = null; + assert.equal(f.execute().armed, true); + assert.ok(!f.calls.some(args => args.includes("--disable-auto"))); + assert.ok(f.calls.find(args => args.includes("--match-head-commit"))); +}); + +test("existing request is revoked before arming the verified Dependabot head", () => { const f = fixture(); assert.equal(f.execute().armed, true); assert.ok(f.calls[1]!.includes("--disable-auto")); From e12d12ec1f117ef95f53f92048bc23d6012997b0 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 02:19:55 +0100 Subject: [PATCH 04/19] Keep the workflow launcher compatible with publication attestation --- .agents/pm/features/pm-github-dependabot-merge.toon | 6 ++++-- .agents/pm/history/pm-github-dependabot-merge.jsonl | 2 ++ .github/workflows/pm-github-dependabot-merge.yml | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 4bb1acda..f4593f7e 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,12 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T01:06:36.971Z" +updated_at: "2026-11-05T01:19:54.728Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[8]{created_at,author,text}: +comments[10]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -20,6 +20,8 @@ comments[8]{created_at,author,text}: "2026-11-05T00:47:36.608Z",codex-agent,"PR opened: https://github.com/unbraind/pm-github/pull/221 at aaffdb6da1a6a5cea010f125aa562fb5cb6fbd72. Published title, feature-item link, plain-text companion reference and public-safe body were read back and verified. Code commits: dd46efa and aaffdb6. The full npm suite is still running; PR body explicitly marks complete release and coverage gates pending. No source changes are planned unless a gate finds a defect." "2026-11-05T00:52:55.096Z",codex-agent,"Read-only Actions workflow API verification found dependabot-auto-merge.yml is disabled_manually. No workflow state was changed. Created follow-up pm-github-dependabot-activation for reviewed activation, token provisioning, protected-branch requirements and controlled live acceptance. This delivery migrates the source caller; it does not establish that repository automation is enabled. CodeQL passed on aaffdb6; the CI matrix is queued or starting." "2026-11-05T01:06:36.970Z",codex-agent,"The focused suite now passes 69 tests, adding the exact fresh-open state with auto_merge=null alongside the existing armed-request revocation case. Test typecheck, ESLint and docstring gate pass after this addition. The first existing closeout workflow integration scenario passed; current long subprocesses are fixture package installs and workflow execution. Source policy behavior remains unchanged from aaffdb6; only helper documentation and the new acceptance case were added." + "2026-11-05T01:11:46.208Z",codex-agent,"Fixture timing isolated repeated npm registry/cache checks as a major local bottleneck: the same existing numeric-pagination workflow case passed in 299.94 seconds normally and 67.47 seconds total with NPM_CONFIG_PREFER_OFFLINE=true and the public npm configuration. Interrupted the first full npm run with exit 130 after verifying and stopping only its own process group; it is not counted as passing. Restarted the entire suite with cache preference, NPM_CONFIG_USERCONFIG=/dev/null, isolated Git config, low priority and at most two workers. Versions, fixtures, assertions, coverage scope and thresholds are unchanged; missing packages still use the registry. The focused suite remains 69 passed." + "2026-11-05T01:19:54.725Z",codex-agent,"CI on aaffdb6 found one related integration failure: the publication-attestation scanner treated TypeScript template literals in the quoted workflow heredoc as unresolved shell execution. The run reported 1712 tests, 1711 passes and 1 failure, with all four source coverage percentages at 100; this is not a passing gate. Evidence: https://github.com/unbraind/pm-github/actions/runs/41943832975/job/126970983903 . Replaced only the generated launcher template literals with string concatenation. The attestation gate now passes, and the combined production-policy plus attestation suite passes 77 tests. No scanner policy, thresholds or merge decisions were changed. Full local npm tests continue using the public npm configuration and cache preference." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 6347011c..3637df6c 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -13,3 +13,5 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T00:49:39.601Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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":"scripts/dependabot-merge.ts"},{"op":"add","path":"/metadata/files/2/note","value":"Regenerated unreleased security entry"},{"op":"replace","path":"/metadata/files/2/path","value":"CHANGELOG.md"},{"op":"add","path":"/metadata/files/4","value":{"path":"test/dependabot-merge.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:49:39.601Z"}],"before_hash":"942dba6671d869444b46ad8074e97c52b328a984c8bdc7aa1a5a1c2010610715","after_hash":"1bd412950fe2e7d45a4efcd2aa0e3b42f51c4dd3e52fd707195bd2a190694d36","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":"fac0c32bb7d4e321708b08ba5a4cba4481b64aba8410fd4d5c0ef0629db18d03"} {"hash_algorithm":"sha256","ts":"2026-11-05T00:52:55.097Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T00:52:55.096Z","author":"codex-agent","text":"Read-only Actions workflow API verification found dependabot-auto-merge.yml is disabled_manually. No workflow state was changed. Created follow-up pm-github-dependabot-activation for reviewed activation, token provisioning, protected-branch requirements and controlled live acceptance. This delivery migrates the source caller; it does not establish that repository automation is enabled. CodeQL passed on aaffdb6; the CI matrix is queued or starting."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T00:52:55.097Z"}],"before_hash":"1bd412950fe2e7d45a4efcd2aa0e3b42f51c4dd3e52fd707195bd2a190694d36","after_hash":"e475464113cfdf8fe38a98b17bf4c8c6b7a56e286eb22899a72f109fb23ab52c","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":"16885035ec185b650be89cd511b2479c43f5030d61488efd1e90ff83bd51a957"} {"hash_algorithm":"sha256","ts":"2026-11-05T01:06:36.971Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T01:06:36.970Z","author":"codex-agent","text":"The focused suite now passes 69 tests, adding the exact fresh-open state with auto_merge=null alongside the existing armed-request revocation case. Test typecheck, ESLint and docstring gate pass after this addition. The first existing closeout workflow integration scenario passed; current long subprocesses are fixture package installs and workflow execution. Source policy behavior remains unchanged from aaffdb6; only helper documentation and the new acceptance case were added."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:06:36.971Z"}],"before_hash":"e475464113cfdf8fe38a98b17bf4c8c6b7a56e286eb22899a72f109fb23ab52c","after_hash":"b427e901bedb86b6467e3651f2c23762a33c69b17d8464a79d13aff816327f51","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":"79c70c26b3a19888a6cd212bc7a0086b3a26023bb2517ec2925efeb6eead5a09"} +{"hash_algorithm":"sha256","ts":"2026-11-05T01:11:46.208Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T01:11:46.208Z","author":"codex-agent","text":"Fixture timing isolated repeated npm registry/cache checks as a major local bottleneck: the same existing numeric-pagination workflow case passed in 299.94 seconds normally and 67.47 seconds total with NPM_CONFIG_PREFER_OFFLINE=true and the public npm configuration. Interrupted the first full npm run with exit 130 after verifying and stopping only its own process group; it is not counted as passing. Restarted the entire suite with cache preference, NPM_CONFIG_USERCONFIG=/dev/null, isolated Git config, low priority and at most two workers. Versions, fixtures, assertions, coverage scope and thresholds are unchanged; missing packages still use the registry. The focused suite remains 69 passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:11:46.208Z"}],"before_hash":"b427e901bedb86b6467e3651f2c23762a33c69b17d8464a79d13aff816327f51","after_hash":"0d7d9baaca6bc1198f72529a63cfdbe72362020938e99f4e9d4894f3ee277204","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":"3d5e385aa5217ab7cdf38fb9faa9926a50330c0c7aa20409def9061d0b4f34ee"} +{"hash_algorithm":"sha256","ts":"2026-11-05T01:19:54.728Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-11-05T01:19:54.725Z","author":"codex-agent","text":"CI on aaffdb6 found one related integration failure: the publication-attestation scanner treated TypeScript template literals in the quoted workflow heredoc as unresolved shell execution. The run reported 1712 tests, 1711 passes and 1 failure, with all four source coverage percentages at 100; this is not a passing gate. Evidence: https://github.com/unbraind/pm-github/actions/runs/41943832975/job/126970983903 . Replaced only the generated launcher template literals with string concatenation. The attestation gate now passes, and the combined production-policy plus attestation suite passes 77 tests. No scanner policy, thresholds or merge decisions were changed. Full local npm tests continue using the public npm configuration and cache preference."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:19:54.728Z"}],"before_hash":"0d7d9baaca6bc1198f72529a63cfdbe72362020938e99f4e9d4894f3ee277204","after_hash":"e569f052d1419eb9499af7fd1f746827a5361bbda6cf45ef8f912fd168923b3b","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":"4d965fb4e88a6a07a9443baebcef9c1e598a3eea09954a4a842140c9a6286540"} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml index bfae618b..590fefa5 100644 --- a/.github/workflows/pm-github-dependabot-merge.yml +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -91,8 +91,8 @@ jobs: head: e.EVENT_HEAD!, phase: e.PHASE!, metadataOutcome: e.METADATA_OUTCOME ?? '', group: e.DEPENDENCY_GROUP ?? '', updateType: e.UPDATE_TYPE ?? '', extraGroups: e.EXTRA_GROUPS!, }); - appendFileSync(e.GITHUB_STEP_SUMMARY!, `### Dependabot merge policy\n\n${result.message}\n`); - console.log(`::notice::${result.message.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A')}`); + appendFileSync(e.GITHUB_STEP_SUMMARY!, '### Dependabot merge policy\n\n' + result.message + '\n'); + console.log('::notice::' + result.message.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A')); TS printf 'loaded=true\n' >> "${GITHUB_OUTPUT}" From e8762abc05f8ffb2b4be6c141615f615e09fc577 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 02:43:51 +0100 Subject: [PATCH 05/19] Avoid revoking requests on token-generated arm events --- .../pm/features/pm-github-dependabot-merge.toon | 6 ++++-- .../pm/history/pm-github-dependabot-merge.jsonl | 2 ++ .github/workflows/dependabot-auto-merge.yml | 2 +- README.md | 5 ++++- test/dependabot-merge.test.ts | 15 +++++++++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index f4593f7e..ce0cbdb4 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,12 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T01:19:54.728Z" +updated_at: "2026-11-05T01:43:50.321Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[10]{created_at,author,text}: +comments[12]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -22,6 +22,8 @@ comments[10]{created_at,author,text}: "2026-11-05T01:06:36.970Z",codex-agent,"The focused suite now passes 69 tests, adding the exact fresh-open state with auto_merge=null alongside the existing armed-request revocation case. Test typecheck, ESLint and docstring gate pass after this addition. The first existing closeout workflow integration scenario passed; current long subprocesses are fixture package installs and workflow execution. Source policy behavior remains unchanged from aaffdb6; only helper documentation and the new acceptance case were added." "2026-11-05T01:11:46.208Z",codex-agent,"Fixture timing isolated repeated npm registry/cache checks as a major local bottleneck: the same existing numeric-pagination workflow case passed in 299.94 seconds normally and 67.47 seconds total with NPM_CONFIG_PREFER_OFFLINE=true and the public npm configuration. Interrupted the first full npm run with exit 130 after verifying and stopping only its own process group; it is not counted as passing. Restarted the entire suite with cache preference, NPM_CONFIG_USERCONFIG=/dev/null, isolated Git config, low priority and at most two workers. Versions, fixtures, assertions, coverage scope and thresholds are unchanged; missing packages still use the registry. The focused suite remains 69 passed." "2026-11-05T01:19:54.725Z",codex-agent,"CI on aaffdb6 found one related integration failure: the publication-attestation scanner treated TypeScript template literals in the quoted workflow heredoc as unresolved shell execution. The run reported 1712 tests, 1711 passes and 1 failure, with all four source coverage percentages at 100; this is not a passing gate. Evidence: https://github.com/unbraind/pm-github/actions/runs/41943832975/job/126970983903 . Replaced only the generated launcher template literals with string concatenation. The attestation gate now passes, and the combined production-policy plus attestation suite passes 77 tests. No scanner policy, thresholds or merge decisions were changed. Full local npm tests continue using the public npm configuration and cache preference." + "2026-11-05T01:26:13.405Z",codex-agent,"Corrected code is pushed at e12d12ec1f117ef95f53f92048bc23d6012997b0. CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41947808408 . Current CI: https://github.com/unbraind/pm-github/actions/runs/41947808424 . Verified the superseded run belonged to PR 221 and branch feat/reusable-dependabot-merge before cancelling its remaining jobs to free capacity for the corrected head; the original failure receipt is retained and no cancellation is counted as approval. A real raw-contents API fetch at e12d12e is byte-identical to scripts/dependabot-merge.ts (SHA-256 d9a8c70f2aeffe832f840a1402701b64d9731d7d49ea85892e90ca359015e799). Final 69-case provenance revert proof gives 13 failures and 56 passes; the pinned linked test runner passes all 69 afterward." + "2026-11-05T01:43:50.312Z",codex-agent,"Review found and fixed a custom-token event loop in the caller: auto_merge_enabled events can belong to the automation credential owner, so subscribing to them would immediately revoke a valid bot-only request under the required actor guard. Removed that event from the own caller and README snippet; synchronize remains subscribed. Added a two-event regression that executes the production policy: token-generated arm events preserve the request, then a foreign synchronize revokes it. Reintroducing only the old event subscription in a disposable copy makes that one behavioral test fail; the unchanged package worktree passes all 70 policy/workflow tests. Combined policy and publication-attestation tests pass 78 cases. Test typecheck, lint and publication-attestation gate pass. No predicate, permissions, thresholds or branch configuration changed." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 3637df6c..85629685 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -15,3 +15,5 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T01:06:36.971Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T01:06:36.970Z","author":"codex-agent","text":"The focused suite now passes 69 tests, adding the exact fresh-open state with auto_merge=null alongside the existing armed-request revocation case. Test typecheck, ESLint and docstring gate pass after this addition. The first existing closeout workflow integration scenario passed; current long subprocesses are fixture package installs and workflow execution. Source policy behavior remains unchanged from aaffdb6; only helper documentation and the new acceptance case were added."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:06:36.971Z"}],"before_hash":"e475464113cfdf8fe38a98b17bf4c8c6b7a56e286eb22899a72f109fb23ab52c","after_hash":"b427e901bedb86b6467e3651f2c23762a33c69b17d8464a79d13aff816327f51","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":"79c70c26b3a19888a6cd212bc7a0086b3a26023bb2517ec2925efeb6eead5a09"} {"hash_algorithm":"sha256","ts":"2026-11-05T01:11:46.208Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T01:11:46.208Z","author":"codex-agent","text":"Fixture timing isolated repeated npm registry/cache checks as a major local bottleneck: the same existing numeric-pagination workflow case passed in 299.94 seconds normally and 67.47 seconds total with NPM_CONFIG_PREFER_OFFLINE=true and the public npm configuration. Interrupted the first full npm run with exit 130 after verifying and stopping only its own process group; it is not counted as passing. Restarted the entire suite with cache preference, NPM_CONFIG_USERCONFIG=/dev/null, isolated Git config, low priority and at most two workers. Versions, fixtures, assertions, coverage scope and thresholds are unchanged; missing packages still use the registry. The focused suite remains 69 passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:11:46.208Z"}],"before_hash":"b427e901bedb86b6467e3651f2c23762a33c69b17d8464a79d13aff816327f51","after_hash":"0d7d9baaca6bc1198f72529a63cfdbe72362020938e99f4e9d4894f3ee277204","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":"3d5e385aa5217ab7cdf38fb9faa9926a50330c0c7aa20409def9061d0b4f34ee"} {"hash_algorithm":"sha256","ts":"2026-11-05T01:19:54.728Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-11-05T01:19:54.725Z","author":"codex-agent","text":"CI on aaffdb6 found one related integration failure: the publication-attestation scanner treated TypeScript template literals in the quoted workflow heredoc as unresolved shell execution. The run reported 1712 tests, 1711 passes and 1 failure, with all four source coverage percentages at 100; this is not a passing gate. Evidence: https://github.com/unbraind/pm-github/actions/runs/41943832975/job/126970983903 . Replaced only the generated launcher template literals with string concatenation. The attestation gate now passes, and the combined production-policy plus attestation suite passes 77 tests. No scanner policy, thresholds or merge decisions were changed. Full local npm tests continue using the public npm configuration and cache preference."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:19:54.728Z"}],"before_hash":"0d7d9baaca6bc1198f72529a63cfdbe72362020938e99f4e9d4894f3ee277204","after_hash":"e569f052d1419eb9499af7fd1f746827a5361bbda6cf45ef8f912fd168923b3b","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":"4d965fb4e88a6a07a9443baebcef9c1e598a3eea09954a4a842140c9a6286540"} +{"hash_algorithm":"sha256","ts":"2026-11-05T01:26:13.406Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/10","value":{"created_at":"2026-11-05T01:26:13.405Z","author":"codex-agent","text":"Corrected code is pushed at e12d12ec1f117ef95f53f92048bc23d6012997b0. CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41947808408 . Current CI: https://github.com/unbraind/pm-github/actions/runs/41947808424 . Verified the superseded run belonged to PR 221 and branch feat/reusable-dependabot-merge before cancelling its remaining jobs to free capacity for the corrected head; the original failure receipt is retained and no cancellation is counted as approval. A real raw-contents API fetch at e12d12e is byte-identical to scripts/dependabot-merge.ts (SHA-256 d9a8c70f2aeffe832f840a1402701b64d9731d7d49ea85892e90ca359015e799). Final 69-case provenance revert proof gives 13 failures and 56 passes; the pinned linked test runner passes all 69 afterward."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:26:13.406Z"}],"before_hash":"e569f052d1419eb9499af7fd1f746827a5361bbda6cf45ef8f912fd168923b3b","after_hash":"e02f4ed527c108f9894604643ae7752b31161772fa4128c11e085d134f31c684","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":"fb4589d4c8201f77a7a4a6be8df00aba6738d494874ee93427ea387333b5cbc3"} +{"hash_algorithm":"sha256","ts":"2026-11-05T01:43:50.321Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/11","value":{"created_at":"2026-11-05T01:43:50.312Z","author":"codex-agent","text":"Review found and fixed a custom-token event loop in the caller: auto_merge_enabled events can belong to the automation credential owner, so subscribing to them would immediately revoke a valid bot-only request under the required actor guard. Removed that event from the own caller and README snippet; synchronize remains subscribed. Added a two-event regression that executes the production policy: token-generated arm events preserve the request, then a foreign synchronize revokes it. Reintroducing only the old event subscription in a disposable copy makes that one behavioral test fail; the unchanged package worktree passes all 70 policy/workflow tests. Combined policy and publication-attestation tests pass 78 cases. Test typecheck, lint and publication-attestation gate pass. No predicate, permissions, thresholds or branch configuration changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:43:50.321Z"}],"before_hash":"e02f4ed527c108f9894604643ae7752b31161772fa4128c11e085d134f31c684","after_hash":"c71c69a4bfd0871b273ae5c8a322132023007f1861606a61ca0add426aa4998c","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":"1259521b6c66803a0cd430c3a5e13eef57a6576f390e1ea51f1058250c368963"} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 165a9ade..33f7792f 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -2,7 +2,7 @@ name: dependabot-auto-merge on: pull_request: - types: [opened, reopened, synchronize, ready_for_review, edited, auto_merge_enabled] + types: [opened, reopened, synchronize, ready_for_review, edited] permissions: {} diff --git a/README.md b/README.md index c8d73199..a4fbdf9c 100644 --- a/README.md +++ b/README.md @@ -857,7 +857,7 @@ updates outside those groups wait for review. The arm request includes name: dependabot-auto-merge on: pull_request: - types: [opened, reopened, synchronize, ready_for_review, edited, auto_merge_enabled] + types: [opened, reopened, synchronize, ready_for_review, edited] permissions: {} jobs: verified-auto-merge: @@ -901,6 +901,9 @@ that self-call fails closed because the trusted policy file is absent. Migration: replace the **entire** copied `dependabot-auto-merge.yml` with the caller above. Retain `synchronize` and omit author/actor job guards: the job must run when a foreign actor pushes so it can revoke an earlier request. +Keep `auto_merge_enabled` out of the caller's event types: a custom token's arm +event belongs to its automation account and would immediately revoke its own +request under the strict Dependabot actor policy. Remove any other workflow that arms Dependabot auto-merge, and clear existing requests when adopting the caller; installing a workflow does not retroactively trigger it for every open PR. This repository uses the supported local path. diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index ab754a8e..0127f394 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -213,6 +213,21 @@ test("workflow policy: pinned actions, minimal grants, no PR checkout or interpo const caller = readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8"); assert.match(caller, /uses: \.\/\.github\/workflows\/pm-github-dependabot-merge.yml/); assert.match(caller, /synchronize/); assert.doesNotMatch(caller, /if:|pull_request_target/); + assert.doesNotMatch(caller, /auto_merge_enabled/, "custom-token arm events must not revoke their own request"); +}); + +test("custom-token arm event preserves the request; a foreign synchronize event revokes it", () => { + const caller = parse(readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8")) as { on: { pull_request: { types: string[] } } }; + const subscribed = caller.on.pull_request.types; + const f = fixture(); f.state.auto_merge = null; + assert.equal(f.execute().armed, true); + // PAT/App-generated events belong to the credential owner, not Dependabot. + f.input.actor = "automation"; f.input.triggeringActor = "automation"; + if (subscribed.includes("auto_merge_enabled")) f.execute(); + assert.notEqual(f.state.auto_merge, null, "the token's own arm event must not disarm the request"); + f.input.actor = "outside"; f.input.triggeringActor = "outside"; f.commit.author.login = "outside"; + if (subscribed.includes("synchronize")) f.execute(); + assert.equal(f.state.auto_merge, null, "foreign synchronize must still revoke the request"); }); for (const mode of ["external", "local", "load-failed", "invalid-sha", "local-ref-failed"]) { From b1e8285b1ff8896d9c81de3ca92a9b93ed2e6eec Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 03:05:48 +0100 Subject: [PATCH 06/19] Respect the current command environment under native Bun --- .../features/pm-github-dependabot-merge.toon | 5 +++-- .../history/pm-github-dependabot-merge.jsonl | 1 + scripts/dependabot-merge.ts | 4 ++-- test/dependabot-merge.test.ts | 22 ++++++++++++++----- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index ce0cbdb4..97b3ac02 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,12 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T01:43:50.321Z" +updated_at: "2026-11-05T02:05:48.108Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[12]{created_at,author,text}: +comments[13]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -24,6 +24,7 @@ comments[12]{created_at,author,text}: "2026-11-05T01:19:54.725Z",codex-agent,"CI on aaffdb6 found one related integration failure: the publication-attestation scanner treated TypeScript template literals in the quoted workflow heredoc as unresolved shell execution. The run reported 1712 tests, 1711 passes and 1 failure, with all four source coverage percentages at 100; this is not a passing gate. Evidence: https://github.com/unbraind/pm-github/actions/runs/41943832975/job/126970983903 . Replaced only the generated launcher template literals with string concatenation. The attestation gate now passes, and the combined production-policy plus attestation suite passes 77 tests. No scanner policy, thresholds or merge decisions were changed. Full local npm tests continue using the public npm configuration and cache preference." "2026-11-05T01:26:13.405Z",codex-agent,"Corrected code is pushed at e12d12ec1f117ef95f53f92048bc23d6012997b0. CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41947808408 . Current CI: https://github.com/unbraind/pm-github/actions/runs/41947808424 . Verified the superseded run belonged to PR 221 and branch feat/reusable-dependabot-merge before cancelling its remaining jobs to free capacity for the corrected head; the original failure receipt is retained and no cancellation is counted as approval. A real raw-contents API fetch at e12d12e is byte-identical to scripts/dependabot-merge.ts (SHA-256 d9a8c70f2aeffe832f840a1402701b64d9731d7d49ea85892e90ca359015e799). Final 69-case provenance revert proof gives 13 failures and 56 passes; the pinned linked test runner passes all 69 afterward." "2026-11-05T01:43:50.312Z",codex-agent,"Review found and fixed a custom-token event loop in the caller: auto_merge_enabled events can belong to the automation credential owner, so subscribing to them would immediately revoke a valid bot-only request under the required actor guard. Removed that event from the own caller and README snippet; synchronize remains subscribed. Added a two-event regression that executes the production policy: token-generated arm events preserve the request, then a foreign synchronize revokes it. Reintroducing only the old event subscription in a disposable copy makes that one behavioral test fail; the unchanged package worktree passes all 70 policy/workflow tests. Combined policy and publication-attestation tests pass 78 cases. Test typecheck, lint and publication-attestation gate pass. No predicate, permissions, thresholds or branch configuration changed." + "2026-11-05T02:05:48.106Z",codex-agent,"Native Bun found two default-adapter fixture failures: Bun command lookup used its startup environment when child_process options omitted env. A unique local-command probe reproduced the difference without network access: implicit lookup failed under Bun while an explicit environment resolved the fixture on Node and Bun. The earlier dynamic-PATH default-gh probe escaped its intended stub and was rejected against the synthetic target; no arm or squash operation was requested. The adapter now snapshots the current environment explicitly, and default-adapter coverage runs in child processes with fixture PATH present at startup. Added a unique-command regression; reverting only the environment option makes that one Bun test fail without any real gh fallback. Fixed source passes 71 native Bun tests and 79 combined Node policy/attestation tests. Pinned pm linked test execution also passes. Test typecheck, lint and docstrings pass." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 85629685..bc91e724 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -17,3 +17,4 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T01:19:54.728Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-11-05T01:19:54.725Z","author":"codex-agent","text":"CI on aaffdb6 found one related integration failure: the publication-attestation scanner treated TypeScript template literals in the quoted workflow heredoc as unresolved shell execution. The run reported 1712 tests, 1711 passes and 1 failure, with all four source coverage percentages at 100; this is not a passing gate. Evidence: https://github.com/unbraind/pm-github/actions/runs/41943832975/job/126970983903 . Replaced only the generated launcher template literals with string concatenation. The attestation gate now passes, and the combined production-policy plus attestation suite passes 77 tests. No scanner policy, thresholds or merge decisions were changed. Full local npm tests continue using the public npm configuration and cache preference."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:19:54.728Z"}],"before_hash":"0d7d9baaca6bc1198f72529a63cfdbe72362020938e99f4e9d4894f3ee277204","after_hash":"e569f052d1419eb9499af7fd1f746827a5361bbda6cf45ef8f912fd168923b3b","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":"4d965fb4e88a6a07a9443baebcef9c1e598a3eea09954a4a842140c9a6286540"} {"hash_algorithm":"sha256","ts":"2026-11-05T01:26:13.406Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/10","value":{"created_at":"2026-11-05T01:26:13.405Z","author":"codex-agent","text":"Corrected code is pushed at e12d12ec1f117ef95f53f92048bc23d6012997b0. CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41947808408 . Current CI: https://github.com/unbraind/pm-github/actions/runs/41947808424 . Verified the superseded run belonged to PR 221 and branch feat/reusable-dependabot-merge before cancelling its remaining jobs to free capacity for the corrected head; the original failure receipt is retained and no cancellation is counted as approval. A real raw-contents API fetch at e12d12e is byte-identical to scripts/dependabot-merge.ts (SHA-256 d9a8c70f2aeffe832f840a1402701b64d9731d7d49ea85892e90ca359015e799). Final 69-case provenance revert proof gives 13 failures and 56 passes; the pinned linked test runner passes all 69 afterward."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:26:13.406Z"}],"before_hash":"e569f052d1419eb9499af7fd1f746827a5361bbda6cf45ef8f912fd168923b3b","after_hash":"e02f4ed527c108f9894604643ae7752b31161772fa4128c11e085d134f31c684","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":"fb4589d4c8201f77a7a4a6be8df00aba6738d494874ee93427ea387333b5cbc3"} {"hash_algorithm":"sha256","ts":"2026-11-05T01:43:50.321Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/11","value":{"created_at":"2026-11-05T01:43:50.312Z","author":"codex-agent","text":"Review found and fixed a custom-token event loop in the caller: auto_merge_enabled events can belong to the automation credential owner, so subscribing to them would immediately revoke a valid bot-only request under the required actor guard. Removed that event from the own caller and README snippet; synchronize remains subscribed. Added a two-event regression that executes the production policy: token-generated arm events preserve the request, then a foreign synchronize revokes it. Reintroducing only the old event subscription in a disposable copy makes that one behavioral test fail; the unchanged package worktree passes all 70 policy/workflow tests. Combined policy and publication-attestation tests pass 78 cases. Test typecheck, lint and publication-attestation gate pass. No predicate, permissions, thresholds or branch configuration changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:43:50.321Z"}],"before_hash":"e02f4ed527c108f9894604643ae7752b31161772fa4128c11e085d134f31c684","after_hash":"c71c69a4bfd0871b273ae5c8a322132023007f1861606a61ca0add426aa4998c","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":"1259521b6c66803a0cd430c3a5e13eef57a6576f390e1ea51f1058250c368963"} +{"hash_algorithm":"sha256","ts":"2026-11-05T02:05:48.108Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/12","value":{"created_at":"2026-11-05T02:05:48.106Z","author":"codex-agent","text":"Native Bun found two default-adapter fixture failures: Bun command lookup used its startup environment when child_process options omitted env. A unique local-command probe reproduced the difference without network access: implicit lookup failed under Bun while an explicit environment resolved the fixture on Node and Bun. The earlier dynamic-PATH default-gh probe escaped its intended stub and was rejected against the synthetic target; no arm or squash operation was requested. The adapter now snapshots the current environment explicitly, and default-adapter coverage runs in child processes with fixture PATH present at startup. Added a unique-command regression; reverting only the environment option makes that one Bun test fail without any real gh fallback. Fixed source passes 71 native Bun tests and 79 combined Node policy/attestation tests. Pinned pm linked test execution also passes. Test typecheck, lint and docstrings pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:05:48.108Z"}],"before_hash":"c71c69a4bfd0871b273ae5c8a322132023007f1861606a61ca0add426aa4998c","after_hash":"e75e523a2e4499b20278e660660726ba15a6746132b68224c4a695d2f65d69ef","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":"9b25bf35b883228285a56460565864239bd193d45607452f024e308b562ba991"} diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts index 40e2a54a..a5dfd809 100644 --- a/scripts/dependabot-merge.ts +++ b/scripts/dependabot-merge.ts @@ -39,9 +39,9 @@ export interface MergeResult { message: string; } -/** Execute gh without a shell; failed API reads cannot become empty success. */ +/** Use the current environment without a shell; failed reads cannot become empty success. */ export function githubCli(args: readonly string[], executable = "gh"): string { - return execFileSync(executable, args, { encoding: "utf8", maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }); + return execFileSync(executable, args, { encoding: "utf8", env: { ...process.env }, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }); } const bot = "dependabot[bot]"; diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 0127f394..05ef44e9 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -266,11 +266,13 @@ esac assert.equal(run.status, 0, run.stderr); assert.match(run.stdout, /::notice::Foreign or unverified commits: aaaaaaaaaaaa outside/); assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /aaaaaaaaaaaa outside/); - const old = process.env.PATH; - try { - process.env.PATH = env.PATH; - assert.equal(runDependabotMerge({ ...fixture().input, phase: "revoke" }).armed, false); - } finally { process.env.PATH = old; } + // Load the original module for coverage, with the fake gh available at + // child startup even if a runtime caches its initial command lookup. + const probe = join(dir, "adapter-probe.mts"); + writeFileSync(probe, readFileSync(join(dir, "run-dependabot-merge.mts"), "utf8") + .replace("from './dependabot-merge.ts'", "from " + JSON.stringify(new URL("../scripts/dependabot-merge.ts", import.meta.url).href))); + const defaultRun = spawnSync(process.execPath, [probe], { env: { ...env, PHASE: "revoke" }, encoding: "utf8" }); + assert.equal(defaultRun.status, 0, defaultRun.stderr); } } finally { rmSync(dir, { recursive: true, force: true }); } }); @@ -281,6 +283,16 @@ test("real CLI adapter passes argv without shell expansion and propagates failur assert.throws(() => githubCli(["-e", "process.exit(3)"], process.execPath)); }); +test("CLI adapter resolves a unique fixture command against the current PATH", () => { + const dir = mkdtempSync(join(tmpdir(), "dependabot-command-")); + const old = process.env.PATH; + try { + writeFileSync(join(dir, "policy-command-probe"), "#!/bin/bash\nprintf fixture-resolved\n", { mode: 0o755 }); + process.env.PATH = dir + ":" + old; + assert.equal(githubCli([], "policy-command-probe"), "fixture-resolved"); + } finally { process.env.PATH = old; rmSync(dir, { recursive: true, force: true }); } +}); + for (const mode of ["armed", "clear", "api-error", "denied"]) { test(`pre-tooling revocation shell: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-revoke-")); From 773283e4eae83e1b7d130afc92997e1007cdd628 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 03:37:36 +0100 Subject: [PATCH 07/19] Scope the thin caller to Dependabot-authored pull requests --- .agents/pm/features/pm-github-dependabot-merge.toon | 6 ++++-- .agents/pm/history/pm-github-dependabot-merge.jsonl | 2 ++ .github/workflows/dependabot-auto-merge.yml | 1 + README.md | 7 +++++-- test/dependabot-merge.test.ts | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 97b3ac02..840300bb 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,12 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T02:05:48.108Z" +updated_at: "2026-11-05T02:36:47.391Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[13]{created_at,author,text}: +comments[15]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -25,6 +25,8 @@ comments[13]{created_at,author,text}: "2026-11-05T01:26:13.405Z",codex-agent,"Corrected code is pushed at e12d12ec1f117ef95f53f92048bc23d6012997b0. CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41947808408 . Current CI: https://github.com/unbraind/pm-github/actions/runs/41947808424 . Verified the superseded run belonged to PR 221 and branch feat/reusable-dependabot-merge before cancelling its remaining jobs to free capacity for the corrected head; the original failure receipt is retained and no cancellation is counted as approval. A real raw-contents API fetch at e12d12e is byte-identical to scripts/dependabot-merge.ts (SHA-256 d9a8c70f2aeffe832f840a1402701b64d9731d7d49ea85892e90ca359015e799). Final 69-case provenance revert proof gives 13 failures and 56 passes; the pinned linked test runner passes all 69 afterward." "2026-11-05T01:43:50.312Z",codex-agent,"Review found and fixed a custom-token event loop in the caller: auto_merge_enabled events can belong to the automation credential owner, so subscribing to them would immediately revoke a valid bot-only request under the required actor guard. Removed that event from the own caller and README snippet; synchronize remains subscribed. Added a two-event regression that executes the production policy: token-generated arm events preserve the request, then a foreign synchronize revokes it. Reintroducing only the old event subscription in a disposable copy makes that one behavioral test fail; the unchanged package worktree passes all 70 policy/workflow tests. Combined policy and publication-attestation tests pass 78 cases. Test typecheck, lint and publication-attestation gate pass. No predicate, permissions, thresholds or branch configuration changed." "2026-11-05T02:05:48.106Z",codex-agent,"Native Bun found two default-adapter fixture failures: Bun command lookup used its startup environment when child_process options omitted env. A unique local-command probe reproduced the difference without network access: implicit lookup failed under Bun while an explicit environment resolved the fixture on Node and Bun. The earlier dynamic-PATH default-gh probe escaped its intended stub and was rejected against the synthetic target; no arm or squash operation was requested. The adapter now snapshots the current environment explicitly, and default-adapter coverage runs in child processes with fixture PATH present at startup. Added a unique-command regression; reverting only the environment option makes that one Bun test fail without any real gh fallback. Fixed source passes 71 native Bun tests and 79 combined Node policy/attestation tests. Pinned pm linked test execution also passes. Test typecheck, lint and docstrings pass." + "2026-11-05T02:16:17.850Z",codex-agent,"Current source head b1e8285b1ff8896d9c81de3ca92a9b93ed2e6eec has passing CodeQL: https://github.com/unbraind/pm-github/actions/runs/41952923845 . CI https://github.com/unbraind/pm-github/actions/runs/41952923878 was manually cancelled under the repository owner account before any job steps started; check-run annotations confirm the cancellation. This is missing validation evidence, not approval. No rerun was requested for that manually cancelled run. Local full npm tests continue through the existing end-to-end closeout decision matrix; the complete npm and Bun release gates will run serially afterward. Native local runtime receipts are Node 24.19.0 and Bun 1.3.5." + "2026-11-05T02:36:47.382Z",codex-agent,"Caller ownership review retained the original PR-author-only filter in the thin caller and documented it in the migration snippet. This preserves other PR automation while still invoking the reusable workflow when a foreign actor pushes to a Dependabot-authored PR. The reusable policy itself retains all author/actor/provenance checks and revocation behavior whenever invoked. Workflow policy tests require the caller author guard and forbid caller actor guards; the two-event foreign synchronize regression still passes. Current receipts: 71 native Bun passes and 79 combined Node policy/attestation passes. Final-policy real API refresh at b1e8285 is byte-identical to the repository script (SHA-256 8063b11c33820d662187bcd5a6741ede744a447be16484d2dc97be4630023356), refuses the real pm-context#211 foreign addition and performs zero mutation calls. Remaining release gates will use the already installed Node 26.13.0 through nvm, matching the CI runtime line; the active npm test run remains Node 24.19.0." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index bc91e724..1db4a93b 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -18,3 +18,5 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T01:26:13.406Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/10","value":{"created_at":"2026-11-05T01:26:13.405Z","author":"codex-agent","text":"Corrected code is pushed at e12d12ec1f117ef95f53f92048bc23d6012997b0. CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41947808408 . Current CI: https://github.com/unbraind/pm-github/actions/runs/41947808424 . Verified the superseded run belonged to PR 221 and branch feat/reusable-dependabot-merge before cancelling its remaining jobs to free capacity for the corrected head; the original failure receipt is retained and no cancellation is counted as approval. A real raw-contents API fetch at e12d12e is byte-identical to scripts/dependabot-merge.ts (SHA-256 d9a8c70f2aeffe832f840a1402701b64d9731d7d49ea85892e90ca359015e799). Final 69-case provenance revert proof gives 13 failures and 56 passes; the pinned linked test runner passes all 69 afterward."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:26:13.406Z"}],"before_hash":"e569f052d1419eb9499af7fd1f746827a5361bbda6cf45ef8f912fd168923b3b","after_hash":"e02f4ed527c108f9894604643ae7752b31161772fa4128c11e085d134f31c684","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":"fb4589d4c8201f77a7a4a6be8df00aba6738d494874ee93427ea387333b5cbc3"} {"hash_algorithm":"sha256","ts":"2026-11-05T01:43:50.321Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/11","value":{"created_at":"2026-11-05T01:43:50.312Z","author":"codex-agent","text":"Review found and fixed a custom-token event loop in the caller: auto_merge_enabled events can belong to the automation credential owner, so subscribing to them would immediately revoke a valid bot-only request under the required actor guard. Removed that event from the own caller and README snippet; synchronize remains subscribed. Added a two-event regression that executes the production policy: token-generated arm events preserve the request, then a foreign synchronize revokes it. Reintroducing only the old event subscription in a disposable copy makes that one behavioral test fail; the unchanged package worktree passes all 70 policy/workflow tests. Combined policy and publication-attestation tests pass 78 cases. Test typecheck, lint and publication-attestation gate pass. No predicate, permissions, thresholds or branch configuration changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T01:43:50.321Z"}],"before_hash":"e02f4ed527c108f9894604643ae7752b31161772fa4128c11e085d134f31c684","after_hash":"c71c69a4bfd0871b273ae5c8a322132023007f1861606a61ca0add426aa4998c","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":"1259521b6c66803a0cd430c3a5e13eef57a6576f390e1ea51f1058250c368963"} {"hash_algorithm":"sha256","ts":"2026-11-05T02:05:48.108Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/12","value":{"created_at":"2026-11-05T02:05:48.106Z","author":"codex-agent","text":"Native Bun found two default-adapter fixture failures: Bun command lookup used its startup environment when child_process options omitted env. A unique local-command probe reproduced the difference without network access: implicit lookup failed under Bun while an explicit environment resolved the fixture on Node and Bun. The earlier dynamic-PATH default-gh probe escaped its intended stub and was rejected against the synthetic target; no arm or squash operation was requested. The adapter now snapshots the current environment explicitly, and default-adapter coverage runs in child processes with fixture PATH present at startup. Added a unique-command regression; reverting only the environment option makes that one Bun test fail without any real gh fallback. Fixed source passes 71 native Bun tests and 79 combined Node policy/attestation tests. Pinned pm linked test execution also passes. Test typecheck, lint and docstrings pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:05:48.108Z"}],"before_hash":"c71c69a4bfd0871b273ae5c8a322132023007f1861606a61ca0add426aa4998c","after_hash":"e75e523a2e4499b20278e660660726ba15a6746132b68224c4a695d2f65d69ef","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":"9b25bf35b883228285a56460565864239bd193d45607452f024e308b562ba991"} +{"hash_algorithm":"sha256","ts":"2026-11-05T02:16:17.851Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/13","value":{"created_at":"2026-11-05T02:16:17.850Z","author":"codex-agent","text":"Current source head b1e8285b1ff8896d9c81de3ca92a9b93ed2e6eec has passing CodeQL: https://github.com/unbraind/pm-github/actions/runs/41952923845 . CI https://github.com/unbraind/pm-github/actions/runs/41952923878 was manually cancelled under the repository owner account before any job steps started; check-run annotations confirm the cancellation. This is missing validation evidence, not approval. No rerun was requested for that manually cancelled run. Local full npm tests continue through the existing end-to-end closeout decision matrix; the complete npm and Bun release gates will run serially afterward. Native local runtime receipts are Node 24.19.0 and Bun 1.3.5."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:16:17.851Z"}],"before_hash":"e75e523a2e4499b20278e660660726ba15a6746132b68224c4a695d2f65d69ef","after_hash":"7fcbd4bebfd6f87966beae09fbf1ea2b42058254e2a847d92893574fb06d85b3","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":"f3c2c7ba0ed1661ae9e0f6ec6d7a483c257a31d415e1f26464bfcddfaf309abe"} +{"hash_algorithm":"sha256","ts":"2026-11-05T02:36:47.391Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/14","value":{"created_at":"2026-11-05T02:36:47.382Z","author":"codex-agent","text":"Caller ownership review retained the original PR-author-only filter in the thin caller and documented it in the migration snippet. This preserves other PR automation while still invoking the reusable workflow when a foreign actor pushes to a Dependabot-authored PR. The reusable policy itself retains all author/actor/provenance checks and revocation behavior whenever invoked. Workflow policy tests require the caller author guard and forbid caller actor guards; the two-event foreign synchronize regression still passes. Current receipts: 71 native Bun passes and 79 combined Node policy/attestation passes. Final-policy real API refresh at b1e8285 is byte-identical to the repository script (SHA-256 8063b11c33820d662187bcd5a6741ede744a447be16484d2dc97be4630023356), refuses the real pm-context#211 foreign addition and performs zero mutation calls. Remaining release gates will use the already installed Node 26.13.0 through nvm, matching the CI runtime line; the active npm test run remains Node 24.19.0."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:36:47.391Z"}],"before_hash":"7fcbd4bebfd6f87966beae09fbf1ea2b42058254e2a847d92893574fb06d85b3","after_hash":"8e7e6baa29c44fc18b3882af7380cf5ac76033f55ee3ebae76e05140972ba8f5","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":"58845d489b56edb51f46a7f2b9824b2f77cdb4b14b61c5fd58904328d307efce"} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 33f7792f..7f8c686d 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -8,6 +8,7 @@ permissions: {} jobs: verified-auto-merge: + if: github.event.pull_request.user.login == 'dependabot[bot]' permissions: contents: write pull-requests: write diff --git a/README.md b/README.md index a4fbdf9c..4ac43b70 100644 --- a/README.md +++ b/README.md @@ -861,6 +861,7 @@ on: permissions: {} jobs: verified-auto-merge: + if: github.event.pull_request.user.login == 'dependabot[bot]' permissions: contents: write pull-requests: write @@ -899,8 +900,10 @@ a PR cannot supply the executable script. Before this change reaches `main`, that self-call fails closed because the trusted policy file is absent. Migration: replace the **entire** copied `dependabot-auto-merge.yml` with the -caller above. Retain `synchronize` and omit author/actor job guards: the job -must run when a foreign actor pushes so it can revoke an earlier request. +caller above. Retain `synchronize` and the PR-author-only caller guard. Other +PR automation keeps its own merge requests; a foreign push to a Dependabot PR +still runs because the PR author remains Dependabot. An actor guard on the +calling job would skip that foreign push and prevent revocation. Keep `auto_merge_enabled` out of the caller's event types: a custom token's arm event belongs to its automation account and would immediately revoke its own request under the strict Dependabot actor policy. diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 05ef44e9..842366cd 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -212,7 +212,9 @@ test("workflow policy: pinned actions, minimal grants, no PR checkout or interpo assert.match(source, /continue-on-error: true/); assert.match(source, /job\.workflow_sha/); const caller = readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8"); assert.match(caller, /uses: \.\/\.github\/workflows\/pm-github-dependabot-merge.yml/); - assert.match(caller, /synchronize/); assert.doesNotMatch(caller, /if:|pull_request_target/); + assert.match(caller, /synchronize/); + assert.match(caller, /if: github\.event\.pull_request\.user\.login == 'dependabot\[bot\]'/); + assert.doesNotMatch(caller, /github\.(?:actor|triggering_actor)|pull_request_target/); assert.doesNotMatch(caller, /auto_merge_enabled/, "custom-token arm events must not revoke their own request"); }); From 850e96434cd79e766bd60d09fd5f48a49e20ec65 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 04:31:24 +0100 Subject: [PATCH 08/19] Fail closed on incomplete early PR state receipts --- .../features/pm-github-dependabot-merge.toon | 6 ++++-- .../history/pm-github-dependabot-merge.jsonl | 2 ++ .../workflows/pm-github-dependabot-merge.yml | 4 ++-- test/dependabot-merge.test.ts | 20 ++++++++++++------- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 840300bb..67fa59e9 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,12 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T02:36:47.391Z" +updated_at: "2026-11-05T03:31:24.214Z" assignee: codex-agent claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[15]{created_at,author,text}: +comments[17]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -27,6 +27,8 @@ comments[15]{created_at,author,text}: "2026-11-05T02:05:48.106Z",codex-agent,"Native Bun found two default-adapter fixture failures: Bun command lookup used its startup environment when child_process options omitted env. A unique local-command probe reproduced the difference without network access: implicit lookup failed under Bun while an explicit environment resolved the fixture on Node and Bun. The earlier dynamic-PATH default-gh probe escaped its intended stub and was rejected against the synthetic target; no arm or squash operation was requested. The adapter now snapshots the current environment explicitly, and default-adapter coverage runs in child processes with fixture PATH present at startup. Added a unique-command regression; reverting only the environment option makes that one Bun test fail without any real gh fallback. Fixed source passes 71 native Bun tests and 79 combined Node policy/attestation tests. Pinned pm linked test execution also passes. Test typecheck, lint and docstrings pass." "2026-11-05T02:16:17.850Z",codex-agent,"Current source head b1e8285b1ff8896d9c81de3ca92a9b93ed2e6eec has passing CodeQL: https://github.com/unbraind/pm-github/actions/runs/41952923845 . CI https://github.com/unbraind/pm-github/actions/runs/41952923878 was manually cancelled under the repository owner account before any job steps started; check-run annotations confirm the cancellation. This is missing validation evidence, not approval. No rerun was requested for that manually cancelled run. Local full npm tests continue through the existing end-to-end closeout decision matrix; the complete npm and Bun release gates will run serially afterward. Native local runtime receipts are Node 24.19.0 and Bun 1.3.5." "2026-11-05T02:36:47.382Z",codex-agent,"Caller ownership review retained the original PR-author-only filter in the thin caller and documented it in the migration snippet. This preserves other PR automation while still invoking the reusable workflow when a foreign actor pushes to a Dependabot-authored PR. The reusable policy itself retains all author/actor/provenance checks and revocation behavior whenever invoked. Workflow policy tests require the caller author guard and forbid caller actor guards; the two-event foreign synchronize regression still passes. Current receipts: 71 native Bun passes and 79 combined Node policy/attestation passes. Final-policy real API refresh at b1e8285 is byte-identical to the repository script (SHA-256 8063b11c33820d662187bcd5a6741ede744a447be16484d2dc97be4630023356), refuses the real pm-context#211 foreign addition and performs zero mutation calls. Remaining release gates will use the already installed Node 26.13.0 through nvm, matching the CI runtime line; the active npm test run remains Node 24.19.0." + "2026-11-05T03:08:03.967Z",codex-agent,"At the 30-minute CI cutoff for source head 773283e4eae83e1b7d130afc92997e1007cdd628, CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41956452617 . CI https://github.com/unbraind/pm-github/actions/runs/41956452612 still has test (22), test (26), bun-release (22) and bun-release (26) in progress. These are pending, not approval; CI polling for this head stops here. Independent local required gates continue. The low-priority full npm suite has passed the closeout matrix, sync publication boundaries and the 500-sequence SDK state-model regression, with no failures reported so far." + "2026-11-05T03:31:24.213Z",codex-agent,"Completed the low-priority Node 24.19.0 npm test run: 1714 passed, zero failed, cancelled or skipped; elapsed 8140.26 seconds. It began before the final three new regression cases, so the fresh final-source release gates will execute the updated corpus. Early shell state projections now reject incomplete open-PR data instead of treating a missing auto_merge field as unarmed; malformed input forces a revocation attempt. Real jq tests cover malformed data and closed historical metadata. Reverting only the projection makes the malformed-state behavioral test fail. Launcher and bootstrap processes now explicitly use non-git temporary working directories, rather than merely storing their files there. Current final focused receipts: 73 native Bun passes, 81 combined Node policy/attestation passes, and successful pinned pm test execution. Test typecheck, lint and publication-attestation checks pass." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 1db4a93b..7d8799bb 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -20,3 +20,5 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T02:05:48.108Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/12","value":{"created_at":"2026-11-05T02:05:48.106Z","author":"codex-agent","text":"Native Bun found two default-adapter fixture failures: Bun command lookup used its startup environment when child_process options omitted env. A unique local-command probe reproduced the difference without network access: implicit lookup failed under Bun while an explicit environment resolved the fixture on Node and Bun. The earlier dynamic-PATH default-gh probe escaped its intended stub and was rejected against the synthetic target; no arm or squash operation was requested. The adapter now snapshots the current environment explicitly, and default-adapter coverage runs in child processes with fixture PATH present at startup. Added a unique-command regression; reverting only the environment option makes that one Bun test fail without any real gh fallback. Fixed source passes 71 native Bun tests and 79 combined Node policy/attestation tests. Pinned pm linked test execution also passes. Test typecheck, lint and docstrings pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:05:48.108Z"}],"before_hash":"c71c69a4bfd0871b273ae5c8a322132023007f1861606a61ca0add426aa4998c","after_hash":"e75e523a2e4499b20278e660660726ba15a6746132b68224c4a695d2f65d69ef","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":"9b25bf35b883228285a56460565864239bd193d45607452f024e308b562ba991"} {"hash_algorithm":"sha256","ts":"2026-11-05T02:16:17.851Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/13","value":{"created_at":"2026-11-05T02:16:17.850Z","author":"codex-agent","text":"Current source head b1e8285b1ff8896d9c81de3ca92a9b93ed2e6eec has passing CodeQL: https://github.com/unbraind/pm-github/actions/runs/41952923845 . CI https://github.com/unbraind/pm-github/actions/runs/41952923878 was manually cancelled under the repository owner account before any job steps started; check-run annotations confirm the cancellation. This is missing validation evidence, not approval. No rerun was requested for that manually cancelled run. Local full npm tests continue through the existing end-to-end closeout decision matrix; the complete npm and Bun release gates will run serially afterward. Native local runtime receipts are Node 24.19.0 and Bun 1.3.5."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:16:17.851Z"}],"before_hash":"e75e523a2e4499b20278e660660726ba15a6746132b68224c4a695d2f65d69ef","after_hash":"7fcbd4bebfd6f87966beae09fbf1ea2b42058254e2a847d92893574fb06d85b3","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":"f3c2c7ba0ed1661ae9e0f6ec6d7a483c257a31d415e1f26464bfcddfaf309abe"} {"hash_algorithm":"sha256","ts":"2026-11-05T02:36:47.391Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/14","value":{"created_at":"2026-11-05T02:36:47.382Z","author":"codex-agent","text":"Caller ownership review retained the original PR-author-only filter in the thin caller and documented it in the migration snippet. This preserves other PR automation while still invoking the reusable workflow when a foreign actor pushes to a Dependabot-authored PR. The reusable policy itself retains all author/actor/provenance checks and revocation behavior whenever invoked. Workflow policy tests require the caller author guard and forbid caller actor guards; the two-event foreign synchronize regression still passes. Current receipts: 71 native Bun passes and 79 combined Node policy/attestation passes. Final-policy real API refresh at b1e8285 is byte-identical to the repository script (SHA-256 8063b11c33820d662187bcd5a6741ede744a447be16484d2dc97be4630023356), refuses the real pm-context#211 foreign addition and performs zero mutation calls. Remaining release gates will use the already installed Node 26.13.0 through nvm, matching the CI runtime line; the active npm test run remains Node 24.19.0."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:36:47.391Z"}],"before_hash":"7fcbd4bebfd6f87966beae09fbf1ea2b42058254e2a847d92893574fb06d85b3","after_hash":"8e7e6baa29c44fc18b3882af7380cf5ac76033f55ee3ebae76e05140972ba8f5","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":"58845d489b56edb51f46a7f2b9824b2f77cdb4b14b61c5fd58904328d307efce"} +{"hash_algorithm":"sha256","ts":"2026-11-05T03:08:03.976Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/15","value":{"created_at":"2026-11-05T03:08:03.967Z","author":"codex-agent","text":"At the 30-minute CI cutoff for source head 773283e4eae83e1b7d130afc92997e1007cdd628, CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41956452617 . CI https://github.com/unbraind/pm-github/actions/runs/41956452612 still has test (22), test (26), bun-release (22) and bun-release (26) in progress. These are pending, not approval; CI polling for this head stops here. Independent local required gates continue. The low-priority full npm suite has passed the closeout matrix, sync publication boundaries and the 500-sequence SDK state-model regression, with no failures reported so far."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:08:03.976Z"}],"before_hash":"8e7e6baa29c44fc18b3882af7380cf5ac76033f55ee3ebae76e05140972ba8f5","after_hash":"0fd69bed81eea80656471c0533c5dd8d1d6ba4777bfc7adb93e612096ecd153b","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":"43d8f0ef033f048a9ea6608ff3ed7df1d1841769bd146b657c5c12c9f22d41f0"} +{"hash_algorithm":"sha256","ts":"2026-11-05T03:31:24.214Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/16","value":{"created_at":"2026-11-05T03:31:24.213Z","author":"codex-agent","text":"Completed the low-priority Node 24.19.0 npm test run: 1714 passed, zero failed, cancelled or skipped; elapsed 8140.26 seconds. It began before the final three new regression cases, so the fresh final-source release gates will execute the updated corpus. Early shell state projections now reject incomplete open-PR data instead of treating a missing auto_merge field as unarmed; malformed input forces a revocation attempt. Real jq tests cover malformed data and closed historical metadata. Reverting only the projection makes the malformed-state behavioral test fail. Launcher and bootstrap processes now explicitly use non-git temporary working directories, rather than merely storing their files there. Current final focused receipts: 73 native Bun passes, 81 combined Node policy/attestation passes, and successful pinned pm test execution. Test typecheck, lint and publication-attestation checks pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:31:24.214Z"}],"before_hash":"0fd69bed81eea80656471c0533c5dd8d1d6ba4777bfc7adb93e612096ecd153b","after_hash":"f156572150329184a2c62c96a18fe6af358925e252f8cb3c9315ad7b82a55765","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":"9483edb5d97bed85572fb45a1d3117139076453337f6b5432c10f751fbffe41f"} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml index 590fefa5..dbab960b 100644 --- a/.github/workflows/pm-github-dependabot-merge.yml +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -43,7 +43,7 @@ jobs: run: | set -euo pipefail [[ "${REPOSITORY}" =~ ^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$ && "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]] - armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.state != "closed" and .auto_merge != null' 2>/dev/null)" || armed=true + armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq 'if .state == "closed" then false elif .state == "open" and has("auto_merge") then .auto_merge != null else error("Unverifiable PR state") end' 2>/dev/null)" || armed=true if [[ "${armed}" != false ]]; then if ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then echo '::error::Cannot revoke auto-merge; check token write permissions immediately.' @@ -71,7 +71,7 @@ jobs: fi if [[ ! "${policy_sha}" =~ ^[a-f0-9]{40}$ ]] || ! gh api "repos/unbraind/pm-github/contents/scripts/dependabot-merge.ts?ref=${policy_sha}" \ -H 'Accept: application/vnd.github.raw+json' > "${RUNNER_TEMP}/dependabot-merge.ts"; then - armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq '.state != "closed" and .auto_merge != null' 2>/dev/null)" || armed=true + armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq 'if .state == "closed" then false elif .state == "open" and has("auto_merge") then .auto_merge != null else error("Unverifiable PR state") end' 2>/dev/null)" || armed=true if [[ "${armed}" != false ]] && ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then echo '::error::Cannot revoke auto-merge after policy load failure; check token write permissions immediately.' exit 1 diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 842366cd..384166a7 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -253,7 +253,7 @@ case "$*" in *) exit 2 ;; esac `, { mode: 0o755 }); - const loaded = spawnSync("bash", ["-c", loader()], { env, encoding: "utf8" }); + const loaded = spawnSync("bash", ["-c", loader()], { cwd: dir, env, encoding: "utf8" }); const failed = ["load-failed", "invalid-sha", "local-ref-failed"].includes(mode); assert.equal(loaded.status, 0, loaded.stderr); const receipts = readFileSync(env.RECEIPTS, "utf8"); @@ -264,7 +264,7 @@ esac } else { assert.match(receipts, new RegExp(`ref=${mode === "local" ? trusted : head}`)); - const run = spawnSync(process.execPath, [join(dir, "run-dependabot-merge.mts")], { env, encoding: "utf8" }); + const run = spawnSync(process.execPath, [join(dir, "run-dependabot-merge.mts")], { cwd: dir, env, encoding: "utf8" }); assert.equal(run.status, 0, run.stderr); assert.match(run.stdout, /::notice::Foreign or unverified commits: aaaaaaaaaaaa outside/); assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /aaaaaaaaaaaa outside/); @@ -273,7 +273,7 @@ esac const probe = join(dir, "adapter-probe.mts"); writeFileSync(probe, readFileSync(join(dir, "run-dependabot-merge.mts"), "utf8") .replace("from './dependabot-merge.ts'", "from " + JSON.stringify(new URL("../scripts/dependabot-merge.ts", import.meta.url).href))); - const defaultRun = spawnSync(process.execPath, [probe], { env: { ...env, PHASE: "revoke" }, encoding: "utf8" }); + const defaultRun = spawnSync(process.execPath, [probe], { cwd: dir, env: { ...env, PHASE: "revoke" }, encoding: "utf8" }); assert.equal(defaultRun.status, 0, defaultRun.stderr); } } finally { rmSync(dir, { recursive: true, force: true }); } @@ -295,7 +295,7 @@ test("CLI adapter resolves a unique fixture command against the current PATH", ( } finally { process.env.PATH = old; rmSync(dir, { recursive: true, force: true }); } }); -for (const mode of ["armed", "clear", "api-error", "denied"]) { +for (const mode of ["armed", "clear", "closed", "malformed-api", "api-error", "denied"]) { test(`pre-tooling revocation shell: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-revoke-")); try { @@ -306,17 +306,23 @@ for (const mode of ["armed", "clear", "api-error", "denied"]) { printf '%s\\n' "$*" >> "$RECEIPTS" if [[ "$1" == api ]]; then [[ "$MODE" != api-error ]] || exit 1 - if [[ "$MODE" == clear ]]; then echo false; else echo true; fi + case "$MODE" in + clear) payload='{"state":"open","auto_merge":null}' ;; + closed) payload='{"state":"closed","auto_merge":{}}' ;; + malformed-api) payload='{}' ;; + *) payload='{"state":"open","auto_merge":{}}' ;; + esac + printf '%s' "$payload" | jq -r "$4" else [[ "$MODE" != denied ]]; fi `, { mode: 0o755 }); const env = { ...process.env, PATH: dir + ":" + process.env.PATH, REPOSITORY: "acme/widgets", PR_NUMBER: "7", MODE: mode, RECEIPTS: join(dir, "receipts"), GITHUB_STEP_SUMMARY: join(dir, "summary") }; - const result = spawnSync("bash", ["-c", shell], { env, encoding: "utf8" }); + const result = spawnSync("bash", ["-c", shell], { cwd: dir, env, encoding: "utf8" }); assert.equal(result.status, mode === "denied" ? 1 : 0, result.stderr); if (mode === "denied") assert.match(result.stdout, /Cannot revoke auto-merge/); else assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /cleared before tooling/); const receipts = readFileSync(env.RECEIPTS, "utf8"); - if (mode === "clear") assert.doesNotMatch(receipts, /--disable-auto/); + if (mode === "clear" || mode === "closed") assert.doesNotMatch(receipts, /--disable-auto/); else assert.match(receipts, /--disable-auto/); } finally { rmSync(dir, { recursive: true, force: true }); } }); From 2652cc9e6f86ef086b6c755b468a17838b7506c4 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 05:21:39 +0100 Subject: [PATCH 09/19] Record Dependabot workflow validation and review handoff --- .../pm/features/pm-github-dependabot-merge.toon | 14 ++++++++++---- .../pm/history/pm-github-dependabot-merge.jsonl | 9 +++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 67fa59e9..9a1c6e1d 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,10 @@ status: in_progress priority: 2 tags[5]: changelog-unreleased,ci,dependabot,security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T03:31:24.214Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-11-05T04:20:55.050Z" author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[17]{created_at,author,text}: +comments[25]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -29,6 +27,14 @@ comments[17]{created_at,author,text}: "2026-11-05T02:36:47.382Z",codex-agent,"Caller ownership review retained the original PR-author-only filter in the thin caller and documented it in the migration snippet. This preserves other PR automation while still invoking the reusable workflow when a foreign actor pushes to a Dependabot-authored PR. The reusable policy itself retains all author/actor/provenance checks and revocation behavior whenever invoked. Workflow policy tests require the caller author guard and forbid caller actor guards; the two-event foreign synchronize regression still passes. Current receipts: 71 native Bun passes and 79 combined Node policy/attestation passes. Final-policy real API refresh at b1e8285 is byte-identical to the repository script (SHA-256 8063b11c33820d662187bcd5a6741ede744a447be16484d2dc97be4630023356), refuses the real pm-context#211 foreign addition and performs zero mutation calls. Remaining release gates will use the already installed Node 26.13.0 through nvm, matching the CI runtime line; the active npm test run remains Node 24.19.0." "2026-11-05T03:08:03.967Z",codex-agent,"At the 30-minute CI cutoff for source head 773283e4eae83e1b7d130afc92997e1007cdd628, CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41956452617 . CI https://github.com/unbraind/pm-github/actions/runs/41956452612 still has test (22), test (26), bun-release (22) and bun-release (26) in progress. These are pending, not approval; CI polling for this head stops here. Independent local required gates continue. The low-priority full npm suite has passed the closeout matrix, sync publication boundaries and the 500-sequence SDK state-model regression, with no failures reported so far." "2026-11-05T03:31:24.213Z",codex-agent,"Completed the low-priority Node 24.19.0 npm test run: 1714 passed, zero failed, cancelled or skipped; elapsed 8140.26 seconds. It began before the final three new regression cases, so the fresh final-source release gates will execute the updated corpus. Early shell state projections now reject incomplete open-PR data instead of treating a missing auto_merge field as unarmed; malformed input forces a revocation attempt. Real jq tests cover malformed data and closed historical metadata. Reverting only the projection makes the malformed-state behavioral test fail. Launcher and bootstrap processes now explicitly use non-git temporary working directories, rather than merely storing their files there. Current final focused receipts: 73 native Bun passes, 81 combined Node policy/attestation passes, and successful pinned pm test execution. Test typecheck, lint and publication-attestation checks pass." + "2026-11-05T03:51:57.488Z",codex-agent,"The first Node 26 release-gate attempt passed prechecks and entered coverage, then was interrupted with exit 130 to isolate inherited agent-session and credential context from synthetic fixtures. It is not counted as passing. Installed SDK source confirms automatic Codex session-provenance capture; CI has no such inherited session context. Restarted the unchanged complete release command in a CI-like child environment with agent/provider credentials and context excluded, public npm configuration, cache preference, empty GitHub credential configuration and isolated Git config. Parent agent configuration and project data remain untouched. All tests, source inventory, thresholds and the two-worker bound are unchanged; no other local suite runs alongside coverage." + "2026-11-05T03:56:25.085Z",codex-agent,"Read-only follow-up confirmed the previous candidate CI run completed successfully: Node 22, Node 26, Bun release on Node 22 and Bun release on Node 26 all passed. This receipt applies to commit 773283e only; the latest stricter malformed-state receipt checks are being verified separately. CodeQL and Semgrep passed for 850e964; its four CI jobs remain in progress. Local final release coverage is running alone after all prechecks passed." + "2026-11-05T03:56:44.272Z",codex-agent,"Strict tracker health reports settings, storage, locks, integrity and append-only history all healthy, but exits 1 on extension_host_pm_cli_version_skew: the pinned host is 2026.10.25 and the package is 2026.11.4. This existing version relationship is unchanged; dependency bumps are owned by Dependabot and are outside this task. The warning is retained as a validation limitation rather than weakening health or altering the SDK pin." + "2026-11-05T03:59:45.842Z",codex-agent,"Clarification of the strict-health warning: installed SDK source shows extension_host_pm_cli_version_skew compares the CLI host version with the CLI copy resolved by a loaded extension, not the extension package version itself. The warning names 2026.10.25 and 2026.11.4. The tracked lockfile and local pinned host both remain 2026.10.25. Disabling extensions for the separate integrity receipt produces all ten checks healthy with zero warnings; normal extension-enabled health remains a disclosed warning." + "2026-11-05T04:01:40.883Z",codex-agent,"Exact source-head 850e964 CI Bun release gate passed on Node 26: https://github.com/unbraind/pm-github/actions/runs/41962134164/job/127030715983 . The complete bun run release:check command passed with 1717 tests, zero failures, zero production audit vulnerabilities and 100 percent lines, statements, branches and functions across the unchanged full source inventory. Local native Bun 1.3.5 focused suite separately passed 73 tests. Other current CI jobs and the local npm release coverage are still running; no incomplete run is counted as approval." + "2026-11-05T04:02:50.796Z",codex-agent,"The 30-minute CI observation cutoff for source head 850e964 has been reached. Run https://github.com/unbraind/pm-github/actions/runs/41962134164 has Bun release on Node 26 successful; npm on Node 22, npm on Node 26 and Bun release on Node 22 were still in progress at the last snapshot. CodeQL and Semgrep succeeded; skipped or neutral bot reviews are not approval. Further CI polling for this head stops at this cutoff. Independent local release coverage continues serially." + "2026-11-05T04:20:01.108Z",codex-agent,"Final source-head gate receipts for 850e964: the complete npm run release:check step succeeded on Node 26 at 04:05:55 UTC and Node 22 at 04:12:18 UTC; both Bun release jobs succeeded as well. All four commands used the unchanged full release gate, including exact full-inventory coverage. The Node 26 Bun log confirms 1717 passing tests, zero failures/cancellations/skips and 30 modules at 100/100/100/100. The npm CI jobs still run their later native Bun phase, so whole-job completion is distinct from the completed npm gate. After verifying these receipts, the duplicate local release attempt was interrupted and is not passing evidence. The earlier low-priority npm test run passed 1714 tests; the final 73-case policy corpus passed under Node and native Bun, with the combined attestation suite at 81. No duplicate local Bun full gate is needed after actual exact-source CI execution of that command. No local full test process is intentionally left running." + "2026-11-05T04:20:53.947Z",codex-agent,"Implementation handoff is ready for review: all seven required policy scenarios are covered in the 73-case production-policy/workflow suite; combined publication-attestation coverage is 81 tests, and the commit-provenance behavioral revert produces 13 failures. Exact-source npm and Bun release gates passed on both supported Node lines in CI with unchanged thresholds. Runtime activation remains separately blocked in pm-github-dependabot-activation: the workflow is disabled and write-token/server-protection acceptance is outside this PR. GitHub asynchronous auto-merge cannot provide an atomic future-head lease; the documented branch-protection requirement remains material. The future release-pin placeholder is intentional because this feature is not in a published release. Installed-command/source diff-gate divergence and extension CLI version skew are disclosed validation-tool limitations. No merges, releases, secret changes or thread replies were performed. Releasing only the codex-agent claim; both items remain open for orchestrator verification." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 7d8799bb..65658793 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -22,3 +22,12 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T02:36:47.391Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/14","value":{"created_at":"2026-11-05T02:36:47.382Z","author":"codex-agent","text":"Caller ownership review retained the original PR-author-only filter in the thin caller and documented it in the migration snippet. This preserves other PR automation while still invoking the reusable workflow when a foreign actor pushes to a Dependabot-authored PR. The reusable policy itself retains all author/actor/provenance checks and revocation behavior whenever invoked. Workflow policy tests require the caller author guard and forbid caller actor guards; the two-event foreign synchronize regression still passes. Current receipts: 71 native Bun passes and 79 combined Node policy/attestation passes. Final-policy real API refresh at b1e8285 is byte-identical to the repository script (SHA-256 8063b11c33820d662187bcd5a6741ede744a447be16484d2dc97be4630023356), refuses the real pm-context#211 foreign addition and performs zero mutation calls. Remaining release gates will use the already installed Node 26.13.0 through nvm, matching the CI runtime line; the active npm test run remains Node 24.19.0."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T02:36:47.391Z"}],"before_hash":"7fcbd4bebfd6f87966beae09fbf1ea2b42058254e2a847d92893574fb06d85b3","after_hash":"8e7e6baa29c44fc18b3882af7380cf5ac76033f55ee3ebae76e05140972ba8f5","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":"58845d489b56edb51f46a7f2b9824b2f77cdb4b14b61c5fd58904328d307efce"} {"hash_algorithm":"sha256","ts":"2026-11-05T03:08:03.976Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/15","value":{"created_at":"2026-11-05T03:08:03.967Z","author":"codex-agent","text":"At the 30-minute CI cutoff for source head 773283e4eae83e1b7d130afc92997e1007cdd628, CodeQL passed: https://github.com/unbraind/pm-github/actions/runs/41956452617 . CI https://github.com/unbraind/pm-github/actions/runs/41956452612 still has test (22), test (26), bun-release (22) and bun-release (26) in progress. These are pending, not approval; CI polling for this head stops here. Independent local required gates continue. The low-priority full npm suite has passed the closeout matrix, sync publication boundaries and the 500-sequence SDK state-model regression, with no failures reported so far."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:08:03.976Z"}],"before_hash":"8e7e6baa29c44fc18b3882af7380cf5ac76033f55ee3ebae76e05140972ba8f5","after_hash":"0fd69bed81eea80656471c0533c5dd8d1d6ba4777bfc7adb93e612096ecd153b","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":"43d8f0ef033f048a9ea6608ff3ed7df1d1841769bd146b657c5c12c9f22d41f0"} {"hash_algorithm":"sha256","ts":"2026-11-05T03:31:24.214Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/16","value":{"created_at":"2026-11-05T03:31:24.213Z","author":"codex-agent","text":"Completed the low-priority Node 24.19.0 npm test run: 1714 passed, zero failed, cancelled or skipped; elapsed 8140.26 seconds. It began before the final three new regression cases, so the fresh final-source release gates will execute the updated corpus. Early shell state projections now reject incomplete open-PR data instead of treating a missing auto_merge field as unarmed; malformed input forces a revocation attempt. Real jq tests cover malformed data and closed historical metadata. Reverting only the projection makes the malformed-state behavioral test fail. Launcher and bootstrap processes now explicitly use non-git temporary working directories, rather than merely storing their files there. Current final focused receipts: 73 native Bun passes, 81 combined Node policy/attestation passes, and successful pinned pm test execution. Test typecheck, lint and publication-attestation checks pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:31:24.214Z"}],"before_hash":"0fd69bed81eea80656471c0533c5dd8d1d6ba4777bfc7adb93e612096ecd153b","after_hash":"f156572150329184a2c62c96a18fe6af358925e252f8cb3c9315ad7b82a55765","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":"9483edb5d97bed85572fb45a1d3117139076453337f6b5432c10f751fbffe41f"} +{"hash_algorithm":"sha256","ts":"2026-11-05T03:51:57.488Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/17","value":{"created_at":"2026-11-05T03:51:57.488Z","author":"codex-agent","text":"The first Node 26 release-gate attempt passed prechecks and entered coverage, then was interrupted with exit 130 to isolate inherited agent-session and credential context from synthetic fixtures. It is not counted as passing. Installed SDK source confirms automatic Codex session-provenance capture; CI has no such inherited session context. Restarted the unchanged complete release command in a CI-like child environment with agent/provider credentials and context excluded, public npm configuration, cache preference, empty GitHub credential configuration and isolated Git config. Parent agent configuration and project data remain untouched. All tests, source inventory, thresholds and the two-worker bound are unchanged; no other local suite runs alongside coverage."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:51:57.488Z"}],"before_hash":"f156572150329184a2c62c96a18fe6af358925e252f8cb3c9315ad7b82a55765","after_hash":"f3cfccdab377e22765bee9f908e0599e395457c7104fa333b777fcf2ac1f1aa3","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":"8a9bf133e9b644d117fd590cdd96016636ebee49c5c05808ac626b0835b3e625"} +{"hash_algorithm":"sha256","ts":"2026-11-05T03:56:25.085Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/18","value":{"created_at":"2026-11-05T03:56:25.085Z","author":"codex-agent","text":"Read-only follow-up confirmed the previous candidate CI run completed successfully: Node 22, Node 26, Bun release on Node 22 and Bun release on Node 26 all passed. This receipt applies to commit 773283e only; the latest stricter malformed-state receipt checks are being verified separately. CodeQL and Semgrep passed for 850e964; its four CI jobs remain in progress. Local final release coverage is running alone after all prechecks passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:56:25.085Z"}],"before_hash":"f3cfccdab377e22765bee9f908e0599e395457c7104fa333b777fcf2ac1f1aa3","after_hash":"89de8efa01499117aab082f01030ee592992cd9d9339ddf1428cf3d37e07ab09","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":"7670b2bed810a3bb41066c22b4ca8d08f3cb5f5436f483c03891d773f3cc3874"} +{"hash_algorithm":"sha256","ts":"2026-11-05T03:56:44.273Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/19","value":{"created_at":"2026-11-05T03:56:44.272Z","author":"codex-agent","text":"Strict tracker health reports settings, storage, locks, integrity and append-only history all healthy, but exits 1 on extension_host_pm_cli_version_skew: the pinned host is 2026.10.25 and the package is 2026.11.4. This existing version relationship is unchanged; dependency bumps are owned by Dependabot and are outside this task. The warning is retained as a validation limitation rather than weakening health or altering the SDK pin."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:56:44.273Z"}],"before_hash":"89de8efa01499117aab082f01030ee592992cd9d9339ddf1428cf3d37e07ab09","after_hash":"9460eb5fc21cc1fa01bdd11bf85b5020cf5ad13c31b6b415222b7fb38e724b5f","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":"01b5b0b34408aa8706d5a498cbe9e30ca2e7f9a9d9a8433c5adbf18b8c1b8bdf"} +{"hash_algorithm":"sha256","ts":"2026-11-05T03:59:45.842Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/20","value":{"created_at":"2026-11-05T03:59:45.842Z","author":"codex-agent","text":"Clarification of the strict-health warning: installed SDK source shows extension_host_pm_cli_version_skew compares the CLI host version with the CLI copy resolved by a loaded extension, not the extension package version itself. The warning names 2026.10.25 and 2026.11.4. The tracked lockfile and local pinned host both remain 2026.10.25. Disabling extensions for the separate integrity receipt produces all ten checks healthy with zero warnings; normal extension-enabled health remains a disclosed warning."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T03:59:45.842Z"}],"before_hash":"9460eb5fc21cc1fa01bdd11bf85b5020cf5ad13c31b6b415222b7fb38e724b5f","after_hash":"e44b30fdbff5f9694ca12f0309aaa087c3d16dfb74d3a5358bf63da57590c899","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":"dc8c6238dd548ae395c1bd7e3cc86d89823dcf8ef57ad3b12a3ef06ac90013a4"} +{"hash_algorithm":"sha256","ts":"2026-11-05T04:01:40.885Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/21","value":{"created_at":"2026-11-05T04:01:40.883Z","author":"codex-agent","text":"Exact source-head 850e964 CI Bun release gate passed on Node 26: https://github.com/unbraind/pm-github/actions/runs/41962134164/job/127030715983 . The complete bun run release:check command passed with 1717 tests, zero failures, zero production audit vulnerabilities and 100 percent lines, statements, branches and functions across the unchanged full source inventory. Local native Bun 1.3.5 focused suite separately passed 73 tests. Other current CI jobs and the local npm release coverage are still running; no incomplete run is counted as approval."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T04:01:40.885Z"}],"before_hash":"e44b30fdbff5f9694ca12f0309aaa087c3d16dfb74d3a5358bf63da57590c899","after_hash":"98de8fb3b00e6de04b3332e96453ac8091bbdf5e5973148938f0d1c6009363d1","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":"3f98e052f9071ef5b23c33c87d84f8db09414ee271c4c825753a785648464869"} +{"hash_algorithm":"sha256","ts":"2026-11-05T04:02:50.797Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/22","value":{"created_at":"2026-11-05T04:02:50.796Z","author":"codex-agent","text":"The 30-minute CI observation cutoff for source head 850e964 has been reached. Run https://github.com/unbraind/pm-github/actions/runs/41962134164 has Bun release on Node 26 successful; npm on Node 22, npm on Node 26 and Bun release on Node 22 were still in progress at the last snapshot. CodeQL and Semgrep succeeded; skipped or neutral bot reviews are not approval. Further CI polling for this head stops at this cutoff. Independent local release coverage continues serially."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T04:02:50.797Z"}],"before_hash":"98de8fb3b00e6de04b3332e96453ac8091bbdf5e5973148938f0d1c6009363d1","after_hash":"224aaa2f346996a1cf907b91b35628fefdb9361c2165fd85b35c9f55f5ef1136","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":"801050983ed1e508b1df166820c59b65b5a470372e0efb5fffda77fddc756cbf"} +{"hash_algorithm":"sha256","ts":"2026-11-05T04:20:01.108Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/23","value":{"created_at":"2026-11-05T04:20:01.108Z","author":"codex-agent","text":"Final source-head gate receipts for 850e964: the complete npm run release:check step succeeded on Node 26 at 04:05:55 UTC and Node 22 at 04:12:18 UTC; both Bun release jobs succeeded as well. All four commands used the unchanged full release gate, including exact full-inventory coverage. The Node 26 Bun log confirms 1717 passing tests, zero failures/cancellations/skips and 30 modules at 100/100/100/100. The npm CI jobs still run their later native Bun phase, so whole-job completion is distinct from the completed npm gate. After verifying these receipts, the duplicate local release attempt was interrupted and is not passing evidence. The earlier low-priority npm test run passed 1714 tests; the final 73-case policy corpus passed under Node and native Bun, with the combined attestation suite at 81. No duplicate local Bun full gate is needed after actual exact-source CI execution of that command. No local full test process is intentionally left running."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T04:20:01.108Z"}],"before_hash":"224aaa2f346996a1cf907b91b35628fefdb9361c2165fd85b35c9f55f5ef1136","after_hash":"b9d1b73348b715567119b05839e1b9c4041c1f760e131bf3242e5fdb90d7e0cd","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":"535b78b728206e4a942ed2918c4a0868709c10bdbdd561a34c6e2948f19bcc29"} +{"hash_algorithm":"sha256","ts":"2026-11-05T04:20:53.947Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/24","value":{"created_at":"2026-11-05T04:20:53.947Z","author":"codex-agent","text":"Implementation handoff is ready for review: all seven required policy scenarios are covered in the 73-case production-policy/workflow suite; combined publication-attestation coverage is 81 tests, and the commit-provenance behavioral revert produces 13 failures. Exact-source npm and Bun release gates passed on both supported Node lines in CI with unchanged thresholds. Runtime activation remains separately blocked in pm-github-dependabot-activation: the workflow is disabled and write-token/server-protection acceptance is outside this PR. GitHub asynchronous auto-merge cannot provide an atomic future-head lease; the documented branch-protection requirement remains material. The future release-pin placeholder is intentional because this feature is not in a published release. Installed-command/source diff-gate divergence and extension CLI version skew are disclosed validation-tool limitations. No merges, releases, secret changes or thread replies were performed. Releasing only the codex-agent claim; both items remain open for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T04:20:53.947Z"}],"before_hash":"b9d1b73348b715567119b05839e1b9c4041c1f760e131bf3242e5fdb90d7e0cd","after_hash":"243e6b3d09ca7e0b4afcd914d77b29645463b3f2218f22a3b834f190b182694f","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":"a8a87830a1827e5552a544b3e11f0ab3dd9cbeffa022c504d40f7bfc946322c5"} +{"hash_algorithm":"sha256","ts":"2026-11-05T04:20:55.050Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T04:20:55.050Z"}],"before_hash":"243e6b3d09ca7e0b4afcd914d77b29645463b3f2218f22a3b834f190b182694f","after_hash":"7f6e1772181c574293478cd939aab7669b0218efee10665edc4f89a571c34f2e","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":"aed2d38e21d9cbd17f67a04578ad9535c5ad1b7f2ffbd2e08596c3c701755f1d"} From c21d40ee5ce23cd80e16db37724657cf5b3b5c46 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 09:21:51 +0100 Subject: [PATCH 10/19] Bound Dependabot policy calls and preserve pinned self-call policy --- .../features/pm-github-dependabot-merge.toon | 31 +++++- .../history/pm-github-dependabot-merge.jsonl | 8 ++ .../workflows/pm-github-dependabot-merge.yml | 50 ++++++++-- README.md | 11 ++- scripts/dependabot-merge.ts | 14 ++- test/dependabot-merge.test.ts | 99 +++++++++++++++---- 6 files changed, 173 insertions(+), 40 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 9a1c6e1d..611dd271 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -4,12 +4,14 @@ description: "Replace copied auto-merge logic with a reusable workflow that revo type: Feature status: in_progress priority: 2 -tags[5]: changelog-unreleased,ci,dependabot,security,workflows +tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T04:20:55.050Z" +updated_at: "2026-11-05T08:21:47.911Z" +assignee: codex-agent +claim_principal: codex-agent author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[25]{created_at,author,text}: +comments[30]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -35,6 +37,11 @@ comments[25]{created_at,author,text}: "2026-11-05T04:02:50.796Z",codex-agent,"The 30-minute CI observation cutoff for source head 850e964 has been reached. Run https://github.com/unbraind/pm-github/actions/runs/41962134164 has Bun release on Node 26 successful; npm on Node 22, npm on Node 26 and Bun release on Node 22 were still in progress at the last snapshot. CodeQL and Semgrep succeeded; skipped or neutral bot reviews are not approval. Further CI polling for this head stops at this cutoff. Independent local release coverage continues serially." "2026-11-05T04:20:01.108Z",codex-agent,"Final source-head gate receipts for 850e964: the complete npm run release:check step succeeded on Node 26 at 04:05:55 UTC and Node 22 at 04:12:18 UTC; both Bun release jobs succeeded as well. All four commands used the unchanged full release gate, including exact full-inventory coverage. The Node 26 Bun log confirms 1717 passing tests, zero failures/cancellations/skips and 30 modules at 100/100/100/100. The npm CI jobs still run their later native Bun phase, so whole-job completion is distinct from the completed npm gate. After verifying these receipts, the duplicate local release attempt was interrupted and is not passing evidence. The earlier low-priority npm test run passed 1714 tests; the final 73-case policy corpus passed under Node and native Bun, with the combined attestation suite at 81. No duplicate local Bun full gate is needed after actual exact-source CI execution of that command. No local full test process is intentionally left running." "2026-11-05T04:20:53.947Z",codex-agent,"Implementation handoff is ready for review: all seven required policy scenarios are covered in the 73-case production-policy/workflow suite; combined publication-attestation coverage is 81 tests, and the commit-provenance behavioral revert produces 13 failures. Exact-source npm and Bun release gates passed on both supported Node lines in CI with unchanged thresholds. Runtime activation remains separately blocked in pm-github-dependabot-activation: the workflow is disabled and write-token/server-protection acceptance is outside this PR. GitHub asynchronous auto-merge cannot provide an atomic future-head lease; the documented branch-protection requirement remains material. The future release-pin placeholder is intentional because this feature is not in a published release. Installed-command/source diff-gate divergence and extension CLI version skew are disclosed validation-tool limitations. No merges, releases, secret changes or thread replies were performed. Releasing only the codex-agent claim; both items remain open for orchestrator verification." + "2026-11-05T08:11:45.790Z",codex-agent,"Review round on 2652cc9: fetched all three unresolved threads with complete comments and latest review body; merged origin/main first (650797d). Valid fixes: bounded gh calls and revocation retries in TypeScript and both shell paths; README owner claim; --auto fixture assertion; local-only main policy override. Also evaluating the unverified web-flow attribution concern. Plan: behavioral regressions and isolated reverts, full npm and Bun release gates serially under nice, append-only tracker evidence and explicit-path commit. No thread replies or resolutions." + "2026-11-05T08:16:42.288Z",codex-agent,"Review body unverified provenance claim: no supported forgery demonstrated. GitHub documents createCommitOnBranch (including web commit behavior) as authenticated-user authorship with no custom author/committer; bot verification requires bot/App authentication without custom identity fields. Rebase-and-merge cannot obtain a generic GitHub system signature. Existing corpus rejects foreign committers even when verified and unsigned web-flow/bot attribution; verified Dependabot/web-flow is intentional for real fleet commits. Sources: https://docs.github.com/en/graphql/reference/commits#createcommitonbranch and https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification . Retaining this documented exception; generic signature validity alone is insufficient, and the policy also enforces author/committer and event identities. GitHub asynchronous merge and compromised credentials remain the previously documented server-protection boundary." + "2026-11-05T08:18:23.452Z",codex-agent,"Implemented all four actionable review findings. githubCli bounds each process with SIGKILL after 60 seconds and at most three attempts; both bootstrap shell paths bound every read, policy download and disable call identically, discard failed output, and report an explicit potentially-still-armed failure on exhausted revocation. Workflow job limit is 30 minutes. job.workflow_ref plus the caller pull-request merge ref limits trusted-main selection, preserving remote SHA-pinned self-calls. Focused Node and native Bun each pass 85 tests; pinned pm linked runner passed 85. Shell fixtures execute real shortened timeout calls against TERM-resistant finite stalls and retry successes. No thresholds changed; dist is generated and untracked." + "2026-11-05T08:19:31.969Z",codex-agent,"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: restoring githubCli from 2652cc9 causes 3 failures/0 passes; removing only timeout+SIGKILL causes 1 failure/2 passes; reverting both shell paths to unbounded gh causes 8 failures/11 passes; restoring repository-only main override causes remote-self 1 failure; removing only production --auto causes 3 success-path failures; restoring README owner claim causes 1 failure. Fully restored copy passes 85/85. Reviewed whole PR diff for these classes: every gh call in both shell blocks is now wrapped, every policy call goes through bounded githubCli, and remote external and self-pinned loaders retain their SHA. Follow-up budget review increased the job cap from the initial 30 to 60 minutes and bounds setup and metadata individually to 5 minutes; this leaves room for worst-case revocation retries instead of cutting off the final fallback." + "2026-11-05T08:21:47.910Z",codex-agent,"Final focused validation: pinned pm linked Node and native Bun runners each pass 85 tests, zero failures/skips. Focused c8 measures scripts/dependabot-merge.ts at exact 100 percent statements, branches, functions and lines. Test typecheck, ESLint, docstrings (271 declarations), zero-threshold duplication and regenerated changelog check pass. Starting the unchanged full npm then Bun release gates serially with nice, a credential-free child context and isolated Git config; complete release and coverage results are pending. Commit identity range contains only the required approved bot email." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project @@ -47,8 +54,22 @@ files[5]: scope: project - path: test/dependabot-merge.test.ts scope: project -tests[1]{command,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: - nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts,project,120,codex-agent,"2026-11-05T00:34:44.010Z",local_mutation,feat/reusable-dependabot-merge +tests[2]: + - command: nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts + scope: project + timeout_seconds: 120 + provenance: + author: codex-agent + created_at: "2026-11-05T00:34:44.010Z" + source_kind: local_mutation + source_ref: feat/reusable-dependabot-merge + - command: nice -n 10 bun test --timeout 600000 test/dependabot-merge.test.ts + scope: project + provenance: + author: codex-agent + created_at: "2026-11-05T08:18:21.359Z" + source_kind: local_mutation + source_ref: feat/reusable-dependabot-merge docs[1]{path,scope,note}: README.md,project,Caller migration token requirements and asynchronous auto-merge limits body: "" diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 65658793..f50a3b3d 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -31,3 +31,11 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T04:20:01.108Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/23","value":{"created_at":"2026-11-05T04:20:01.108Z","author":"codex-agent","text":"Final source-head gate receipts for 850e964: the complete npm run release:check step succeeded on Node 26 at 04:05:55 UTC and Node 22 at 04:12:18 UTC; both Bun release jobs succeeded as well. All four commands used the unchanged full release gate, including exact full-inventory coverage. The Node 26 Bun log confirms 1717 passing tests, zero failures/cancellations/skips and 30 modules at 100/100/100/100. The npm CI jobs still run their later native Bun phase, so whole-job completion is distinct from the completed npm gate. After verifying these receipts, the duplicate local release attempt was interrupted and is not passing evidence. The earlier low-priority npm test run passed 1714 tests; the final 73-case policy corpus passed under Node and native Bun, with the combined attestation suite at 81. No duplicate local Bun full gate is needed after actual exact-source CI execution of that command. No local full test process is intentionally left running."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T04:20:01.108Z"}],"before_hash":"224aaa2f346996a1cf907b91b35628fefdb9361c2165fd85b35c9f55f5ef1136","after_hash":"b9d1b73348b715567119b05839e1b9c4041c1f760e131bf3242e5fdb90d7e0cd","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":"535b78b728206e4a942ed2918c4a0868709c10bdbdd561a34c6e2948f19bcc29"} {"hash_algorithm":"sha256","ts":"2026-11-05T04:20:53.947Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/24","value":{"created_at":"2026-11-05T04:20:53.947Z","author":"codex-agent","text":"Implementation handoff is ready for review: all seven required policy scenarios are covered in the 73-case production-policy/workflow suite; combined publication-attestation coverage is 81 tests, and the commit-provenance behavioral revert produces 13 failures. Exact-source npm and Bun release gates passed on both supported Node lines in CI with unchanged thresholds. Runtime activation remains separately blocked in pm-github-dependabot-activation: the workflow is disabled and write-token/server-protection acceptance is outside this PR. GitHub asynchronous auto-merge cannot provide an atomic future-head lease; the documented branch-protection requirement remains material. The future release-pin placeholder is intentional because this feature is not in a published release. Installed-command/source diff-gate divergence and extension CLI version skew are disclosed validation-tool limitations. No merges, releases, secret changes or thread replies were performed. Releasing only the codex-agent claim; both items remain open for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T04:20:53.947Z"}],"before_hash":"b9d1b73348b715567119b05839e1b9c4041c1f760e131bf3242e5fdb90d7e0cd","after_hash":"243e6b3d09ca7e0b4afcd914d77b29645463b3f2218f22a3b834f190b182694f","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":"a8a87830a1827e5552a544b3e11f0ab3dd9cbeffa022c504d40f7bfc946322c5"} {"hash_algorithm":"sha256","ts":"2026-11-05T04:20:55.050Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-05T04:20:55.050Z"}],"before_hash":"243e6b3d09ca7e0b4afcd914d77b29645463b3f2218f22a3b834f190b182694f","after_hash":"7f6e1772181c574293478cd939aab7669b0218efee10665edc4f89a571c34f2e","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":"aed2d38e21d9cbd17f67a04578ad9535c5ad1b7f2ffbd2e08596c3c701755f1d"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:11:41.880Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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-05T08:11:41.880Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"7f6e1772181c574293478cd939aab7669b0218efee10665edc4f89a571c34f2e","after_hash":"5a440858a26dc807c795c253db2727dfae934725beb175dbc60dad446b29d928","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":"af767c1867669d62c9bc836b017bbf987e011447a14ee16f1a835a28da3c19ff"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:11:43.978Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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-05T08:11:43.978Z"},{"op":"replace","path":"/metadata/tags/4","value":"security"},{"op":"replace","path":"/metadata/tags/3","value":"gh:unbraind/pm-github#221"},{"op":"add","path":"/metadata/tags/5","value":"workflows"}],"before_hash":"5a440858a26dc807c795c253db2727dfae934725beb175dbc60dad446b29d928","after_hash":"886365bdaca7a2058b01bbd173ee7766940a3b0e5ea805d0788cd5380ba7c71e","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":"ebcaa9912c3c4abaa91800e9b0250e9b252aacc09e7b8c88d5840f4814cc1f58"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:11:45.797Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/25","value":{"created_at":"2026-11-05T08:11:45.790Z","author":"codex-agent","text":"Review round on 2652cc9: fetched all three unresolved threads with complete comments and latest review body; merged origin/main first (650797d). Valid fixes: bounded gh calls and revocation retries in TypeScript and both shell paths; README owner claim; --auto fixture assertion; local-only main policy override. Also evaluating the unverified web-flow attribution concern. Plan: behavioral regressions and isolated reverts, full npm and Bun release gates serially under nice, append-only tracker evidence and explicit-path commit. No thread replies or resolutions."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:11:45.797Z"}],"before_hash":"886365bdaca7a2058b01bbd173ee7766940a3b0e5ea805d0788cd5380ba7c71e","after_hash":"da1cad3f016d10165161551ba6f428e75d4012b086fb68417bb931468a6c3bbb","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":"ccc6520eb2075ce242bdf38f5ce32dfd26b746ffa63862c98fa62d4b97e9ffe5"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:16:42.289Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/26","value":{"created_at":"2026-11-05T08:16:42.288Z","author":"codex-agent","text":"Review body unverified provenance claim: no supported forgery demonstrated. GitHub documents createCommitOnBranch (including web commit behavior) as authenticated-user authorship with no custom author/committer; bot verification requires bot/App authentication without custom identity fields. Rebase-and-merge cannot obtain a generic GitHub system signature. Existing corpus rejects foreign committers even when verified and unsigned web-flow/bot attribution; verified Dependabot/web-flow is intentional for real fleet commits. Sources: https://docs.github.com/en/graphql/reference/commits#createcommitonbranch and https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification . Retaining this documented exception; generic signature validity alone is insufficient, and the policy also enforces author/committer and event identities. GitHub asynchronous merge and compromised credentials remain the previously documented server-protection boundary."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:16:42.289Z"}],"before_hash":"da1cad3f016d10165161551ba6f428e75d4012b086fb68417bb931468a6c3bbb","after_hash":"a3c2964118a7897504b176f0bf84071b20ab13e95aa56bf656f61f831f4aae3b","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":"9e863dea7ba4a95f7c287f4d721ec4b443549a591960020ec4934bbddba6d383"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:18:21.534Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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":"nice -n 10 bun test --timeout 600000 test/dependabot-merge.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-11-05T08:18:21.359Z","source_kind":"local_mutation","source_ref":"feat/reusable-dependabot-merge"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:18:21.534Z"}],"before_hash":"a3c2964118a7897504b176f0bf84071b20ab13e95aa56bf656f61f831f4aae3b","after_hash":"e2f1d11b161400c7161aaf01a5fde87e353cdeff5c67754ec1702e3b28181098","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":"51e8d5a9a5f37a8b058b23a0a66466fd8589a9b961a4ed63cc38aa6d19d3c3a9"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:18:23.452Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/27","value":{"created_at":"2026-11-05T08:18:23.452Z","author":"codex-agent","text":"Implemented all four actionable review findings. githubCli bounds each process with SIGKILL after 60 seconds and at most three attempts; both bootstrap shell paths bound every read, policy download and disable call identically, discard failed output, and report an explicit potentially-still-armed failure on exhausted revocation. Workflow job limit is 30 minutes. job.workflow_ref plus the caller pull-request merge ref limits trusted-main selection, preserving remote SHA-pinned self-calls. Focused Node and native Bun each pass 85 tests; pinned pm linked runner passed 85. Shell fixtures execute real shortened timeout calls against TERM-resistant finite stalls and retry successes. No thresholds changed; dist is generated and untracked."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:18:23.452Z"}],"before_hash":"e2f1d11b161400c7161aaf01a5fde87e353cdeff5c67754ec1702e3b28181098","after_hash":"8e2c821e54198c39d771b4a76664200c7010c8f2c23eeef9f2bd3e98860f3c0a","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":"2ae37d1dc755d86678d390f53e7fb2c44225fd464c3c4db1f21e4edbd9371f96"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:19:31.970Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/28","value":{"created_at":"2026-11-05T08:19:31.969Z","author":"codex-agent","text":"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: restoring githubCli from 2652cc9 causes 3 failures/0 passes; removing only timeout+SIGKILL causes 1 failure/2 passes; reverting both shell paths to unbounded gh causes 8 failures/11 passes; restoring repository-only main override causes remote-self 1 failure; removing only production --auto causes 3 success-path failures; restoring README owner claim causes 1 failure. Fully restored copy passes 85/85. Reviewed whole PR diff for these classes: every gh call in both shell blocks is now wrapped, every policy call goes through bounded githubCli, and remote external and self-pinned loaders retain their SHA. Follow-up budget review increased the job cap from the initial 30 to 60 minutes and bounds setup and metadata individually to 5 minutes; this leaves room for worst-case revocation retries instead of cutting off the final fallback."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:19:31.970Z"}],"before_hash":"8e2c821e54198c39d771b4a76664200c7010c8f2c23eeef9f2bd3e98860f3c0a","after_hash":"511d956d99524d1adbb80aabba0fa74a76b699b9e772a39798112a795195b6bb","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":"63817e011c52d34c3c232c4de8fe84e0734c31de9161db5930225b5b1cdb47cd"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:21:47.911Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/29","value":{"created_at":"2026-11-05T08:21:47.910Z","author":"codex-agent","text":"Final focused validation: pinned pm linked Node and native Bun runners each pass 85 tests, zero failures/skips. Focused c8 measures scripts/dependabot-merge.ts at exact 100 percent statements, branches, functions and lines. Test typecheck, ESLint, docstrings (271 declarations), zero-threshold duplication and regenerated changelog check pass. Starting the unchanged full npm then Bun release gates serially with nice, a credential-free child context and isolated Git config; complete release and coverage results are pending. Commit identity range contains only the required approved bot email."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:21:47.911Z"}],"before_hash":"511d956d99524d1adbb80aabba0fa74a76b699b9e772a39798112a795195b6bb","after_hash":"63d0c084bab9c998b0dbbedb94280fd17f99436e0f26e1e78f0608ffcbd0ecea","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":"98f92bf63214a6317d879c323e9f76608be7a962ca2f31a10437bba26ed8a298"} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml index dbab960b..731d71d6 100644 --- a/.github/workflows/pm-github-dependabot-merge.yml +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -22,6 +22,8 @@ concurrency: jobs: dependabot-merge: runs-on: ubuntu-latest + # Covers the worst-case bounded policy/revocation budget plus tooling and metadata. + timeout-minutes: 60 permissions: contents: write pull-requests: write @@ -42,16 +44,29 @@ jobs: shell: bash run: | set -euo pipefail + # Keep retries bounded and discard partial output from failed attempts. + bounded_gh() { + local attempt output + for attempt in 1 2 3; do + if output="$(timeout --signal=KILL 60s gh "$@" 2>/dev/null)"; then + printf '%s' "${output}" + return 0 + fi + echo "::warning::GitHub CLI failed or timed out (attempt ${attempt}/3)." >&2 + done + return 1 + } [[ "${REPOSITORY}" =~ ^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$ && "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]] - armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq 'if .state == "closed" then false elif .state == "open" and has("auto_merge") then .auto_merge != null else error("Unverifiable PR state") end' 2>/dev/null)" || armed=true + armed="$(bounded_gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq 'if .state == "closed" then false elif .state == "open" and has("auto_merge") then .auto_merge != null else error("Unverifiable PR state") end')" || armed=true if [[ "${armed}" != false ]]; then - if ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then - echo '::error::Cannot revoke auto-merge; check token write permissions immediately.' + if ! bounded_gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto; then + echo '::error::Cannot revoke auto-merge after 3 bounded attempts; request may still be armed. Check token write permissions immediately.' exit 1 fi fi echo 'Previous auto-merge requests cleared before tooling.' >> "${GITHUB_STEP_SUMMARY}" - name: Setup policy runtime + timeout-minutes: 5 uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: "24" @@ -60,20 +75,36 @@ jobs: id: policy env: WORKFLOW_SHA: ${{ job.workflow_sha }} + WORKFLOW_REF: ${{ job.workflow_ref }} + CALLER_REF: ${{ github.ref }} shell: bash run: | set -euo pipefail + # Keep retries bounded and discard partial output from failed attempts. + bounded_gh() { + local attempt output + for attempt in 1 2 3; do + if output="$(timeout --signal=KILL 60s gh "$@" 2>/dev/null)"; then + printf '%s' "${output}" + return 0 + fi + echo "::warning::GitHub CLI failed or timed out (attempt ${attempt}/3)." >&2 + done + return 1 + } policy_sha="${WORKFLOW_SHA}" # A local-path pull_request call resolves workflow_sha from the PR. # Its executable policy must instead come from the trusted default branch. - if [[ "${REPOSITORY}" == unbraind/pm-github ]]; then - policy_sha="$(gh api repos/unbraind/pm-github/git/ref/heads/main --jq '.object.sha')" || policy_sha="" + if [[ "${REPOSITORY}" == unbraind/pm-github && "${EVENT_NAME}" == pull_request && + "${WORKFLOW_REF}" == "unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@${CALLER_REF}" && + "${CALLER_REF}" == "refs/pull/${PR_NUMBER}/merge" ]]; then + policy_sha="$(bounded_gh api repos/unbraind/pm-github/git/ref/heads/main --jq '.object.sha')" || policy_sha="" fi - if [[ ! "${policy_sha}" =~ ^[a-f0-9]{40}$ ]] || ! gh api "repos/unbraind/pm-github/contents/scripts/dependabot-merge.ts?ref=${policy_sha}" \ + if [[ ! "${policy_sha}" =~ ^[a-f0-9]{40}$ ]] || ! bounded_gh api "repos/unbraind/pm-github/contents/scripts/dependabot-merge.ts?ref=${policy_sha}" \ -H 'Accept: application/vnd.github.raw+json' > "${RUNNER_TEMP}/dependabot-merge.ts"; then - armed="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq 'if .state == "closed" then false elif .state == "open" and has("auto_merge") then .auto_merge != null else error("Unverifiable PR state") end' 2>/dev/null)" || armed=true - if [[ "${armed}" != false ]] && ! gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto 2>/dev/null; then - echo '::error::Cannot revoke auto-merge after policy load failure; check token write permissions immediately.' + armed="$(bounded_gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq 'if .state == "closed" then false elif .state == "open" and has("auto_merge") then .auto_merge != null else error("Unverifiable PR state") end')" || armed=true + if [[ "${armed}" != false ]] && ! bounded_gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto; then + echo '::error::Cannot revoke auto-merge after policy load failure and 3 bounded attempts; request may still be armed. Check token write permissions immediately.' exit 1 fi echo '::notice::Cannot load trusted merge policy; auto-merge not armed.' @@ -103,6 +134,7 @@ jobs: run: node "${RUNNER_TEMP}/run-dependabot-merge.mts" - name: Fetch Dependabot metadata + timeout-minutes: 5 id: metadata if: steps.policy.outputs.loaded == 'true' && github.event_name == 'pull_request' && github.actor == 'dependabot[bot]' && github.triggering_actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' continue-on-error: true diff --git a/README.md b/README.md index 3da02019..2202a9c2 100644 --- a/README.md +++ b/README.md @@ -890,8 +890,11 @@ and review rules on the protected base branch. Foreign commits revoke auto-merge and produce a successful job, a summary and `::notice` listing **short SHA and author login only**. Disallowed metadata, unsupported events (including `pull_request_target`), API failures and incomplete -pagination also leave auto-merge unarmed with a clear notice. An inability to -revoke is an operational failure, rather than a false successful safety claim. +pagination also leave auto-merge unarmed with a clear notice. Every CLI call is +limited to three attempts of 60 seconds each; failed attempts discard partial +output. A timed-out state read still attempts revocation. An +inability to revoke is an operational failure that warns the request may remain +armed, rather than a false successful safety claim. No PR files, Git author names/emails, commit messages or raw API errors are printed. Actions are SHA-pinned and no PR code is checked out or executed. The standalone policy is fetched from `job.workflow_sha` in pm-github; this @@ -982,8 +985,8 @@ The group pattern is the repository name, covering both reusable workflow paths. Use exactly `pm-toolchain`: the fleet's existing [dependabot-auto-merge.yml](.github/workflows/dependabot-auto-merge.yml) enables `gh pr merge --auto --squash` for Dependabot PRs in that group, including calendar -version major changes. The workflow checks the Dependabot author and repository -owner, then the metadata group or minor/patch update type. Commit prefix and +version major changes. The workflow checks the Dependabot author, then the +metadata group or minor/patch update type. Commit prefix and labels do not control merge eligibility; the explicit values above keep PRs consistent. Ensure the two labels exist (GitHub ignores unknown custom labels). Repository auto-merge must be enabled and required branch-protection checks must diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts index a5dfd809..4580c50b 100644 --- a/scripts/dependabot-merge.ts +++ b/scripts/dependabot-merge.ts @@ -39,9 +39,15 @@ export interface MergeResult { message: string; } -/** Use the current environment without a shell; failed reads cannot become empty success. */ -export function githubCli(args: readonly string[], executable = "gh"): string { - return execFileSync(executable, args, { encoding: "utf8", env: { ...process.env }, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }); +/** Bound each CLI attempt, discard failed output, and retry at most three times without a shell. */ +export function githubCli(args: readonly string[], executable = "gh", timeout = 60_000): string { + for (let attempt = 1; attempt <= 3; attempt++) { + try { + return execFileSync(executable, args, { encoding: "utf8", env: { ...process.env }, maxBuffer: 32 * 1024 * 1024, + stdio: ["ignore", "pipe", "pipe"], timeout, killSignal: "SIGKILL" }); + } catch { /* A timeout or failed attempt must never expose partial output. */ } + } + throw new Error("GitHub CLI failed or timed out after 3 bounded attempts (60 seconds each in production)."); } const bot = "dependabot[bot]"; @@ -73,7 +79,7 @@ export function runDependabotMerge(input: MergeInput, gh: (args: readonly string } catch { /* Unreadable state still owes revocation. */ } if (armed) { try { gh(["pr", "merge", ...target, "--disable-auto"]); } - catch { throw new Error("Auto-merge revocation denied or unavailable; check token write permissions immediately."); } + catch { throw new Error("Auto-merge revocation denied or unavailable after bounded retries; request may still be armed. Check token write permissions and retry immediately."); } } }; revoke(); diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 384166a7..10acbdc1 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -42,6 +42,7 @@ function fixture() { state.auto_merge = null; } else { assert.ok(args.includes("--squash")); + assert.ok(args.includes("--auto"), "the arm request must enable auto-merge"); assert.equal(args.at(-1), head); if (f.failArm) throw new Error("expected head changed"); state.auto_merge = {}; @@ -201,11 +202,15 @@ function loader(): string { test("workflow policy: pinned actions, minimal grants, no PR checkout or interpolated shell", () => { const source = readFileSync(join(root, ".github/workflows/pm-github-dependabot-merge.yml"), "utf8"); - const workflow = parse(source) as { on: Record; permissions: object; jobs: Record }; + const workflow = parse(source) as { on: Record; permissions: object; jobs: Record }; assert.deepEqual(Object.keys(workflow.on), ["workflow_call"]); assert.deepEqual(workflow.permissions, {}); assert.deepEqual(workflow.jobs["dependabot-merge"]!.permissions, { contents: "write", "pull-requests": "write" }); + assert.equal(workflow.jobs["dependabot-merge"]!["timeout-minutes"], 60); for (const step of workflow.jobs["dependabot-merge"]!.steps) { - if (step.uses) assert.match(step.uses, /@[a-f0-9]{40}$/); + if (step.uses) { + assert.match(step.uses, /@[a-f0-9]{40}$/); + assert.equal(step["timeout-minutes"], 5); + } if (step.run) assert.doesNotMatch(step.run, /\$\{\{/); } assert.doesNotMatch(source, /actions\/checkout|skip-commit-verification|--admin/); @@ -232,38 +237,61 @@ test("custom-token arm event preserves the request; a foreign synchronize event assert.equal(f.state.auto_merge, null, "foreign synchronize must still revoke the request"); }); -for (const mode of ["external", "local", "load-failed", "invalid-sha", "local-ref-failed"]) { +/** Exercise the production timeout arguments with a shorter real clock in shell fixtures. */ +function installFixtureTimeout(dir: string): void { + writeFileSync(join(dir, "timeout"), `#!/bin/bash +[[ "$1" == --signal=KILL && "$2" == 60s ]] || exit 97 +shift 2 +duration=60s +case "$MODE" in *hang*) duration=0.3s ;; esac +exec /usr/bin/timeout --signal=KILL "$duration" "$@" +`, { mode: 0o755 }); +} + +for (const mode of ["external", "local", "remote-self", "load-failed", "invalid-sha", "local-ref-failed", "load-hang", "fallback-read-hang", "fallback-disable-hang", "load-retry"]) { test(`real workflow loader/launcher: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-policy-")); const env = { ...process.env, PATH: dir + ":" + process.env.PATH, RUNNER_TEMP: dir, - POLICY_SOURCE: join(root, "scripts/dependabot-merge.ts"), WORKFLOW_SHA: mode === "invalid-sha" ? "bad" : head, - REPOSITORY: mode.startsWith("local") ? "unbraind/pm-github" : "acme/widgets", PR_NUMBER: "7", MODE: mode, + RETRY_MARKER: join(dir, "retried"), POLICY_SOURCE: join(root, "scripts/dependabot-merge.ts"), WORKFLOW_SHA: mode === "invalid-sha" ? "bad" : head, + WORKFLOW_REF: `unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@${mode.startsWith("local") ? "refs/pull/7/merge" : head}`, + CALLER_REF: "refs/pull/7/merge", + REPOSITORY: mode.startsWith("local") || mode === "remote-self" ? "unbraind/pm-github" : "acme/widgets", PR_NUMBER: "7", MODE: mode, GITHUB_STEP_SUMMARY: join(dir, "summary"), EVENT_NAME: "pull_request", EVENT_ACTOR: "outside", TRIGGERING_ACTOR: "outside", GITHUB_OUTPUT: join(dir, "output"), PR_AUTHOR: bot, EVENT_HEAD: head, EXTRA_GROUPS: "", PHASE: "evaluate", RECEIPTS: join(dir, "receipts") }; try { + installFixtureTimeout(dir); writeFileSync(join(dir, "gh"), `#!/bin/bash printf '%s\\n' "$*" >> "$RECEIPTS" case "$*" in *git/ref/heads/main*) [[ "$MODE" != local-ref-failed ]] || exit 1; printf '%s' '${trusted}' ;; - *contents/scripts*) [[ "$MODE" != load-failed ]] || exit 1; cat "$POLICY_SOURCE" ;; + *contents/scripts*) + [[ "$MODE" != load-hang ]] || { trap '' TERM; sleep 2; exit 0; } + [[ "$MODE" != load-retry || -f "$RETRY_MARKER" ]] || { touch "$RETRY_MARKER"; printf partial; exit 1; } + [[ "$MODE" != load-failed && "$MODE" != fallback-* ]] || exit 1; cat "$POLICY_SOURCE" ;; *'/commits?'*) printf '%s' '{"sha":"${head}","author":{"login":"outside"},"committer":{"login":"outside"}}' ;; - 'api '*) printf '%s' '{"state":"open","user":{"login":"${bot}"},"head":{"sha":"${head}"},"commits":1,"auto_merge":null}' ;; - 'pr merge '*) ;; + 'api '*) [[ "$MODE" != fallback-read-hang ]] || { trap '' TERM; sleep 2; exit 0; }; printf '%s' '{"state":"open","user":{"login":"${bot}"},"head":{"sha":"${head}"},"commits":1,"auto_merge":null}' ;; + 'pr merge '*) [[ "$MODE" != fallback-disable-hang ]] || { trap '' TERM; sleep 2; exit 0; } ;; *) exit 2 ;; esac `, { mode: 0o755 }); - const loaded = spawnSync("bash", ["-c", loader()], { cwd: dir, env, encoding: "utf8" }); - const failed = ["load-failed", "invalid-sha", "local-ref-failed"].includes(mode); - assert.equal(loaded.status, 0, loaded.stderr); + const loaded = spawnSync("bash", ["-c", loader()], { cwd: dir, env, encoding: "utf8", timeout: 10_000, killSignal: "SIGKILL" }); + const failed = ["load-failed", "invalid-sha", "local-ref-failed", "load-hang", "fallback-read-hang"].includes(mode); + assert.equal(loaded.status, mode === "fallback-disable-hang" ? 1 : 0, loaded.stderr); const receipts = readFileSync(env.RECEIPTS, "utf8"); - if (failed) { + if (mode === "fallback-disable-hang") { + assert.match(loaded.stdout, /Cannot revoke auto-merge after policy load failure and 3 bounded attempts/); + assert.equal(receipts.split("\n").filter(line => line.startsWith("pr merge ")).length, 3); + } else if (failed) { + if (mode === "load-hang") assert.equal(receipts.split("\n").filter(line => line.includes("contents/scripts")).length, 3); + if (mode === "fallback-read-hang") assert.equal(receipts.split("\n").filter(line => line.includes("pulls/7")).length, 3); assert.match(receipts, /--disable-auto/); assert.match(loaded.stdout, /::notice::Cannot load trusted merge policy/); assert.match(readFileSync(env.GITHUB_OUTPUT, "utf8"), /loaded=false/); } else { assert.match(receipts, new RegExp(`ref=${mode === "local" ? trusted : head}`)); + if (mode === "remote-self") assert.doesNotMatch(receipts, /git\/ref\/heads\/main/); const run = spawnSync(process.execPath, [join(dir, "run-dependabot-merge.mts")], { cwd: dir, env, encoding: "utf8" }); assert.equal(run.status, 0, run.stderr); assert.match(run.stdout, /::notice::Foreign or unverified commits: aaaaaaaaaaaa outside/); @@ -295,7 +323,7 @@ test("CLI adapter resolves a unique fixture command against the current PATH", ( } finally { process.env.PATH = old; rmSync(dir, { recursive: true, force: true }); } }); -for (const mode of ["armed", "clear", "closed", "malformed-api", "api-error", "denied"]) { +for (const mode of ["armed", "clear", "closed", "malformed-api", "api-error", "denied", "hang-read", "hang-disable", "retry-disable"]) { test(`pre-tooling revocation shell: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-revoke-")); try { @@ -305,6 +333,7 @@ for (const mode of ["armed", "clear", "closed", "malformed-api", "api-error", "d writeFileSync(join(dir, "gh"), `#!/bin/bash printf '%s\\n' "$*" >> "$RECEIPTS" if [[ "$1" == api ]]; then + [[ "$MODE" != hang-read ]] || { trap '' TERM; sleep 2; exit 0; } [[ "$MODE" != api-error ]] || exit 1 case "$MODE" in clear) payload='{"state":"open","auto_merge":null}' ;; @@ -313,17 +342,51 @@ if [[ "$1" == api ]]; then *) payload='{"state":"open","auto_merge":{}}' ;; esac printf '%s' "$payload" | jq -r "$4" -else [[ "$MODE" != denied ]]; fi +else + [[ "$MODE" != hang-disable ]] || { trap '' TERM; sleep 2; exit 0; } + [[ "$MODE" != retry-disable || -f "$RETRY_MARKER" ]] || { touch "$RETRY_MARKER"; exit 1; } + [[ "$MODE" != denied ]] +fi `, { mode: 0o755 }); - const env = { ...process.env, PATH: dir + ":" + process.env.PATH, REPOSITORY: "acme/widgets", PR_NUMBER: "7", + installFixtureTimeout(dir); + const env = { ...process.env, PATH: dir + ":" + process.env.PATH, RETRY_MARKER: join(dir, "retried"), REPOSITORY: "acme/widgets", PR_NUMBER: "7", MODE: mode, RECEIPTS: join(dir, "receipts"), GITHUB_STEP_SUMMARY: join(dir, "summary") }; - const result = spawnSync("bash", ["-c", shell], { cwd: dir, env, encoding: "utf8" }); - assert.equal(result.status, mode === "denied" ? 1 : 0, result.stderr); - if (mode === "denied") assert.match(result.stdout, /Cannot revoke auto-merge/); + const result = spawnSync("bash", ["-c", shell], { cwd: dir, env, encoding: "utf8", timeout: 10_000, killSignal: "SIGKILL" }); + assert.equal(result.status, mode === "denied" || mode === "hang-disable" ? 1 : 0, result.stderr); + if (mode === "denied" || mode === "hang-disable") assert.match(result.stdout, /Cannot revoke auto-merge/); else assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /cleared before tooling/); const receipts = readFileSync(env.RECEIPTS, "utf8"); + if (mode === "hang-read") assert.equal(receipts.split("\n").filter(line => line.startsWith("api ")).length, 3); + if (mode === "hang-disable" || mode === "denied") assert.equal(receipts.split("\n").filter(line => line.startsWith("pr merge ")).length, 3); + if (mode === "retry-disable") assert.equal(receipts.split("\n").filter(line => line.startsWith("pr merge ")).length, 2); if (mode === "clear" || mode === "closed") assert.doesNotMatch(receipts, /--disable-auto/); else assert.match(receipts, /--disable-auto/); } finally { rmSync(dir, { recursive: true, force: true }); } }); } + + +test("README describes the actual author guard without an owner restriction", () => { + const readme = readFileSync(join(root, "README.md"), "utf8"); + assert.match(readme, /workflow checks the Dependabot author, then the\s+metadata group/); + assert.doesNotMatch(readme, /Dependabot author and repository\s+owner/); +}); + +for (const mode of ["retry", "exhausted", "timeout"]) { + test(`real CLI bounded attempts discard partial output: ${mode}`, () => { + const dir = mkdtempSync(join(tmpdir(), "dependabot-bounded-")); + try { + const counter = join(dir, "count"); + const script = `const fs = require('node:fs'); const p = process.argv[1]; + const n = fs.existsSync(p) ? Number(fs.readFileSync(p, 'utf8')) + 1 : 1; + fs.writeFileSync(p, String(n)); + if (process.argv[2] === 'timeout') { process.stdout.write('partial'); process.on('SIGTERM', () => {}); setTimeout(() => { process.stdout.write('late'); process.exit(0); }, 2000); } + else if (process.argv[2] === 'retry' && n === 3) process.stdout.write('complete'); + else { process.stdout.write('partial'); process.exit(1); }`; + const run = () => githubCli(["-e", script, counter, mode], process.execPath, mode === "timeout" ? 500 : 5000); + if (mode === "retry") assert.equal(run(), "complete"); + else assert.throws(run, /failed or timed out after 3 bounded attempts/); + assert.equal(readFileSync(counter, "utf8"), "3"); + } finally { rmSync(dir, { recursive: true, force: true }); } + }); +} From 440b8304391f18e64eed47265bbfd279c79e2bf2 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 09:52:10 +0100 Subject: [PATCH 11/19] Record review proofs and baseline release gate receipts --- .agents/pm/features/pm-github-dependabot-merge.toon | 9 +++++---- .agents/pm/history/pm-github-dependabot-merge.jsonl | 4 ++++ .agents/pm/history/pm-github-gaq8.jsonl | 1 + .agents/pm/issues/pm-github-gaq8.toon | 4 +++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 611dd271..508d1c12 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,10 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T08:21:47.911Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-11-05T08:52:04.099Z" author: codex-agent acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[30]{created_at,author,text}: +comments[33]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -42,6 +40,9 @@ comments[30]{created_at,author,text}: "2026-11-05T08:18:23.452Z",codex-agent,"Implemented all four actionable review findings. githubCli bounds each process with SIGKILL after 60 seconds and at most three attempts; both bootstrap shell paths bound every read, policy download and disable call identically, discard failed output, and report an explicit potentially-still-armed failure on exhausted revocation. Workflow job limit is 30 minutes. job.workflow_ref plus the caller pull-request merge ref limits trusted-main selection, preserving remote SHA-pinned self-calls. Focused Node and native Bun each pass 85 tests; pinned pm linked runner passed 85. Shell fixtures execute real shortened timeout calls against TERM-resistant finite stalls and retry successes. No thresholds changed; dist is generated and untracked." "2026-11-05T08:19:31.969Z",codex-agent,"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: restoring githubCli from 2652cc9 causes 3 failures/0 passes; removing only timeout+SIGKILL causes 1 failure/2 passes; reverting both shell paths to unbounded gh causes 8 failures/11 passes; restoring repository-only main override causes remote-self 1 failure; removing only production --auto causes 3 success-path failures; restoring README owner claim causes 1 failure. Fully restored copy passes 85/85. Reviewed whole PR diff for these classes: every gh call in both shell blocks is now wrapped, every policy call goes through bounded githubCli, and remote external and self-pinned loaders retain their SHA. Follow-up budget review increased the job cap from the initial 30 to 60 minutes and bounds setup and metadata individually to 5 minutes; this leaves room for worst-case revocation retries instead of cutting off the final fallback." "2026-11-05T08:21:47.910Z",codex-agent,"Final focused validation: pinned pm linked Node and native Bun runners each pass 85 tests, zero failures/skips. Focused c8 measures scripts/dependabot-merge.ts at exact 100 percent statements, branches, functions and lines. Test typecheck, ESLint, docstrings (271 declarations), zero-threshold duplication and regenerated changelog check pass. Starting the unchanged full npm then Bun release gates serially with nice, a credential-free child context and isolated Git config; complete release and coverage results are pending. Commit identity range contains only the required approved bot email." + "2026-11-05T08:25:09.233Z",codex-agent,"Read-only operational refresh supersedes the earlier disabled-state snapshot: the workflow API now reports dependabot-auto-merge.yml active. This review performed no workflow enable/disable, token, secret or branch-rule changes. Live bot-only arm, foreign-commit revocation and token/server-protection acceptance remain unverified and tracked separately in pm-github-dependabot-activation. Source head c21d40e has passing CodeQL; complete release CI is running at https://github.com/unbraind/pm-github/actions/runs/41993892442 . Local npm release has passed prechecks and privacy (8845 audited objects) and is running coverage alone under nice." + "2026-11-05T08:35:31.484Z",codex-agent,"The local full npm coverage attempt was interrupted after existing closeout-workflow fixture failures and is not passing evidence. Isolated first scenario fails on both the branch and disposable pristine origin/main 650797d: real npm ci reports EUSAGE because the synthetic caller has no package-lock, despite its npm shim. The relevant workflow/test are unchanged. Recorded precise reproduction in existing follow-up pm-github-gaq8; no duplicate item created, no out-of-scope code or gate relaxation. CI release gates remain running on c21d40e. Focused 85-case Node/Bun and focused 100/100/100/100 policy coverage remain passing." + "2026-11-05T08:52:02.249Z",codex-agent,"Review handoff for source c21d40ee5ce23cd80e16db37724657cf5b3b5c46: all three unresolved thread IDs and review-body follow-up are fixed; final complete review-thread refresh found no new threads or newer CodeRabbit review. Six disposable revert proofs are recorded above; restored policy corpus is 85/85 on Node and 85/85 native Bun, with focused policy coverage at exact 100/100/100/100. Full npm run release:check PASSED on Node 26 in CI at 08:46:09 UTC, at unchanged thresholds. At the last observation within the 30-minute cap, npm on Node 22 and both bun run release:check jobs remained in progress: https://github.com/unbraind/pm-github/actions/runs/41993892442 . These three are pending, not passing evidence; whole npm job also has a later native Bun phase. Job logs are unavailable while jobs run, so no full-suite test total is invented. CodeQL passed at https://github.com/unbraind/pm-github/actions/runs/41993892315 . Local production audit, package dry run, changelog check and publication attestation all pass; strict tracker health passes with zero warnings. Local full npm attempt remains non-passing due to the independently reproduced main-baseline closeout fixture issue tracked in pm-github-gaq8. No local test/watch process remains. No PM CLI/SDK bug was established in this review. No thread replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes were performed. Releasing only my feature claim; both touched items remain open for orchestrator verification. Final receipt push requires fresh CI and will be reported as pending rather than reusing prior-head approval." files[5]: - path: .github/workflows/dependabot-auto-merge.yml scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index f50a3b3d..ab41efa9 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -39,3 +39,7 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T08:18:23.452Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/27","value":{"created_at":"2026-11-05T08:18:23.452Z","author":"codex-agent","text":"Implemented all four actionable review findings. githubCli bounds each process with SIGKILL after 60 seconds and at most three attempts; both bootstrap shell paths bound every read, policy download and disable call identically, discard failed output, and report an explicit potentially-still-armed failure on exhausted revocation. Workflow job limit is 30 minutes. job.workflow_ref plus the caller pull-request merge ref limits trusted-main selection, preserving remote SHA-pinned self-calls. Focused Node and native Bun each pass 85 tests; pinned pm linked runner passed 85. Shell fixtures execute real shortened timeout calls against TERM-resistant finite stalls and retry successes. No thresholds changed; dist is generated and untracked."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:18:23.452Z"}],"before_hash":"e2f1d11b161400c7161aaf01a5fde87e353cdeff5c67754ec1702e3b28181098","after_hash":"8e2c821e54198c39d771b4a76664200c7010c8f2c23eeef9f2bd3e98860f3c0a","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":"2ae37d1dc755d86678d390f53e7fb2c44225fd464c3c4db1f21e4edbd9371f96"} {"hash_algorithm":"sha256","ts":"2026-11-05T08:19:31.970Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/28","value":{"created_at":"2026-11-05T08:19:31.969Z","author":"codex-agent","text":"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: restoring githubCli from 2652cc9 causes 3 failures/0 passes; removing only timeout+SIGKILL causes 1 failure/2 passes; reverting both shell paths to unbounded gh causes 8 failures/11 passes; restoring repository-only main override causes remote-self 1 failure; removing only production --auto causes 3 success-path failures; restoring README owner claim causes 1 failure. Fully restored copy passes 85/85. Reviewed whole PR diff for these classes: every gh call in both shell blocks is now wrapped, every policy call goes through bounded githubCli, and remote external and self-pinned loaders retain their SHA. Follow-up budget review increased the job cap from the initial 30 to 60 minutes and bounds setup and metadata individually to 5 minutes; this leaves room for worst-case revocation retries instead of cutting off the final fallback."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:19:31.970Z"}],"before_hash":"8e2c821e54198c39d771b4a76664200c7010c8f2c23eeef9f2bd3e98860f3c0a","after_hash":"511d956d99524d1adbb80aabba0fa74a76b699b9e772a39798112a795195b6bb","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":"63817e011c52d34c3c232c4de8fe84e0734c31de9161db5930225b5b1cdb47cd"} {"hash_algorithm":"sha256","ts":"2026-11-05T08:21:47.911Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/29","value":{"created_at":"2026-11-05T08:21:47.910Z","author":"codex-agent","text":"Final focused validation: pinned pm linked Node and native Bun runners each pass 85 tests, zero failures/skips. Focused c8 measures scripts/dependabot-merge.ts at exact 100 percent statements, branches, functions and lines. Test typecheck, ESLint, docstrings (271 declarations), zero-threshold duplication and regenerated changelog check pass. Starting the unchanged full npm then Bun release gates serially with nice, a credential-free child context and isolated Git config; complete release and coverage results are pending. Commit identity range contains only the required approved bot email."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:21:47.911Z"}],"before_hash":"511d956d99524d1adbb80aabba0fa74a76b699b9e772a39798112a795195b6bb","after_hash":"63d0c084bab9c998b0dbbedb94280fd17f99436e0f26e1e78f0608ffcbd0ecea","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":"98f92bf63214a6317d879c323e9f76608be7a962ca2f31a10437bba26ed8a298"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:25:09.235Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/30","value":{"created_at":"2026-11-05T08:25:09.233Z","author":"codex-agent","text":"Read-only operational refresh supersedes the earlier disabled-state snapshot: the workflow API now reports dependabot-auto-merge.yml active. This review performed no workflow enable/disable, token, secret or branch-rule changes. Live bot-only arm, foreign-commit revocation and token/server-protection acceptance remain unverified and tracked separately in pm-github-dependabot-activation. Source head c21d40e has passing CodeQL; complete release CI is running at https://github.com/unbraind/pm-github/actions/runs/41993892442 . Local npm release has passed prechecks and privacy (8845 audited objects) and is running coverage alone under nice."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:25:09.235Z"}],"before_hash":"63d0c084bab9c998b0dbbedb94280fd17f99436e0f26e1e78f0608ffcbd0ecea","after_hash":"be6ba677822a75968ae96bc9def2dee759b55a423b00ebbe43b77d46806bb935","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":"7ccf27945f22d5c3e9fb4593122a2aa8c001cc23b09679af37fe62c7b8411613"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:35:31.484Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/31","value":{"created_at":"2026-11-05T08:35:31.484Z","author":"codex-agent","text":"The local full npm coverage attempt was interrupted after existing closeout-workflow fixture failures and is not passing evidence. Isolated first scenario fails on both the branch and disposable pristine origin/main 650797d: real npm ci reports EUSAGE because the synthetic caller has no package-lock, despite its npm shim. The relevant workflow/test are unchanged. Recorded precise reproduction in existing follow-up pm-github-gaq8; no duplicate item created, no out-of-scope code or gate relaxation. CI release gates remain running on c21d40e. Focused 85-case Node/Bun and focused 100/100/100/100 policy coverage remain passing."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:35:31.484Z"}],"before_hash":"be6ba677822a75968ae96bc9def2dee759b55a423b00ebbe43b77d46806bb935","after_hash":"e41523470924ed42993ca08c5ebdfb9e27c2ce02abf653d87402e7f508cbd657","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":"167d648509ced36c679555e090ba81c793ae92b7d9ba693247a516368453f239"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:52:02.249Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/32","value":{"created_at":"2026-11-05T08:52:02.249Z","author":"codex-agent","text":"Review handoff for source c21d40ee5ce23cd80e16db37724657cf5b3b5c46: all three unresolved thread IDs and review-body follow-up are fixed; final complete review-thread refresh found no new threads or newer CodeRabbit review. Six disposable revert proofs are recorded above; restored policy corpus is 85/85 on Node and 85/85 native Bun, with focused policy coverage at exact 100/100/100/100. Full npm run release:check PASSED on Node 26 in CI at 08:46:09 UTC, at unchanged thresholds. At the last observation within the 30-minute cap, npm on Node 22 and both bun run release:check jobs remained in progress: https://github.com/unbraind/pm-github/actions/runs/41993892442 . These three are pending, not passing evidence; whole npm job also has a later native Bun phase. Job logs are unavailable while jobs run, so no full-suite test total is invented. CodeQL passed at https://github.com/unbraind/pm-github/actions/runs/41993892315 . Local production audit, package dry run, changelog check and publication attestation all pass; strict tracker health passes with zero warnings. Local full npm attempt remains non-passing due to the independently reproduced main-baseline closeout fixture issue tracked in pm-github-gaq8. No local test/watch process remains. No PM CLI/SDK bug was established in this review. No thread replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes were performed. Releasing only my feature claim; both touched items remain open for orchestrator verification. Final receipt push requires fresh CI and will be reported as pending rather than reusing prior-head approval."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:52:02.249Z"}],"before_hash":"e41523470924ed42993ca08c5ebdfb9e27c2ce02abf653d87402e7f508cbd657","after_hash":"db1e731c58de3368508fb501415b7858306ce6579bd74f9b760a2487fa0922ff","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":"1b964957e323ec01f4d56d23030e53b709cffc0485e767f0e1363bd9d434ea55"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:52:04.099Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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-05T08:52:04.099Z"}],"before_hash":"db1e731c58de3368508fb501415b7858306ce6579bd74f9b760a2487fa0922ff","after_hash":"9fdd8c2d87c64509b719a4ae21e6513962a46f67e52b74b50cd68a7533f93462","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":"920353cad45c828ec93c110ca2319a762a1bc4cff6107fb51e76212677dba612"} diff --git a/.agents/pm/history/pm-github-gaq8.jsonl b/.agents/pm/history/pm-github-gaq8.jsonl index 5062b748..569ff3d2 100644 --- a/.agents/pm/history/pm-github-gaq8.jsonl +++ b/.agents/pm/history/pm-github-gaq8.jsonl @@ -1 +1,2 @@ {"hash_algorithm":"sha256","ts":"2026-11-03T01:36:20.521Z","author":"pi-ollama","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.3:cloud","agent_model_source":"environment","agent_instance":"fffd5142cf13337359aa8b1f","agent_provenance":{"model":{"value":"glm-5.3:cloud","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-gaq8"},{"op":"add","path":"/metadata/title","value":"closeout-workflow.test.ts fails wholesale on the local shared host while CI stays green on main"},{"op":"add","path":"/metadata/description","value":"Running the closeout workflow suite on the package worktree fails nearly every test on pristine main (assertions receive git branch-switch output and a pm 'Command failed' receipt instead of expected diagnostics), while the CI workflow on the same main commit is green. The suite also exceeds 4 minutes locally and hits local timeouts. Root-cause the host-local incompatibility (host git version prints advice output the fixtures do not expect) or make the fixtures version-tolerant; out of scope for the release-tag normalization work."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["regression","test"]},{"op":"add","path":"/metadata/created_at","value":"2026-11-03T01:36:20.521Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-11-03T01:36:20.521Z"},{"op":"add","path":"/metadata/author","value":"pi-ollama"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"94886e60dad04ae37f0050f2f0582027306c31074b7fb23634b8a012b21c389c","item_hash_version":3,"message":"","event_class":"substantive","record_hash_version":1,"record_hash":"9929569a61b7b9df20da0249aa2ab88e008c563faab6fbbf8669388ac95f3f6a"} +{"hash_algorithm":"sha256","ts":"2026-11-05T08:35:28.981Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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-05T08:35:28.981Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-11-05T08:35:28.981Z","author":"codex-agent","text":"PR 221 review-round baseline confirmation: with Node 26.13.0/npm 11.20.0, public npm config and isolated Git config, the single schedule-without-repository-payload scenario fails on both the branch and a disposable origin/main snapshot (650797d). Exact command: nice -n 10 node --test --test-concurrency=2 --test-name-pattern=\"schedule without repository payload\" test/closeout-workflow.test.ts . Failure is npm EUSAGE: npm ci requires a package-lock in the synthetic caller, despite the fixture npm shim. The closeout workflow and test are byte-identical between branch and main. This is an environment-specific baseline fixture failure, not attributed to the Dependabot policy changes. No fixture or threshold was weakened. Full release evidence is being collected separately in CI on c21d40e."}]}],"before_hash":"94886e60dad04ae37f0050f2f0582027306c31074b7fb23634b8a012b21c389c","after_hash":"de918bd8e1ccf4e2f5c9cba799eaa7312d6dc5f58d4b90b2bb3d498b75aac931","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":"0b4307510d3e4f871ecbefcda313bfd0f6bfda4513b307aea012f5e5179e8625"} diff --git a/.agents/pm/issues/pm-github-gaq8.toon b/.agents/pm/issues/pm-github-gaq8.toon index 3401cbb4..62491bc5 100644 --- a/.agents/pm/issues/pm-github-gaq8.toon +++ b/.agents/pm/issues/pm-github-gaq8.toon @@ -6,6 +6,8 @@ status: open priority: 2 tags[2]: regression,test created_at: "2026-11-03T01:36:20.521Z" -updated_at: "2026-11-03T01:36:20.521Z" +updated_at: "2026-11-05T08:35:28.981Z" author: pi-ollama +comments[1]{created_at,author,text}: + "2026-11-05T08:35:28.981Z",codex-agent,"PR 221 review-round baseline confirmation: with Node 26.13.0/npm 11.20.0, public npm config and isolated Git config, the single schedule-without-repository-payload scenario fails on both the branch and a disposable origin/main snapshot (650797d). Exact command: nice -n 10 node --test --test-concurrency=2 --test-name-pattern=\"schedule without repository payload\" test/closeout-workflow.test.ts . Failure is npm EUSAGE: npm ci requires a package-lock in the synthetic caller, despite the fixture npm shim. The closeout workflow and test are byte-identical between branch and main. This is an environment-specific baseline fixture failure, not attributed to the Dependabot policy changes. No fixture or threshold was weakened. Full release evidence is being collected separately in CI on c21d40e." body: "" From 48abcd82b0c0e18e2b2409a631030e984d445af8 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 11:38:38 +0100 Subject: [PATCH 12/19] Trust Dependabot workflow source and audit suppressed updates --- .../pm-github-dependabot-activation.toon | 18 +- .../features/pm-github-dependabot-merge.toon | 17 +- .../pm-github-dependabot-activation.jsonl | 7 + .../history/pm-github-dependabot-merge.jsonl | 8 + .github/workflows/dependabot-auto-merge.yml | 21 ++- .../workflows/pm-github-dependabot-merge.yml | 32 ++-- README.md | 45 ++++- scripts/dependabot-merge.ts | 35 +++- test/dependabot-merge.test.ts | 177 ++++++++++++++++-- 9 files changed, 306 insertions(+), 54 deletions(-) diff --git a/.agents/pm/chores/pm-github-dependabot-activation.toon b/.agents/pm/chores/pm-github-dependabot-activation.toon index 16db214b..07c26b3c 100644 --- a/.agents/pm/chores/pm-github-dependabot-activation.toon +++ b/.agents/pm/chores/pm-github-dependabot-activation.toon @@ -1,15 +1,25 @@ id: pm-github-dependabot-activation -title: Activate the verified Dependabot merge caller after review and token provisioning -description: Read-only Actions API inspection found the existing dependabot-auto-merge.yml workflow is disabled_manually. Source migration alone does not activate it. Keep activation separate from the reusable workflow implementation and coordinate with companion pm-cli-website-wis6. +title: Verify live Dependabot merge safety and coordinate fleet activation +description: "Read-only Actions API inspection now reports dependabot-auto-merge.yml active, superseding the earlier disabled snapshot. PR #221 delivers the reusable provenance policy, trusted default-branch caller and independent scheduled audit source. Workflow state alone does not prove safe runtime activation. Keep repository token, event-policy, protected-branch and live-acceptance verification and fleet caller adoption separate from this implementation. Coordinate with companion pm-cli-website-wis6." type: Chore status: blocked priority: 2 tags[4]: ci,dependabot,follow-up,security created_at: "2026-11-05T00:52:51.053Z" -updated_at: "2026-11-05T00:52:51.053Z" +updated_at: "2026-11-05T10:34:23.888Z" +assignee: codex-agent +claim_principal: codex-agent author: codex-agent -acceptance_criteria: "After reviewed delivery and release, verify the selected caller pins a release containing the policy or uses the reviewed local path.; Provision a repository-scoped merge token as both Actions and Dependabot secrets when needed, and verify server-enforced protected-branch checks and review requirements.; With owner authorization enable the previously disabled workflow and record live bot-only arm plus foreign-commit revocation acceptance without permitting an unintended merge." +acceptance_criteria: "After reviewed delivery and release, verify each selected caller pins a release containing the policy or uses the reviewed trusted local path.; Verify the active caller has a repository-scoped merge token as Actions and Dependabot secrets when needed, required Actions event policy permits its trusted trigger, and server-enforced protected-branch checks and review requirements prevent unreviewed merges.; With owner authorization record live bot-only arm, foreign-commit revocation and independent scheduled audit acceptance without permitting an unintended merge, and coordinate remaining fleet adoption separately." blocked_by: pm-github-dependabot-merge dependencies[1]{id,kind,created_at,author,source_kind,author_source}: pm-github-dependabot-merge,blocked_by,"2026-11-05T00:52:51.053Z",codex-agent,"cli:create:blocked_by",detected +comments[1]{created_at,author,text}: + "2026-11-05T10:29:37.084Z",codex-agent,"PRRT_kwDOTbN4pM6z_gYw verified: Actions API currently reports the existing workflow active. Updated title, description and acceptance criteria to remove the obsolete enable request. Item remains blocked on reviewed implementation and runtime token/protection/live acceptance; active workflow state is not proof of fleet activation. No live workflow or secret settings changed." +files[1]{path,scope}: + .github/workflows/dependabot-auto-merge.yml,project +tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}: + node --test --test-concurrency=2 --test-name-pattern=activation test/dependabot-merge.test.ts,project,codex-agent,"2026-11-05T10:33:59.799Z",local_mutation,feat/reusable-dependabot-merge +docs[1]{path,scope}: + README.md,project body: "" diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 508d1c12..df4786f5 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,10 +6,12 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T08:52:04.099Z" +updated_at: "2026-11-05T10:38:33.053Z" +assignee: codex-agent +claim_principal: codex-agent author: codex-agent -acceptance_criteria: "Only pull_request events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates." -comments[33]{created_at,author,text}: +acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." +comments[38]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -43,7 +45,14 @@ comments[33]{created_at,author,text}: "2026-11-05T08:25:09.233Z",codex-agent,"Read-only operational refresh supersedes the earlier disabled-state snapshot: the workflow API now reports dependabot-auto-merge.yml active. This review performed no workflow enable/disable, token, secret or branch-rule changes. Live bot-only arm, foreign-commit revocation and token/server-protection acceptance remain unverified and tracked separately in pm-github-dependabot-activation. Source head c21d40e has passing CodeQL; complete release CI is running at https://github.com/unbraind/pm-github/actions/runs/41993892442 . Local npm release has passed prechecks and privacy (8845 audited objects) and is running coverage alone under nice." "2026-11-05T08:35:31.484Z",codex-agent,"The local full npm coverage attempt was interrupted after existing closeout-workflow fixture failures and is not passing evidence. Isolated first scenario fails on both the branch and disposable pristine origin/main 650797d: real npm ci reports EUSAGE because the synthetic caller has no package-lock, despite its npm shim. The relevant workflow/test are unchanged. Recorded precise reproduction in existing follow-up pm-github-gaq8; no duplicate item created, no out-of-scope code or gate relaxation. CI release gates remain running on c21d40e. Focused 85-case Node/Bun and focused 100/100/100/100 policy coverage remain passing." "2026-11-05T08:52:02.249Z",codex-agent,"Review handoff for source c21d40ee5ce23cd80e16db37724657cf5b3b5c46: all three unresolved thread IDs and review-body follow-up are fixed; final complete review-thread refresh found no new threads or newer CodeRabbit review. Six disposable revert proofs are recorded above; restored policy corpus is 85/85 on Node and 85/85 native Bun, with focused policy coverage at exact 100/100/100/100. Full npm run release:check PASSED on Node 26 in CI at 08:46:09 UTC, at unchanged thresholds. At the last observation within the 30-minute cap, npm on Node 22 and both bun run release:check jobs remained in progress: https://github.com/unbraind/pm-github/actions/runs/41993892442 . These three are pending, not passing evidence; whole npm job also has a later native Bun phase. Job logs are unavailable while jobs run, so no full-suite test total is invented. CodeQL passed at https://github.com/unbraind/pm-github/actions/runs/41993892315 . Local production audit, package dry run, changelog check and publication attestation all pass; strict tracker health passes with zero warnings. Local full npm attempt remains non-passing due to the independently reproduced main-baseline closeout fixture issue tracked in pm-github-gaq8. No local test/watch process remains. No PM CLI/SDK bug was established in this review. No thread replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes were performed. Releasing only my feature claim; both touched items remain open for orchestrator verification. Final receipt push requires fresh CI and will be reported as pending rather than reusing prior-head approval." -files[5]: + "2026-11-05T10:25:26.459Z",codex-agent,"Round 2 at 440b830: remote head pulled, origin/main already contained. Retrieved every review thread and all comments; pagination is complete. One unresolved thread PRRT_kwDOTbN4pM6z_gYw concerns stale activation text. Latest review has two outside-diff concerns: GITHUB_TOKEN-generated pushes can park pull_request revocation, and PR-local workflow source can access the merge token before trusted policy loading. Verifying official event semantics, then testing a trusted-base event path with no PR checkout. No replies, resolutions, live configuration changes or merges." + "2026-11-05T10:30:00.489Z",codex-agent,"Both outside-diff findings are valid. Caller now uses pull_request_target to source all YAML from the trusted default branch and only loads policy from job.workflow_sha. Added independent five-minute/manual audit-only invocation because GITHUB_TOKEN-generated updates can suppress or park PR runs. Audit shares runDependabotMerge provenance decisions, preserves valid armed requests, never arms, paginates all open requests and commits, continues remaining requests after denied revocation, and fails with sanitized errors. README carries full migration template, event-policy requirement and eventual-revocation/server-protection limitation. Official sources: https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target and https://docs.github.com/en/actions/concepts/security/github_token . No live configuration changes." + "2026-11-05T10:33:12.033Z",codex-agent,"Revert proofs in a disposable non-git copy with unchanged tests: restoring activation item from 440b830 fails 1 test; replacing only auditDependabotMerges with a no-op fails the workflow-generated-push scenario (1 test); reverting caller trigger/guard to pull_request fails the trusted-caller contract (1 test); restoring only the production arm-event comparison to pull_request fails 2 behavioral cases (valid trusted open and unsupported untrusted event). Restoring fixed files passes all 105 tests. Node and native Bun focused suites each pass 105, zero failures/skips; c8 policy receipts are exact 153/153 lines and statements, 111/111 branches, 6/6 functions. Reviewed the entire PR implementation, caller/template, loader, all commit checks and tracker descriptions for the same event/trust/state defects. No other live workflow changes or foreign branch scripts are introduced. Lint, test typecheck, docstrings (272 declarations), unchanged zero-threshold duplication and strict tracker health pass." + "2026-11-05T10:34:09.288Z",codex-agent,"Pinned pm test --run --progress passed both linked commands: 105 Node tests and 105 native Bun tests, zero failed/skipped. Test-result tracking is disabled by existing project policy, so outcomes are recorded here without changing settings. Full npm run release:check is now running serially under Node 26 with isolated HOME/Git identity, public npm configuration and inherited credentials/session context excluded. Full Bun release gate follows; unchanged 100/100/100/100 thresholds and two-worker bound are retained." + "2026-11-05T10:38:33.046Z",codex-agent,"All source changes and regression proofs are ready for review. Full npm gate passed prechecks and privacy (8872 objects) and is running complete coverage with no failures yet. Preparing a source push so exact-head CI can verify npm and Bun gates concurrently with the single local gate; no incomplete gate is counted as passing. Tracker claims remain held during validation. The independent schedule is an eventual revocation path, not an atomic future-head merge guarantee; server protections and live activation remain separate." +files[6]: + - path: .agents/pm/chores/pm-github-dependabot-activation.toon + scope: project - path: .github/workflows/dependabot-auto-merge.yml scope: project - path: .github/workflows/pm-github-dependabot-merge.yml diff --git a/.agents/pm/history/pm-github-dependabot-activation.jsonl b/.agents/pm/history/pm-github-dependabot-activation.jsonl index 16150fed..aab3461a 100644 --- a/.agents/pm/history/pm-github-dependabot-activation.jsonl +++ b/.agents/pm/history/pm-github-dependabot-activation.jsonl @@ -1 +1,8 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T00:52:51.053Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"0253287c7b07e97397888a5e","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-dependabot-activation"},{"op":"add","path":"/metadata/title","value":"Activate the verified Dependabot merge caller after review and token provisioning"},{"op":"add","path":"/metadata/description","value":"Read-only Actions API inspection found the existing dependabot-auto-merge.yml workflow is disabled_manually. Source migration alone does not activate it. Keep activation separate from the reusable workflow implementation and coordinate with companion pm-cli-website-wis6."},{"op":"add","path":"/metadata/type","value":"Chore"},{"op":"add","path":"/metadata/status","value":"blocked"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["ci","dependabot","follow-up","security"]},{"op":"add","path":"/metadata/created_at","value":"2026-11-05T00:52:51.053Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-11-05T00:52:51.053Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"After reviewed delivery and release, verify the selected caller pins a release containing the policy or uses the reviewed local path.; Provision a repository-scoped merge token as both Actions and Dependabot secrets when needed, and verify server-enforced protected-branch checks and review requirements.; With owner authorization enable the previously disabled workflow and record live bot-only arm plus foreign-commit revocation acceptance without permitting an unintended merge."},{"op":"add","path":"/metadata/blocked_by","value":"pm-github-dependabot-merge"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-github-dependabot-merge","kind":"blocked_by","created_at":"2026-11-05T00:52:51.053Z","author":"codex-agent","source_kind":"cli:create:blocked_by","author_source":"detected"}]}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"5f1e4235e95edabb65ffd2d44d511b21e074255ab28656811e7608d7b6e13b7c","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":"3b1bc39b31fdb456d21bcd0e24917e73c8e19a19f714eee50ee3460d33da7d80"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:25:29.481Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:25:29.481Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"5f1e4235e95edabb65ffd2d44d511b21e074255ab28656811e7608d7b6e13b7c","after_hash":"801e5f79966a974b4b7da44e61823ef3ff9395f0b7c5fec0287131cdfceff78b","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":"a59f3fd33eca21adb4c63e50e53a9e314b478309bebd4d792613052a5d543b7c"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:29:33.743Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/acceptance_criteria","value":"After reviewed delivery and release, verify each selected caller pins a release containing the policy or uses the reviewed trusted local path.; Verify the active caller has a repository-scoped merge token as Actions and Dependabot secrets when needed, required Actions event policy permits its trusted trigger, and server-enforced protected-branch checks and review requirements prevent unreviewed merges.; With owner authorization record live bot-only arm, foreign-commit revocation and independent scheduled audit acceptance without permitting an unintended merge; coordinate remaining fleet adoption separately."},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:29:33.743Z"},{"op":"replace","path":"/metadata/description","value":"Read-only Actions API inspection now reports dependabot-auto-merge.yml active, superseding the earlier disabled snapshot. PR #221 delivers the reusable provenance policy, trusted default-branch caller and independent scheduled audit source. Workflow state alone does not prove safe runtime activation. Keep repository token, event-policy, protected-branch and live-acceptance verification and fleet caller adoption separate from this implementation."},{"op":"replace","path":"/metadata/title","value":"Verify live Dependabot merge safety and coordinate fleet activation"}],"before_hash":"801e5f79966a974b4b7da44e61823ef3ff9395f0b7c5fec0287131cdfceff78b","after_hash":"66d6c67be52e3ca09ba83d026eaf6b3c13246dea6d2aea81795950c1b609a925","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":"231e1b9017008f126329f313582f9519fe48419c1bc941f4d0e33379ff9b4400"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:29:37.084Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:29:37.084Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-11-05T10:29:37.084Z","author":"codex-agent","text":"PRRT_kwDOTbN4pM6z_gYw verified: Actions API currently reports the existing workflow active. Updated title, description and acceptance criteria to remove the obsolete enable request. Item remains blocked on reviewed implementation and runtime token/protection/live acceptance; active workflow state is not proof of fleet activation. No live workflow or secret settings changed."}]}],"before_hash":"66d6c67be52e3ca09ba83d026eaf6b3c13246dea6d2aea81795950c1b609a925","after_hash":"9cdf3c1f6bbfe646d780d2d6c1a8d39905785054d8120b6182540aa6fbab9455","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":"c378c35c1a39110d0315db1cc2b124e1d1939f7f8ebb47f6b83e7c2551363f61"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:32:40.009Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:32:40.009Z"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/dependabot-auto-merge.yml","scope":"project"}]}],"before_hash":"9cdf3c1f6bbfe646d780d2d6c1a8d39905785054d8120b6182540aa6fbab9455","after_hash":"641e6a46e83ccc9cb31804fbeeab70df925063d49e97a3ed8492737bb9c7aae2","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":"8e2b6a0d0a4fc5fd32bf1233f8a72cf1de314ec8d8a3199b235cb279b285e756"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:32:43.391Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:32:43.391Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"README.md","scope":"project"}]}],"before_hash":"641e6a46e83ccc9cb31804fbeeab70df925063d49e97a3ed8492737bb9c7aae2","after_hash":"079de8485b8f4374501d88249a2ce8349220c14587d5f3bb0bfaa56cde9eb19f","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":"96a0a85b9c83e515e2cebc5cd8fb1e15995bb51c0dd7030350b7c7d0fccbf2e4"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:34:00.010Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:34:00.010Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --test-concurrency=2 --test-name-pattern=activation test/dependabot-merge.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-11-05T10:33:59.799Z","source_kind":"local_mutation","source_ref":"feat/reusable-dependabot-merge"}}]}],"before_hash":"079de8485b8f4374501d88249a2ce8349220c14587d5f3bb0bfaa56cde9eb19f","after_hash":"3b2f10527989ae702d2bcafc98c243d44996c4b6439007f1fc0606882cecf1db","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":"1555b0ac5882e4d3b7acbe042f8cc602b0fda63a3b2c1cf3970f4595fa3599ca"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:34:23.888Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/acceptance_criteria","value":"After reviewed delivery and release, verify each selected caller pins a release containing the policy or uses the reviewed trusted local path.; Verify the active caller has a repository-scoped merge token as Actions and Dependabot secrets when needed, required Actions event policy permits its trusted trigger, and server-enforced protected-branch checks and review requirements prevent unreviewed merges.; With owner authorization record live bot-only arm, foreign-commit revocation and independent scheduled audit acceptance without permitting an unintended merge, and coordinate remaining fleet adoption separately."},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:34:23.888Z"},{"op":"replace","path":"/metadata/description","value":"Read-only Actions API inspection now reports dependabot-auto-merge.yml active, superseding the earlier disabled snapshot. PR #221 delivers the reusable provenance policy, trusted default-branch caller and independent scheduled audit source. Workflow state alone does not prove safe runtime activation. Keep repository token, event-policy, protected-branch and live-acceptance verification and fleet caller adoption separate from this implementation. Coordinate with companion pm-cli-website-wis6."}],"before_hash":"3b2f10527989ae702d2bcafc98c243d44996c4b6439007f1fc0606882cecf1db","after_hash":"9c47de40b0527c135a0f69fa42f3732c64f016ef4f3e50998cf7b9371f16985b","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":"34b620385da3c5010853b4ac607c99833adf614ac6f4d551758eea8c8c614957"} diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index ab41efa9..fc7db6fb 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -43,3 +43,11 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T08:35:31.484Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/31","value":{"created_at":"2026-11-05T08:35:31.484Z","author":"codex-agent","text":"The local full npm coverage attempt was interrupted after existing closeout-workflow fixture failures and is not passing evidence. Isolated first scenario fails on both the branch and disposable pristine origin/main 650797d: real npm ci reports EUSAGE because the synthetic caller has no package-lock, despite its npm shim. The relevant workflow/test are unchanged. Recorded precise reproduction in existing follow-up pm-github-gaq8; no duplicate item created, no out-of-scope code or gate relaxation. CI release gates remain running on c21d40e. Focused 85-case Node/Bun and focused 100/100/100/100 policy coverage remain passing."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:35:31.484Z"}],"before_hash":"be6ba677822a75968ae96bc9def2dee759b55a423b00ebbe43b77d46806bb935","after_hash":"e41523470924ed42993ca08c5ebdfb9e27c2ce02abf653d87402e7f508cbd657","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":"167d648509ced36c679555e090ba81c793ae92b7d9ba693247a516368453f239"} {"hash_algorithm":"sha256","ts":"2026-11-05T08:52:02.249Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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/32","value":{"created_at":"2026-11-05T08:52:02.249Z","author":"codex-agent","text":"Review handoff for source c21d40ee5ce23cd80e16db37724657cf5b3b5c46: all three unresolved thread IDs and review-body follow-up are fixed; final complete review-thread refresh found no new threads or newer CodeRabbit review. Six disposable revert proofs are recorded above; restored policy corpus is 85/85 on Node and 85/85 native Bun, with focused policy coverage at exact 100/100/100/100. Full npm run release:check PASSED on Node 26 in CI at 08:46:09 UTC, at unchanged thresholds. At the last observation within the 30-minute cap, npm on Node 22 and both bun run release:check jobs remained in progress: https://github.com/unbraind/pm-github/actions/runs/41993892442 . These three are pending, not passing evidence; whole npm job also has a later native Bun phase. Job logs are unavailable while jobs run, so no full-suite test total is invented. CodeQL passed at https://github.com/unbraind/pm-github/actions/runs/41993892315 . Local production audit, package dry run, changelog check and publication attestation all pass; strict tracker health passes with zero warnings. Local full npm attempt remains non-passing due to the independently reproduced main-baseline closeout fixture issue tracked in pm-github-gaq8. No local test/watch process remains. No PM CLI/SDK bug was established in this review. No thread replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes were performed. Releasing only my feature claim; both touched items remain open for orchestrator verification. Final receipt push requires fresh CI and will be reported as pending rather than reusing prior-head approval."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T08:52:02.249Z"}],"before_hash":"e41523470924ed42993ca08c5ebdfb9e27c2ce02abf653d87402e7f508cbd657","after_hash":"db1e731c58de3368508fb501415b7858306ce6579bd74f9b760a2487fa0922ff","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":"1b964957e323ec01f4d56d23030e53b709cffc0485e767f0e1363bd9d434ea55"} {"hash_algorithm":"sha256","ts":"2026-11-05T08:52:04.099Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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-05T08:52:04.099Z"}],"before_hash":"db1e731c58de3368508fb501415b7858306ce6579bd74f9b760a2487fa0922ff","after_hash":"9fdd8c2d87c64509b719a4ae21e6513962a46f67e52b74b50cd68a7533f93462","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":"920353cad45c828ec93c110ca2319a762a1bc4cff6107fb51e76212677dba612"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:25:20.626Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:25:20.626Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"9fdd8c2d87c64509b719a4ae21e6513962a46f67e52b74b50cd68a7533f93462","after_hash":"c4de17e2c5b4f19efe8ab26ed27ea460ce1d1b1848b5de6fe7db6a09404d7062","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":"bae21c5f77cd22a959bfc704a995ff731df0ffb88c760cbe90a49e0459730e15"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:25:26.460Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/33","value":{"created_at":"2026-11-05T10:25:26.459Z","author":"codex-agent","text":"Round 2 at 440b830: remote head pulled, origin/main already contained. Retrieved every review thread and all comments; pagination is complete. One unresolved thread PRRT_kwDOTbN4pM6z_gYw concerns stale activation text. Latest review has two outside-diff concerns: GITHUB_TOKEN-generated pushes can park pull_request revocation, and PR-local workflow source can access the merge token before trusted policy loading. Verifying official event semantics, then testing a trusted-base event path with no PR checkout. No replies, resolutions, live configuration changes or merges."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:25:26.460Z"}],"before_hash":"c4de17e2c5b4f19efe8ab26ed27ea460ce1d1b1848b5de6fe7db6a09404d7062","after_hash":"cefb0fb43975941dbd3644f044d2f5da4bdd60d1d1cbb9d4eba190b0d97deabc","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":"84fdee743673312da36ef9159a72c678c426b853f65da4c3a5a1097275953d0d"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:29:58.136Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/acceptance_criteria","value":"Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming."},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:29:58.136Z"}],"before_hash":"cefb0fb43975941dbd3644f044d2f5da4bdd60d1d1cbb9d4eba190b0d97deabc","after_hash":"6beb9312a49e9377db31dda119df4231db755e985ae20669ef2c32083b59890b","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":"65bf4ed253808b939f576be34d7b7e25941086cc458cee12b60b69b47dc993af"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:30:00.492Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/34","value":{"created_at":"2026-11-05T10:30:00.489Z","author":"codex-agent","text":"Both outside-diff findings are valid. Caller now uses pull_request_target to source all YAML from the trusted default branch and only loads policy from job.workflow_sha. Added independent five-minute/manual audit-only invocation because GITHUB_TOKEN-generated updates can suppress or park PR runs. Audit shares runDependabotMerge provenance decisions, preserves valid armed requests, never arms, paginates all open requests and commits, continues remaining requests after denied revocation, and fails with sanitized errors. README carries full migration template, event-policy requirement and eventual-revocation/server-protection limitation. Official sources: https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target and https://docs.github.com/en/actions/concepts/security/github_token . No live configuration changes."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:30:00.492Z"}],"before_hash":"6beb9312a49e9377db31dda119df4231db755e985ae20669ef2c32083b59890b","after_hash":"4955a5e262a38ce22eb8840202ade23e5556f92cd62c9cd7337e4ee99f5cd630","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":"e0f0f6bbaf957d4692e4ee1b7524220da82c783c7224605737693589c9e60d6b"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:32:35.679Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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":"scripts/dependabot-merge.ts"},{"op":"add","path":"/metadata/files/3/note","value":"Regenerated unreleased security entry"},{"op":"replace","path":"/metadata/files/3/path","value":"CHANGELOG.md"},{"op":"remove","path":"/metadata/files/2/note"},{"op":"replace","path":"/metadata/files/2/path","value":".github/workflows/pm-github-dependabot-merge.yml"},{"op":"replace","path":"/metadata/files/1/path","value":".github/workflows/dependabot-auto-merge.yml"},{"op":"replace","path":"/metadata/files/0/path","value":".agents/pm/chores/pm-github-dependabot-activation.toon"},{"op":"add","path":"/metadata/files/5","value":{"path":"test/dependabot-merge.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:32:35.679Z"}],"before_hash":"4955a5e262a38ce22eb8840202ade23e5556f92cd62c9cd7337e4ee99f5cd630","after_hash":"09ace34053b8f6320a3edc2adf48d9646a562bca0a2c5c444f6470f5754a6c59","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":"c21bc26a3038f794ba94c3a0ba0f33f5021fb676f0cb244052ac56b6ca761cb8"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:33:12.036Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/35","value":{"created_at":"2026-11-05T10:33:12.033Z","author":"codex-agent","text":"Revert proofs in a disposable non-git copy with unchanged tests: restoring activation item from 440b830 fails 1 test; replacing only auditDependabotMerges with a no-op fails the workflow-generated-push scenario (1 test); reverting caller trigger/guard to pull_request fails the trusted-caller contract (1 test); restoring only the production arm-event comparison to pull_request fails 2 behavioral cases (valid trusted open and unsupported untrusted event). Restoring fixed files passes all 105 tests. Node and native Bun focused suites each pass 105, zero failures/skips; c8 policy receipts are exact 153/153 lines and statements, 111/111 branches, 6/6 functions. Reviewed the entire PR implementation, caller/template, loader, all commit checks and tracker descriptions for the same event/trust/state defects. No other live workflow changes or foreign branch scripts are introduced. Lint, test typecheck, docstrings (272 declarations), unchanged zero-threshold duplication and strict tracker health pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:33:12.036Z"}],"before_hash":"09ace34053b8f6320a3edc2adf48d9646a562bca0a2c5c444f6470f5754a6c59","after_hash":"72e379f40e14f08ed079eefc57413050f862ec6f797ffe481c51c491770fc211","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":"5e093e98915bf3a7b84980a0baa56378f9572020cfe85c269fd07f3db721d369"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:34:09.289Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/36","value":{"created_at":"2026-11-05T10:34:09.288Z","author":"codex-agent","text":"Pinned pm test --run --progress passed both linked commands: 105 Node tests and 105 native Bun tests, zero failed/skipped. Test-result tracking is disabled by existing project policy, so outcomes are recorded here without changing settings. Full npm run release:check is now running serially under Node 26 with isolated HOME/Git identity, public npm configuration and inherited credentials/session context excluded. Full Bun release gate follows; unchanged 100/100/100/100 thresholds and two-worker bound are retained."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:34:09.289Z"}],"before_hash":"72e379f40e14f08ed079eefc57413050f862ec6f797ffe481c51c491770fc211","after_hash":"608db69f9c57d9775ed0ea3a9ca8f145292ab40fa686aa744038c15094461703","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":"9502a95ca5b8782f7dbe06fd803e7ef52467ac55db854e6c098933c854917607"} +{"hash_algorithm":"sha256","ts":"2026-11-05T10:38:33.053Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/37","value":{"created_at":"2026-11-05T10:38:33.046Z","author":"codex-agent","text":"All source changes and regression proofs are ready for review. Full npm gate passed prechecks and privacy (8872 objects) and is running complete coverage with no failures yet. Preparing a source push so exact-head CI can verify npm and Bun gates concurrently with the single local gate; no incomplete gate is counted as passing. Tracker claims remain held during validation. The independent schedule is an eventual revocation path, not an atomic future-head merge guarantee; server protections and live activation remain separate."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:38:33.053Z"}],"before_hash":"608db69f9c57d9775ed0ea3a9ca8f145292ab40fa686aa744038c15094461703","after_hash":"200c1f6de8475b1dced688c31dd61bd4c4ce046da07cb246ddac3b10335a6e04","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":"45cd004cf46a43e0b3158d681755f665e122504ed910a4f4d4123d0113779cb7"} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 7f8c686d..ac7e52eb 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -1,17 +1,34 @@ name: dependabot-auto-merge on: - pull_request: + # Trusted default-branch workflow; never execute PR-local workflow code. + pull_request_target: types: [opened, reopened, synchronize, ready_for_review, edited] + # Independent of GITHUB_TOKEN-generated synchronize runs awaiting approval. + schedule: + - cron: "3-58/5 * * * *" + workflow_dispatch: + permissions: {} jobs: verified-auto-merge: - if: github.event.pull_request.user.login == 'dependabot[bot]' + if: github.event_name == 'pull_request_target' && github.event.pull_request.user.login == 'dependabot[bot]' + permissions: + contents: write + pull-requests: write + uses: ./.github/workflows/pm-github-dependabot-merge.yml + secrets: + merge_token: ${{ secrets.DEPENDABOT_MERGE_TOKEN }} + + audit-armed-requests: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' permissions: contents: write pull-requests: write uses: ./.github/workflows/pm-github-dependabot-merge.yml + with: + audit-only: true secrets: merge_token: ${{ secrets.DEPENDABOT_MERGE_TOKEN }} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml index 731d71d6..484cbec7 100644 --- a/.github/workflows/pm-github-dependabot-merge.yml +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -3,6 +3,11 @@ name: Verified Dependabot squash auto-merge on: workflow_call: inputs: + audit-only: + description: "Inspect all armed Dependabot requests without ever arming; for trusted scheduled callers" + type: boolean + required: false + default: false extra-allowed-groups: description: "Additional exact Dependabot groups, comma or whitespace separated" type: string @@ -10,13 +15,13 @@ on: default: "" secrets: merge_token: - description: "Optional write-capable token; also configure as a Dependabot secret for pull_request runs" + description: "Optional write-capable token; also configure as a Dependabot secret when required by event policy" required: false permissions: {} concurrency: - group: pm-github-dependabot-merge-${{ github.repository }}-${{ github.event.pull_request.number }} + group: pm-github-dependabot-merge-${{ github.repository }}-${{ inputs.audit-only && 'audit' || github.event.pull_request.number }} cancel-in-progress: false jobs: @@ -36,11 +41,13 @@ jobs: TRIGGERING_ACTOR: ${{ github.triggering_actor }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} EVENT_HEAD: ${{ github.event.pull_request.head.sha }} + AUDIT_ONLY: ${{ inputs.audit-only }} EXTRA_GROUPS: ${{ inputs.extra-allowed-groups }} steps: # Revoke before even setting up Node or downloading policy. If the state # read fails, attempt revocation anyway; inability to revoke must be visible. - name: Revoke previous auto-merge before tooling + if: ${{ !inputs.audit-only }} shell: bash run: | set -euo pipefail @@ -75,8 +82,6 @@ jobs: id: policy env: WORKFLOW_SHA: ${{ job.workflow_sha }} - WORKFLOW_REF: ${{ job.workflow_ref }} - CALLER_REF: ${{ github.ref }} shell: bash run: | set -euo pipefail @@ -93,15 +98,12 @@ jobs: return 1 } policy_sha="${WORKFLOW_SHA}" - # A local-path pull_request call resolves workflow_sha from the PR. - # Its executable policy must instead come from the trusted default branch. - if [[ "${REPOSITORY}" == unbraind/pm-github && "${EVENT_NAME}" == pull_request && - "${WORKFLOW_REF}" == "unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@${CALLER_REF}" && - "${CALLER_REF}" == "refs/pull/${PR_NUMBER}/merge" ]]; then - policy_sha="$(bounded_gh api repos/unbraind/pm-github/git/ref/heads/main --jq '.object.sha')" || policy_sha="" - fi if [[ ! "${policy_sha}" =~ ^[a-f0-9]{40}$ ]] || ! bounded_gh api "repos/unbraind/pm-github/contents/scripts/dependabot-merge.ts?ref=${policy_sha}" \ -H 'Accept: application/vnd.github.raw+json' > "${RUNNER_TEMP}/dependabot-merge.ts"; then + if [[ "${AUDIT_ONLY}" == true ]]; then + echo '::error::Cannot load trusted audit policy; requests may still be armed. Retry immediately.' + exit 1 + fi armed="$(bounded_gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq 'if .state == "closed" then false elif .state == "open" and has("auto_merge") then .auto_merge != null else error("Unverifiable PR state") end')" || armed=true if [[ "${armed}" != false ]] && ! bounded_gh pr merge "${PR_NUMBER}" --repo "${REPOSITORY}" --disable-auto; then echo '::error::Cannot revoke auto-merge after policy load failure and 3 bounded attempts; request may still be armed. Check token write permissions immediately.' @@ -114,9 +116,9 @@ jobs: fi cat > "${RUNNER_TEMP}/run-dependabot-merge.mts" <<'TS' import { appendFileSync } from 'node:fs'; - import { runDependabotMerge } from './dependabot-merge.ts'; + import { auditDependabotMerges, runDependabotMerge } from './dependabot-merge.ts'; const e = process.env; - const result = runDependabotMerge({ + const result = e.AUDIT_ONLY === 'true' ? auditDependabotMerges(e.REPOSITORY!) : runDependabotMerge({ repository: e.REPOSITORY!, number: e.PR_NUMBER!, event: e.EVENT_NAME!, actor: e.EVENT_ACTOR!, triggeringActor: e.TRIGGERING_ACTOR!, author: e.PR_AUTHOR!, head: e.EVENT_HEAD!, phase: e.PHASE!, metadataOutcome: e.METADATA_OUTCOME ?? '', @@ -128,7 +130,7 @@ jobs: printf 'loaded=true\n' >> "${GITHUB_OUTPUT}" - name: Revoke previous auto-merge before metadata - if: steps.policy.outputs.loaded == 'true' + if: steps.policy.outputs.loaded == 'true' && !inputs.audit-only env: PHASE: revoke run: node "${RUNNER_TEMP}/run-dependabot-merge.mts" @@ -136,7 +138,7 @@ jobs: - name: Fetch Dependabot metadata timeout-minutes: 5 id: metadata - if: steps.policy.outputs.loaded == 'true' && github.event_name == 'pull_request' && github.actor == 'dependabot[bot]' && github.triggering_actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' + if: steps.policy.outputs.loaded == 'true' && !inputs.audit-only && github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]' && github.triggering_actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' continue-on-error: true uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 with: diff --git a/README.md b/README.md index 2202a9c2..50ddbe8e 100644 --- a/README.md +++ b/README.md @@ -837,7 +837,7 @@ With `--json`, successful reports go to stdout; failures use the host CLI's JSON Use [the reusable merge workflow](.github/workflows/pm-github-dependabot-merge.yml) instead of copying merge logic into each repository. It clears existing auto-merge before tooling or metadata, then requests squash auto-merge only for -`pull_request` events whose PR author, event actor and re-run actor are all +`pull_request_target` events whose PR author, event actor and re-run actor are all `dependabot[bot]`. It reads **every page** of the PR commits API and requires each commit's API-linked author to be Dependabot and its committer to be either Dependabot or `web-flow`. Every commit must also have GitHub verification @@ -856,12 +856,15 @@ updates outside those groups wait for review. The arm request includes ```yaml name: dependabot-auto-merge on: - pull_request: + pull_request_target: types: [opened, reopened, synchronize, ready_for_review, edited] + schedule: + - cron: "3-58/5 * * * *" + workflow_dispatch: permissions: {} jobs: verified-auto-merge: - if: github.event.pull_request.user.login == 'dependabot[bot]' + if: github.event_name == 'pull_request_target' && github.event.pull_request.user.login == 'dependabot[bot]' permissions: contents: write pull-requests: write @@ -870,6 +873,16 @@ jobs: extra-allowed-groups: "" # Optional exact names, separated by commas or whitespace secrets: merge_token: ${{ secrets.DEPENDABOT_MERGE_TOKEN }} + audit-armed-requests: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + permissions: + contents: write + pull-requests: write + uses: unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@RELEASE_COMMIT_SHA # pm-github RELEASE_VERSION + with: + audit-only: true + secrets: + merge_token: ${{ secrets.DEPENDABOT_MERGE_TOKEN }} ``` Replace `RELEASE_COMMIT_SHA` with the full 40-character SHA of the reviewed @@ -889,7 +902,7 @@ and review rules on the protected base branch. Foreign commits revoke auto-merge and produce a successful job, a summary and `::notice` listing **short SHA and author login only**. Disallowed metadata, -unsupported events (including `pull_request_target`), API failures and incomplete +unsupported arm events (including `pull_request`), API failures and incomplete pagination also leave auto-merge unarmed with a clear notice. Every CLI call is limited to three attempts of 60 seconds each; failed attempts discard partial output. A timed-out state read still attempts revocation. An @@ -897,10 +910,15 @@ inability to revoke is an operational failure that warns the request may remain armed, rather than a false successful safety claim. No PR files, Git author names/emails, commit messages or raw API errors are printed. Actions are SHA-pinned and no PR code is checked out or executed. -The standalone policy is fetched from `job.workflow_sha` in pm-github; this -repository's local-path caller instead fetches policy from trusted `main`, so -a PR cannot supply the executable script. Before this change reaches `main`, -that self-call fails closed because the trusted policy file is absent. +The caller runs on `pull_request_target`, so its YAML and local reusable +workflow come from the trusted default branch. Remote callers pin the reusable +workflow to a reviewed release SHA. In both cases the standalone policy is +fetched from that exact `job.workflow_sha`; no policy override reads a mutable +branch or PR revision. Before this change reaches the default branch, the new +trusted caller does not run. Configure an Actions event policy permitting +`pull_request_target` where required; GitHub's default public-repository policy +is scheduled for enforcement on December 2, 2026. See +[GitHub's trusted-trigger guidance](https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target). Migration: replace the **entire** copied `dependabot-auto-merge.yml` with the caller above. Retain `synchronize` and the PR-author-only caller guard. Other @@ -914,6 +932,17 @@ Remove any other workflow that arms Dependabot auto-merge, and clear existing requests when adopting the caller; installing a workflow does not retroactively trigger it for every open PR. This repository uses the supported local path. +Keep the scheduled audit job when adopting the caller. GitHub can suppress +workflow-generated events or park their `pull_request` runs awaiting approval; +changing only the PR trigger does not establish an independent revocation path. +The five-minute schedule and manual dispatch audit every armed Dependabot PR, +including every page of its commits, with the same provenance validator used to +arm. Unsafe or unverifiable snapshots are revoked; valid requests are preserved. +Audits never arm requests or depend on PR actor/metadata. Listing, policy-load +or revocation failures fail visibly. Scheduled runs can be delayed or disabled +by GitHub, so this is eventual revocation and requires operational monitoring, +not an atomic merge guard. See [GitHub's token event rules](https://docs.github.com/en/actions/concepts/security/github_token). + GitHub auto-merge is asynchronous. This workflow verifies the head it arms and revokes on subsequent events; it cannot atomically prevent GitHub merging a later push before the next workflow runs. A literal guarantee for future diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts index 4580c50b..fdd06f89 100644 --- a/scripts/dependabot-merge.ts +++ b/scripts/dependabot-merge.ts @@ -59,7 +59,8 @@ function login(value: string | undefined): string { } /** - * Revoke first and independently re-read provenance before a head-bound arm. + * Revoke before arming and independently re-read provenance for a head-bound arm. + * Audit-only calls preserve verified requests and never arm a new request. * Every refusal revokes even after an API failure. Inability to revoke throws: * the job must not claim success with an unsafe request potentially still armed. */ @@ -82,7 +83,8 @@ export function runDependabotMerge(input: MergeInput, gh: (args: readonly string catch { throw new Error("Auto-merge revocation denied or unavailable after bounded retries; request may still be armed. Check token write permissions and retry immediately."); } } }; - revoke(); + const audit = input.phase === "audit"; + if (!audit) revoke(); if (input.phase === "revoke") return { armed: false, message: "Previous auto-merge request revoked before metadata." }; let message: string; try { @@ -105,7 +107,10 @@ export function runDependabotMerge(input: MergeInput, gh: (args: readonly string const identities = foreign.map(commit => `${commit.sha.slice(0, 12)} ${login(commit.author?.login)}`); const allowedGroups = ["pm-toolchain", ...input.extraGroups.split(/[\s,]+/).filter(Boolean)]; if (foreign.length > 0) message = `Foreign or unverified commits: ${identities.join(", ")}.`; - else if (input.event !== "pull_request") message = "Only pull_request callers are supported; pull_request_target is refused."; + else if (audit && before.state === "open" && after.state === "open" && before.user.login === bot && after.user.login === bot) { + return { armed: false, message: `Verified Dependabot head ${after.head.sha.slice(0, 12)} retained; audit never arms auto-merge.` }; + } + else if (input.event !== "pull_request_target" || audit) message = "Only trusted pull_request_target callers can arm auto-merge."; else if (input.actor !== bot || input.triggeringActor !== bot || input.author !== bot || before.user.login !== bot || after.user.login !== bot) message = "PR author and event/re-run actors must all be dependabot[bot]."; else if (before.state !== "open" || after.state !== "open" || !shaPattern.test(input.head) || input.head !== after.head.sha) message = "PR is closed or the event head is stale."; else if (input.metadataOutcome !== "success") message = "Dependabot metadata unavailable or failed; auto-merge not armed."; @@ -123,3 +128,27 @@ export function runDependabotMerge(input: MergeInput, gh: (args: readonly string revoke(); return { armed: false, message }; } + +/** Audit every armed Dependabot PR independently of suppressed push events; never arm a request. */ +export function auditDependabotMerges(repository: string, gh: (args: readonly string[]) => string = githubCli): MergeResult { + if (!/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repository)) throw new Error("Invalid audit repository."); + let pulls: { number: number; user: { login: string }; auto_merge: unknown }[]; + try { + const receipt = gh(["api", `repos/${repository}/pulls?state=open&per_page=100`, "--paginate", "--jq", ".[] | @json"]).trim(); + pulls = receipt === "" ? [] : receipt.split("\n").map(line => JSON.parse(line)); + } catch { throw new Error("Cannot read complete PR audit listing; retry immediately."); } + if (!pulls.every(pr => pr && Number.isSafeInteger(pr.number) && pr.number > 0 && typeof pr.user?.login === "string" && Object.hasOwn(pr, "auto_merge"))) { + throw new Error("Incomplete PR audit listing; retry immediately."); + } + const results: string[] = []; + let failures = 0; + for (const pr of pulls.filter(pr => pr.user.login === bot && pr.auto_merge !== null)) { + try { + const result = runDependabotMerge({ repository, number: String(pr.number), phase: "audit", event: "schedule", + actor: "", triggeringActor: "", author: bot, head: "", metadataOutcome: "", group: "", updateType: "", extraGroups: "" }, gh); + results.push(`#${pr.number}: ${result.message}`); + } catch { failures++; } + } + if (failures > 0) throw new Error(`Cannot complete ${failures} auto-merge revocations; requests may still be armed. Retry immediately.`); + return { armed: false, message: results.join("\n") || "No armed Dependabot requests to audit." }; +} diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 10acbdc1..7ab3c82d 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -6,7 +6,7 @@ import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import test from "node:test"; import { parse } from "yaml"; -import { githubCli, runDependabotMerge, type MergeInput } from "../scripts/dependabot-merge.ts"; +import { auditDependabotMerges, githubCli, runDependabotMerge, type MergeInput } from "../scripts/dependabot-merge.ts"; const root = resolve(import.meta.dirname, ".."); const bot = "dependabot[bot]"; @@ -15,7 +15,7 @@ const trusted = "b".repeat(40); /** Model only remote API responses and mutation receipts; execute the actual policy. */ function fixture() { - const input: MergeInput = { repository: "acme/widgets", number: "7", event: "pull_request", actor: bot, + const input: MergeInput = { repository: "acme/widgets", number: "7", event: "pull_request_target", actor: bot, triggeringActor: bot, author: bot, head, phase: "evaluate", metadataOutcome: "success", group: "", updateType: "version-update:semver-patch", extraGroups: "" }; const commit = { sha: head, author: { login: bot }, committer: { login: "web-flow" }, commit: { verification: { verified: true, reason: "valid" } } }; @@ -25,7 +25,7 @@ function fixture() { let reads = 0; const f = { input, commit, state, pages, calls, failCommits: false, failReads: false, failArm: false, failRevoke: false, rawPages: undefined as string | undefined, onRead: (_read: number) => {}, - execute: () => runDependabotMerge(input, args => { + execute: () => runDependabotMerge(input, f.gh), gh: (args: readonly string[]): string => { calls.push([...args]); if (args[0] === "api" && args[1]!.includes("/commits?")) { assert.ok(args.includes("--paginate")); assert.ok(args.includes("--jq")); @@ -48,7 +48,7 @@ function fixture() { state.auto_merge = {}; } return ""; - }) }; + } }; return f; } @@ -93,7 +93,7 @@ for (const [name, mutate] of [ } for (const changes of [ - { event: "pull_request_target" }, { event: "workflow_dispatch" }, { actor: "outside" }, + { event: "pull_request" }, { event: "workflow_dispatch" }, { actor: "outside" }, { triggeringActor: "outside" }, { author: "outside" }, { head: "c".repeat(40) }, { head: "invalid" }, { metadataOutcome: "failure" }, { metadataOutcome: "skipped" }, { updateType: "version-update:semver-major" }, { updateType: "" }, @@ -218,14 +218,14 @@ test("workflow policy: pinned actions, minimal grants, no PR checkout or interpo const caller = readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8"); assert.match(caller, /uses: \.\/\.github\/workflows\/pm-github-dependabot-merge.yml/); assert.match(caller, /synchronize/); - assert.match(caller, /if: github\.event\.pull_request\.user\.login == 'dependabot\[bot\]'/); - assert.doesNotMatch(caller, /github\.(?:actor|triggering_actor)|pull_request_target/); + assert.match(caller, /if: github\.event_name == 'pull_request_target' && github\.event\.pull_request\.user\.login == 'dependabot\[bot\]'/); + assert.doesNotMatch(caller, /github\.(?:actor|triggering_actor)/); assert.doesNotMatch(caller, /auto_merge_enabled/, "custom-token arm events must not revoke their own request"); }); test("custom-token arm event preserves the request; a foreign synchronize event revokes it", () => { - const caller = parse(readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8")) as { on: { pull_request: { types: string[] } } }; - const subscribed = caller.on.pull_request.types; + const caller = parse(readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8")) as { on: { pull_request_target: { types: string[] } } }; + const subscribed = caller.on.pull_request_target.types; const f = fixture(); f.state.auto_merge = null; assert.equal(f.execute().armed, true); // PAT/App-generated events belong to the credential owner, not Dependabot. @@ -248,15 +248,15 @@ exec /usr/bin/timeout --signal=KILL "$duration" "$@" `, { mode: 0o755 }); } -for (const mode of ["external", "local", "remote-self", "load-failed", "invalid-sha", "local-ref-failed", "load-hang", "fallback-read-hang", "fallback-disable-hang", "load-retry"]) { +for (const mode of ["external", "local", "remote-self", "audit", "audit-load-failed", "load-failed", "invalid-sha", "load-hang", "fallback-read-hang", "fallback-disable-hang", "load-retry"]) { test(`real workflow loader/launcher: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-policy-")); const env = { ...process.env, PATH: dir + ":" + process.env.PATH, RUNNER_TEMP: dir, - RETRY_MARKER: join(dir, "retried"), POLICY_SOURCE: join(root, "scripts/dependabot-merge.ts"), WORKFLOW_SHA: mode === "invalid-sha" ? "bad" : head, + RETRY_MARKER: join(dir, "retried"), POLICY_SOURCE: join(root, "scripts/dependabot-merge.ts"), WORKFLOW_SHA: mode === "invalid-sha" ? "bad" : mode === "local" ? trusted : head, WORKFLOW_REF: `unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@${mode.startsWith("local") ? "refs/pull/7/merge" : head}`, CALLER_REF: "refs/pull/7/merge", - REPOSITORY: mode.startsWith("local") || mode === "remote-self" ? "unbraind/pm-github" : "acme/widgets", PR_NUMBER: "7", MODE: mode, - GITHUB_STEP_SUMMARY: join(dir, "summary"), EVENT_NAME: "pull_request", EVENT_ACTOR: "outside", TRIGGERING_ACTOR: "outside", + REPOSITORY: mode.startsWith("local") || mode === "remote-self" ? "unbraind/pm-github" : "acme/widgets", PR_NUMBER: mode.startsWith("audit") ? "" : "7", MODE: mode, + GITHUB_STEP_SUMMARY: join(dir, "summary"), EVENT_NAME: "pull_request_target", AUDIT_ONLY: String(mode.startsWith("audit")), EVENT_ACTOR: "outside", TRIGGERING_ACTOR: "outside", GITHUB_OUTPUT: join(dir, "output"), PR_AUTHOR: bot, EVENT_HEAD: head, EXTRA_GROUPS: "", PHASE: "evaluate", RECEIPTS: join(dir, "receipts") }; try { @@ -268,7 +268,8 @@ case "$*" in *contents/scripts*) [[ "$MODE" != load-hang ]] || { trap '' TERM; sleep 2; exit 0; } [[ "$MODE" != load-retry || -f "$RETRY_MARKER" ]] || { touch "$RETRY_MARKER"; printf partial; exit 1; } - [[ "$MODE" != load-failed && "$MODE" != fallback-* ]] || exit 1; cat "$POLICY_SOURCE" ;; + [[ "$MODE" != load-failed && "$MODE" != audit-load-failed && "$MODE" != fallback-* ]] || exit 1; cat "$POLICY_SOURCE" ;; + *'/pulls?'*) printf '%s' '{"number":7,"user":{"login":"${bot}"},"auto_merge":{}}' ;; *'/commits?'*) printf '%s' '{"sha":"${head}","author":{"login":"outside"},"committer":{"login":"outside"}}' ;; 'api '*) [[ "$MODE" != fallback-read-hang ]] || { trap '' TERM; sleep 2; exit 0; }; printf '%s' '{"state":"open","user":{"login":"${bot}"},"head":{"sha":"${head}"},"commits":1,"auto_merge":null}' ;; 'pr merge '*) [[ "$MODE" != fallback-disable-hang ]] || { trap '' TERM; sleep 2; exit 0; } ;; @@ -276,10 +277,13 @@ case "$*" in esac `, { mode: 0o755 }); const loaded = spawnSync("bash", ["-c", loader()], { cwd: dir, env, encoding: "utf8", timeout: 10_000, killSignal: "SIGKILL" }); - const failed = ["load-failed", "invalid-sha", "local-ref-failed", "load-hang", "fallback-read-hang"].includes(mode); - assert.equal(loaded.status, mode === "fallback-disable-hang" ? 1 : 0, loaded.stderr); + const failed = ["load-failed", "invalid-sha", "load-hang", "fallback-read-hang"].includes(mode); + assert.equal(loaded.status, mode === "fallback-disable-hang" || mode === "audit-load-failed" ? 1 : 0, loaded.stderr); const receipts = readFileSync(env.RECEIPTS, "utf8"); - if (mode === "fallback-disable-hang") { + if (mode === "audit-load-failed") { + assert.match(loaded.stdout, /Cannot load trusted audit policy/); + assert.doesNotMatch(receipts, /pr merge|pulls\//); + } else if (mode === "fallback-disable-hang") { assert.match(loaded.stdout, /Cannot revoke auto-merge after policy load failure and 3 bounded attempts/); assert.equal(receipts.split("\n").filter(line => line.startsWith("pr merge ")).length, 3); } else if (failed) { @@ -294,7 +298,7 @@ esac if (mode === "remote-self") assert.doesNotMatch(receipts, /git\/ref\/heads\/main/); const run = spawnSync(process.execPath, [join(dir, "run-dependabot-merge.mts")], { cwd: dir, env, encoding: "utf8" }); assert.equal(run.status, 0, run.stderr); - assert.match(run.stdout, /::notice::Foreign or unverified commits: aaaaaaaaaaaa outside/); + assert.match(run.stdout, /::notice::(?:#7: )?Foreign or unverified commits: aaaaaaaaaaaa outside/); assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /aaaaaaaaaaaa outside/); // Load the original module for coverage, with the fake gh available at // child startup even if a runtime caches its initial command lookup. @@ -390,3 +394,140 @@ for (const mode of ["retry", "exhausted", "timeout"]) { } finally { rmSync(dir, { recursive: true, force: true }); } }); } + + +/** Supply paginated PR-list receipts while exercising the shared production provenance checks. */ +function auditFixture() { + const f = fixture(); + const pulls: unknown[] = [{ number: 7, user: { login: bot }, auto_merge: {} }]; + const gh = (args: readonly string[]): string => { + if (args[1]?.includes("/pulls?")) { + assert.ok(args.includes("--paginate")); + return pulls.map(pr => JSON.stringify(pr)).join("\n"); + } + return f.gh(args); + }; + return { f, pulls, gh, execute: () => auditDependabotMerges(f.input.repository, gh) }; +} + +test("independent audit preserves verified requests and never arms them", () => { + const a = auditFixture(); + assert.match(a.execute().message, /Verified Dependabot head.*retained/); + assert.notEqual(a.f.state.auto_merge, null); + assert.ok(!a.f.calls.some(args => args[0] === "pr")); +}); + +test("scheduled audit revokes a foreign push when its synchronize run never executes", () => { + const caller = parse(readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8")) as { + on: { schedule: { cron: string }[]; workflow_dispatch: unknown; pull_request_target: { types: string[] } }; + jobs: Record; + }; + assert.equal(caller.on.schedule[0]!.cron, "3-58/5 * * * *"); + assert.ok(Object.hasOwn(caller.on, "workflow_dispatch")); + assert.equal(caller.jobs["audit-armed-requests"]!.if, "github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"); + assert.equal(caller.jobs["audit-armed-requests"]!.with!["audit-only"], true); + assert.ok(caller.on.pull_request_target.types.includes("synchronize")); + const a = auditFixture(); a.f.commit.author.login = "github-actions[bot]"; + // No synchronize policy run: a workflow-generated push leaves the old request armed. + assert.notEqual(a.f.state.auto_merge, null); + assert.match(a.execute().message, /Foreign or unverified commits/); + assert.equal(a.f.state.auto_merge, null); + assert.ok(!a.f.calls.some(args => args.includes("--auto"))); +}); + +test("audit and arm use identical provenance verdicts over varied API snapshots", () => { + for (let i = 0; i < 40; i++) { + const a = auditFixture(); const f = fixture(); + for (const next of [a.f, f]) { + next.commit.author.login = i % 2 ? bot : "outside"; + next.commit.committer.login = i % 3 ? "web-flow" : "outside"; + next.commit.commit.verification.verified = i % 5 !== 0; + next.commit.commit.verification.reason = i % 7 ? "valid" : "unsigned"; + } + const eligible = f.execute().armed; + a.execute(); + assert.equal(a.f.state.auto_merge !== null, eligible); + assert.ok(!a.f.calls.some(args => args.includes("--auto"))); + } +}); + +for (const change of ["closed-before", "closed-after", "foreign-before", "foreign-after", "changed-head", "api-error"] as const) { + test(`independent audit refuses stale or unverifiable snapshots: ${change}`, () => { + const a = auditFixture(); + if (change === "closed-before") a.f.state.state = "closed"; + if (change === "foreign-before") a.f.state.user.login = "outside"; + if (change === "closed-after") a.f.onRead = n => { if (n === 2) a.f.state.state = "closed"; }; + if (change === "foreign-after") a.f.onRead = n => { if (n === 2) a.f.state.user.login = "outside"; }; + if (change === "changed-head") a.f.onRead = n => { if (n === 2) a.f.state.head.sha = trusted; }; + if (change === "api-error") a.f.failCommits = true; + a.execute(); + assert.ok(!a.f.calls.some(args => args.includes("--auto"))); + if (!change.startsWith("closed")) assert.equal(a.f.state.auto_merge, null); + }); +} + +test("audit ignores unarmed and other authors' requests, and handles an empty listing", () => { + const a = auditFixture(); + a.pulls[0] = { number: 7, user: { login: bot }, auto_merge: null }; + a.pulls.push({ number: 8, user: { login: "outside" }, auto_merge: {} }); + assert.match(a.execute().message, /No armed Dependabot requests/); + a.pulls.length = 0; + assert.equal(a.execute().armed, false); + assert.equal(a.f.calls.length, 0); +}); + +for (const pr of [null, {}, { number: 0 }, { number: 1, user: null }, { number: 1, user: { login: bot } }]) { + test(`audit refuses an incomplete listing before any mutation: ${JSON.stringify(pr)}`, () => { + const a = auditFixture(); a.pulls[0] = pr; + assert.throws(a.execute, /Incomplete PR audit listing/); + assert.equal(a.f.calls.length, 0); + }); +} + +test("audit listing errors and malformed pagination fail with sanitized messages", () => { + assert.throws(() => auditDependabotMerges("--bad"), /Invalid audit repository/); + for (const receipt of ["private malformed", "{}\npartial"]) { + assert.throws(() => auditDependabotMerges("acme/widgets", () => receipt), /^Error: Cannot read complete PR audit listing; retry immediately\.$/); + } + assert.throws(() => auditDependabotMerges("acme/widgets", () => { throw new Error("private remote error"); }), /Cannot read complete PR audit listing/); +}); + +test("audit continues other PRs after denied revocation and reports operational failure", () => { + const a = auditFixture(); a.pulls.push({ number: 8, user: { login: bot }, auto_merge: {} }); + a.f.commit.author.login = "outside"; a.f.failRevoke = true; + assert.throws(a.execute, /Cannot complete 2 auto-merge revocations/); + assert.equal(a.f.calls.filter(args => args.includes("--disable-auto")).length, 2); +}); + +test("activation tracker reflects active workflow and keeps fleet acceptance separate", () => { + const chore = readFileSync(join(root, ".agents/pm/chores/pm-github-dependabot-activation.toon"), "utf8"); + const description = chore.split("description: ")[1]!.split("\ntype:")[0]!; + const criteria = chore.split("acceptance_criteria: ")[1]!.split("\nblocked_by:")[0]!; + assert.match(description, /now reports.*active/); + assert.match(description, /fleet caller adoption separate/); + assert.doesNotMatch(criteria, /enable the previously disabled/); + for (const requirement of ["token", "protected-branch", "live", "fleet", "event policy", "scheduled audit"]) assert.ok(criteria.includes(requirement)); +}); + + +test("trusted caller and audit launcher cannot run PR workflow code or accidentally arm during a sweep", () => { + const caller = parse(readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8")) as { + on: Record; jobs: Record; + }; + assert.ok(Object.hasOwn(caller.on, "pull_request_target")); + assert.ok(!Object.hasOwn(caller.on, "pull_request")); + for (const job of Object.values(caller.jobs)) assert.equal(job.uses, "./.github/workflows/pm-github-dependabot-merge.yml"); + const reusable = parse(readFileSync(join(root, ".github/workflows/pm-github-dependabot-merge.yml"), "utf8")) as { + jobs: Record; steps: { name: string; if?: string; run?: string; env?: Record }[] }>; + }; + const job = reusable.jobs["dependabot-merge"]!; + assert.equal(job.env.AUDIT_ONLY, "${{ inputs.audit-only }}"); + for (const step of job.steps.filter(step => step.name.includes("Revoke previous") || step.name === "Fetch Dependabot metadata")) { + assert.match(step.if!, /!inputs.audit-only/); + } + assert.match(job.steps.find(step => step.name === "Fetch Dependabot metadata")!.if!, /github.event_name == 'pull_request_target'/); + const load = job.steps.find(step => step.name === "Load trusted merge policy")!; + assert.equal(load.env!.WORKFLOW_SHA, "${{ job.workflow_sha }}"); + assert.doesNotMatch(load.run!, /git\/ref\/heads|EVENT_HEAD.*contents|CALLER_REF/); + assert.match(load.run!, /e\.AUDIT_ONLY === 'true' \? auditDependabotMerges/); +}); From 4a77bc0715c379d7e8fc0b12ad763612b2bbdbad Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:12:28 +0100 Subject: [PATCH 13/19] Record second review proofs and release gate receipts --- .agents/pm/chores/pm-github-dependabot-activation.toon | 7 +++---- .agents/pm/features/pm-github-dependabot-merge.toon | 8 ++++---- .agents/pm/history/pm-github-dependabot-activation.jsonl | 2 ++ .agents/pm/history/pm-github-dependabot-merge.jsonl | 3 +++ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.agents/pm/chores/pm-github-dependabot-activation.toon b/.agents/pm/chores/pm-github-dependabot-activation.toon index 07c26b3c..bbfbdc83 100644 --- a/.agents/pm/chores/pm-github-dependabot-activation.toon +++ b/.agents/pm/chores/pm-github-dependabot-activation.toon @@ -6,16 +6,15 @@ status: blocked priority: 2 tags[4]: ci,dependabot,follow-up,security created_at: "2026-11-05T00:52:51.053Z" -updated_at: "2026-11-05T10:34:23.888Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-11-05T11:10:09.277Z" author: codex-agent acceptance_criteria: "After reviewed delivery and release, verify each selected caller pins a release containing the policy or uses the reviewed trusted local path.; Verify the active caller has a repository-scoped merge token as Actions and Dependabot secrets when needed, required Actions event policy permits its trusted trigger, and server-enforced protected-branch checks and review requirements prevent unreviewed merges.; With owner authorization record live bot-only arm, foreign-commit revocation and independent scheduled audit acceptance without permitting an unintended merge, and coordinate remaining fleet adoption separately." blocked_by: pm-github-dependabot-merge dependencies[1]{id,kind,created_at,author,source_kind,author_source}: pm-github-dependabot-merge,blocked_by,"2026-11-05T00:52:51.053Z",codex-agent,"cli:create:blocked_by",detected -comments[1]{created_at,author,text}: +comments[2]{created_at,author,text}: "2026-11-05T10:29:37.084Z",codex-agent,"PRRT_kwDOTbN4pM6z_gYw verified: Actions API currently reports the existing workflow active. Updated title, description and acceptance criteria to remove the obsolete enable request. Item remains blocked on reviewed implementation and runtime token/protection/live acceptance; active workflow state is not proof of fleet activation. No live workflow or secret settings changed." + "2026-11-05T11:10:03.277Z",codex-agent,"Round 2 source delivered and tested without changing live settings: activation snapshot corrected, trusted default-branch caller plus scheduled/manual provenance audit implemented. Focused regression covers this item and passes through the pinned pm runner; restoring the obsolete item fails it. Runtime token, event-policy, branch-protection, live arm/revoke/audit and fleet adoption checks remain blocked pending orchestrator verification. Releasing only my claim; this item remains open." files[1]{path,scope}: .github/workflows/dependabot-auto-merge.yml,project tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}: diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index df4786f5..490b553b 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,10 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T10:38:33.053Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-11-05T11:10:12.745Z" author: codex-agent acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." -comments[38]{created_at,author,text}: +comments[40]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -50,6 +48,8 @@ comments[38]{created_at,author,text}: "2026-11-05T10:33:12.033Z",codex-agent,"Revert proofs in a disposable non-git copy with unchanged tests: restoring activation item from 440b830 fails 1 test; replacing only auditDependabotMerges with a no-op fails the workflow-generated-push scenario (1 test); reverting caller trigger/guard to pull_request fails the trusted-caller contract (1 test); restoring only the production arm-event comparison to pull_request fails 2 behavioral cases (valid trusted open and unsupported untrusted event). Restoring fixed files passes all 105 tests. Node and native Bun focused suites each pass 105, zero failures/skips; c8 policy receipts are exact 153/153 lines and statements, 111/111 branches, 6/6 functions. Reviewed the entire PR implementation, caller/template, loader, all commit checks and tracker descriptions for the same event/trust/state defects. No other live workflow changes or foreign branch scripts are introduced. Lint, test typecheck, docstrings (272 declarations), unchanged zero-threshold duplication and strict tracker health pass." "2026-11-05T10:34:09.288Z",codex-agent,"Pinned pm test --run --progress passed both linked commands: 105 Node tests and 105 native Bun tests, zero failed/skipped. Test-result tracking is disabled by existing project policy, so outcomes are recorded here without changing settings. Full npm run release:check is now running serially under Node 26 with isolated HOME/Git identity, public npm configuration and inherited credentials/session context excluded. Full Bun release gate follows; unchanged 100/100/100/100 thresholds and two-worker bound are retained." "2026-11-05T10:38:33.046Z",codex-agent,"All source changes and regression proofs are ready for review. Full npm gate passed prechecks and privacy (8872 objects) and is running complete coverage with no failures yet. Preparing a source push so exact-head CI can verify npm and Bun gates concurrently with the single local gate; no incomplete gate is counted as passing. Tracker claims remain held during validation. The independent schedule is an eventual revocation path, not an atomic future-head merge guarantee; server protections and live activation remain separate." + "2026-11-05T11:10:00.550Z",codex-agent,"Cutoff receipt for source 48abcd82b0c0e18e2b2409a631030e984d445af8: complete bun run release:check PASSED on Node 26 at 11:07:47 UTC, job https://github.com/unbraind/pm-github/actions/runs/42012432073/job/127194531337 . Downloaded completed-job logs verify 1754 tests: 1752 passed, zero failed/cancelled, two pre-existing SDK 2026.11.4 lifecycle-compensation quarantines for pm-cli#1656. Full inventory: 30 authored modules at exact 100/100/100/100 coverage, zero production audit vulnerabilities and all remaining release steps passed. At the 30-minute observation cutoff, full npm gates on Node 22/26 and Bun on Node 22 were still running: https://github.com/unbraind/pm-github/actions/runs/42012432073 . Those three are pending, not passing evidence; no further CI polling. CodeQL and Semgrep passed. Local Node 26 full npm attempt passed prechecks and privacy, then progressed without reported failures through real closeout fixtures, including the previously problematic first scenario after fresh npm ci. Stopped its owned process tree at handoff; this unfinished attempt is not a pass. No local test/watch process remains. Separate production audit, package dry run/build, changelog check and publication attestation passed. No dependency, threshold or quarantine changes were made." + "2026-11-05T11:10:06.899Z",codex-agent,"Final review refresh found only original unresolved thread PRRT_kwDOTbN4pM6z_gYw and no newer CodeRabbit review than 09:03:38 UTC. Its tracker fix and both outside-diff security fixes are committed in 48abcd8. Four isolated revert variants produced 1, 1, 1 and 2 expected failures; restored suite passed 105. Both linked Node/Bun commands passed 105 each. No review replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes. No new PM CLI/SDK bug was established. Final receipt commit changes tracker evidence only, so prior source-head CI is not final-head approval: fresh final-head CI remains required. Releasing only my feature claim and leaving both items open for orchestrator verification." files[6]: - path: .agents/pm/chores/pm-github-dependabot-activation.toon scope: project diff --git a/.agents/pm/history/pm-github-dependabot-activation.jsonl b/.agents/pm/history/pm-github-dependabot-activation.jsonl index aab3461a..a29733ae 100644 --- a/.agents/pm/history/pm-github-dependabot-activation.jsonl +++ b/.agents/pm/history/pm-github-dependabot-activation.jsonl @@ -6,3 +6,5 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T10:32:43.391Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:32:43.391Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"README.md","scope":"project"}]}],"before_hash":"641e6a46e83ccc9cb31804fbeeab70df925063d49e97a3ed8492737bb9c7aae2","after_hash":"079de8485b8f4374501d88249a2ce8349220c14587d5f3bb0bfaa56cde9eb19f","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":"96a0a85b9c83e515e2cebc5cd8fb1e15995bb51c0dd7030350b7c7d0fccbf2e4"} {"hash_algorithm":"sha256","ts":"2026-11-05T10:34:00.010Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T10:34:00.010Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --test-concurrency=2 --test-name-pattern=activation test/dependabot-merge.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-11-05T10:33:59.799Z","source_kind":"local_mutation","source_ref":"feat/reusable-dependabot-merge"}}]}],"before_hash":"079de8485b8f4374501d88249a2ce8349220c14587d5f3bb0bfaa56cde9eb19f","after_hash":"3b2f10527989ae702d2bcafc98c243d44996c4b6439007f1fc0606882cecf1db","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":"1555b0ac5882e4d3b7acbe042f8cc602b0fda63a3b2c1cf3970f4595fa3599ca"} {"hash_algorithm":"sha256","ts":"2026-11-05T10:34:23.888Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/acceptance_criteria","value":"After reviewed delivery and release, verify each selected caller pins a release containing the policy or uses the reviewed trusted local path.; Verify the active caller has a repository-scoped merge token as Actions and Dependabot secrets when needed, required Actions event policy permits its trusted trigger, and server-enforced protected-branch checks and review requirements prevent unreviewed merges.; With owner authorization record live bot-only arm, foreign-commit revocation and independent scheduled audit acceptance without permitting an unintended merge, and coordinate remaining fleet adoption separately."},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:34:23.888Z"},{"op":"replace","path":"/metadata/description","value":"Read-only Actions API inspection now reports dependabot-auto-merge.yml active, superseding the earlier disabled snapshot. PR #221 delivers the reusable provenance policy, trusted default-branch caller and independent scheduled audit source. Workflow state alone does not prove safe runtime activation. Keep repository token, event-policy, protected-branch and live-acceptance verification and fleet caller adoption separate from this implementation. Coordinate with companion pm-cli-website-wis6."}],"before_hash":"3b2f10527989ae702d2bcafc98c243d44996c4b6439007f1fc0606882cecf1db","after_hash":"9c47de40b0527c135a0f69fa42f3732c64f016ef4f3e50998cf7b9371f16985b","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":"34b620385da3c5010853b4ac607c99833adf614ac6f4d551758eea8c8c614957"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:10:03.282Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T11:10:03.277Z","author":"codex-agent","text":"Round 2 source delivered and tested without changing live settings: activation snapshot corrected, trusted default-branch caller plus scheduled/manual provenance audit implemented. Focused regression covers this item and passes through the pinned pm runner; restoring the obsolete item fails it. Runtime token, event-policy, branch-protection, live arm/revoke/audit and fleet adoption checks remain blocked pending orchestrator verification. Releasing only my claim; this item remains open."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:10:03.282Z"}],"before_hash":"9c47de40b0527c135a0f69fa42f3732c64f016ef4f3e50998cf7b9371f16985b","after_hash":"c91f6a68282d39cfd18bd782354913f4d5f9a7f117f54530470a1b18818f2827","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":"11ed2b2db856555ee02e9c7b8e63f3ffe863d949fd7e49c2da9b405bf964cc4d"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:10:09.277Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T11:10:09.277Z"}],"before_hash":"c91f6a68282d39cfd18bd782354913f4d5f9a7f117f54530470a1b18818f2827","after_hash":"5b86f8fffbca66fffbf1cb6212b7d1a0e23c07592c8b7e8353f9166c53ff05a6","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":"7f7f28410ef821dd2f94af6e9a94c3fe1dc112830e86ca5a653ffd1d63f2b262"} diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index fc7db6fb..0046c361 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -51,3 +51,6 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T10:33:12.036Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/35","value":{"created_at":"2026-11-05T10:33:12.033Z","author":"codex-agent","text":"Revert proofs in a disposable non-git copy with unchanged tests: restoring activation item from 440b830 fails 1 test; replacing only auditDependabotMerges with a no-op fails the workflow-generated-push scenario (1 test); reverting caller trigger/guard to pull_request fails the trusted-caller contract (1 test); restoring only the production arm-event comparison to pull_request fails 2 behavioral cases (valid trusted open and unsupported untrusted event). Restoring fixed files passes all 105 tests. Node and native Bun focused suites each pass 105, zero failures/skips; c8 policy receipts are exact 153/153 lines and statements, 111/111 branches, 6/6 functions. Reviewed the entire PR implementation, caller/template, loader, all commit checks and tracker descriptions for the same event/trust/state defects. No other live workflow changes or foreign branch scripts are introduced. Lint, test typecheck, docstrings (272 declarations), unchanged zero-threshold duplication and strict tracker health pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:33:12.036Z"}],"before_hash":"09ace34053b8f6320a3edc2adf48d9646a562bca0a2c5c444f6470f5754a6c59","after_hash":"72e379f40e14f08ed079eefc57413050f862ec6f797ffe481c51c491770fc211","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":"5e093e98915bf3a7b84980a0baa56378f9572020cfe85c269fd07f3db721d369"} {"hash_algorithm":"sha256","ts":"2026-11-05T10:34:09.289Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/36","value":{"created_at":"2026-11-05T10:34:09.288Z","author":"codex-agent","text":"Pinned pm test --run --progress passed both linked commands: 105 Node tests and 105 native Bun tests, zero failed/skipped. Test-result tracking is disabled by existing project policy, so outcomes are recorded here without changing settings. Full npm run release:check is now running serially under Node 26 with isolated HOME/Git identity, public npm configuration and inherited credentials/session context excluded. Full Bun release gate follows; unchanged 100/100/100/100 thresholds and two-worker bound are retained."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:34:09.289Z"}],"before_hash":"72e379f40e14f08ed079eefc57413050f862ec6f797ffe481c51c491770fc211","after_hash":"608db69f9c57d9775ed0ea3a9ca8f145292ab40fa686aa744038c15094461703","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":"9502a95ca5b8782f7dbe06fd803e7ef52467ac55db854e6c098933c854917607"} {"hash_algorithm":"sha256","ts":"2026-11-05T10:38:33.053Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/37","value":{"created_at":"2026-11-05T10:38:33.046Z","author":"codex-agent","text":"All source changes and regression proofs are ready for review. Full npm gate passed prechecks and privacy (8872 objects) and is running complete coverage with no failures yet. Preparing a source push so exact-head CI can verify npm and Bun gates concurrently with the single local gate; no incomplete gate is counted as passing. Tracker claims remain held during validation. The independent schedule is an eventual revocation path, not an atomic future-head merge guarantee; server protections and live activation remain separate."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T10:38:33.053Z"}],"before_hash":"608db69f9c57d9775ed0ea3a9ca8f145292ab40fa686aa744038c15094461703","after_hash":"200c1f6de8475b1dced688c31dd61bd4c4ce046da07cb246ddac3b10335a6e04","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":"45cd004cf46a43e0b3158d681755f665e122504ed910a4f4d4123d0113779cb7"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:10:00.551Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/38","value":{"created_at":"2026-11-05T11:10:00.550Z","author":"codex-agent","text":"Cutoff receipt for source 48abcd82b0c0e18e2b2409a631030e984d445af8: complete bun run release:check PASSED on Node 26 at 11:07:47 UTC, job https://github.com/unbraind/pm-github/actions/runs/42012432073/job/127194531337 . Downloaded completed-job logs verify 1754 tests: 1752 passed, zero failed/cancelled, two pre-existing SDK 2026.11.4 lifecycle-compensation quarantines for pm-cli#1656. Full inventory: 30 authored modules at exact 100/100/100/100 coverage, zero production audit vulnerabilities and all remaining release steps passed. At the 30-minute observation cutoff, full npm gates on Node 22/26 and Bun on Node 22 were still running: https://github.com/unbraind/pm-github/actions/runs/42012432073 . Those three are pending, not passing evidence; no further CI polling. CodeQL and Semgrep passed. Local Node 26 full npm attempt passed prechecks and privacy, then progressed without reported failures through real closeout fixtures, including the previously problematic first scenario after fresh npm ci. Stopped its owned process tree at handoff; this unfinished attempt is not a pass. No local test/watch process remains. Separate production audit, package dry run/build, changelog check and publication attestation passed. No dependency, threshold or quarantine changes were made."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:10:00.551Z"}],"before_hash":"200c1f6de8475b1dced688c31dd61bd4c4ce046da07cb246ddac3b10335a6e04","after_hash":"ae8269f563d7b920b186c68fa3a1d5f1a2c32294e065e2d5dbfc94d05a9724e0","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":"aae01047c03f6ee81b991472e62c0c3a4ecb8f7e07387651f01ec2e6ca95a8cc"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:10:06.900Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/39","value":{"created_at":"2026-11-05T11:10:06.899Z","author":"codex-agent","text":"Final review refresh found only original unresolved thread PRRT_kwDOTbN4pM6z_gYw and no newer CodeRabbit review than 09:03:38 UTC. Its tracker fix and both outside-diff security fixes are committed in 48abcd8. Four isolated revert variants produced 1, 1, 1 and 2 expected failures; restored suite passed 105. Both linked Node/Bun commands passed 105 each. No review replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes. No new PM CLI/SDK bug was established. Final receipt commit changes tracker evidence only, so prior source-head CI is not final-head approval: fresh final-head CI remains required. Releasing only my feature claim and leaving both items open for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:10:06.900Z"}],"before_hash":"ae8269f563d7b920b186c68fa3a1d5f1a2c32294e065e2d5dbfc94d05a9724e0","after_hash":"bea4b234a928f4d88d500b80cab1224f5cb6d9a51fafe42a9873e030eac8a14c","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":"810407895e6498350b7f73aaea600448641bf387e8def36ab6f6b6c7cdc6de4d"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:10:12.745Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T11:10:12.745Z"}],"before_hash":"bea4b234a928f4d88d500b80cab1224f5cb6d9a51fafe42a9873e030eac8a14c","after_hash":"2d222f6e6c20ddab9e42eb73c0ea4ba865ed74830be125e704b485cda0ff1db3","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":"dc30e65b5f0b709edbd9791f749ab52870ab436751adb538668f34c5efc213f7"} From 37049e008505c46880c205c828f7b9faee8129ed Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 13:02:15 +0100 Subject: [PATCH 14/19] fix: keep Dependabot audit callers within daily runner budget --- .../features/pm-github-dependabot-merge.toon | 15 +++- .../history/pm-github-dependabot-merge.jsonl | 7 ++ .github/workflows/dependabot-auto-merge.yml | 3 +- README.md | 7 +- test/dependabot-merge.test.ts | 76 ++++++++++++++++++- 5 files changed, 101 insertions(+), 7 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 490b553b..0fea959b 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,10 +6,12 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T11:10:12.745Z" +updated_at: "2026-11-05T12:02:03.387Z" +assignee: codex-agent +claim_principal: codex-agent author: codex-agent acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." -comments[40]{created_at,author,text}: +comments[45]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -50,7 +52,12 @@ comments[40]{created_at,author,text}: "2026-11-05T10:38:33.046Z",codex-agent,"All source changes and regression proofs are ready for review. Full npm gate passed prechecks and privacy (8872 objects) and is running complete coverage with no failures yet. Preparing a source push so exact-head CI can verify npm and Bun gates concurrently with the single local gate; no incomplete gate is counted as passing. Tracker claims remain held during validation. The independent schedule is an eventual revocation path, not an atomic future-head merge guarantee; server protections and live activation remain separate." "2026-11-05T11:10:00.550Z",codex-agent,"Cutoff receipt for source 48abcd82b0c0e18e2b2409a631030e984d445af8: complete bun run release:check PASSED on Node 26 at 11:07:47 UTC, job https://github.com/unbraind/pm-github/actions/runs/42012432073/job/127194531337 . Downloaded completed-job logs verify 1754 tests: 1752 passed, zero failed/cancelled, two pre-existing SDK 2026.11.4 lifecycle-compensation quarantines for pm-cli#1656. Full inventory: 30 authored modules at exact 100/100/100/100 coverage, zero production audit vulnerabilities and all remaining release steps passed. At the 30-minute observation cutoff, full npm gates on Node 22/26 and Bun on Node 22 were still running: https://github.com/unbraind/pm-github/actions/runs/42012432073 . Those three are pending, not passing evidence; no further CI polling. CodeQL and Semgrep passed. Local Node 26 full npm attempt passed prechecks and privacy, then progressed without reported failures through real closeout fixtures, including the previously problematic first scenario after fresh npm ci. Stopped its owned process tree at handoff; this unfinished attempt is not a pass. No local test/watch process remains. Separate production audit, package dry run/build, changelog check and publication attestation passed. No dependency, threshold or quarantine changes were made." "2026-11-05T11:10:06.899Z",codex-agent,"Final review refresh found only original unresolved thread PRRT_kwDOTbN4pM6z_gYw and no newer CodeRabbit review than 09:03:38 UTC. Its tracker fix and both outside-diff security fixes are committed in 48abcd8. Four isolated revert variants produced 1, 1, 1 and 2 expected failures; restored suite passed 105. Both linked Node/Bun commands passed 105 each. No review replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes. No new PM CLI/SDK bug was established. Final receipt commit changes tracker evidence only, so prior source-head CI is not final-head approval: fresh final-head CI remains required. Releasing only my feature claim and leaving both items open for orchestrator verification." -files[6]: + "2026-11-05T11:18:15.876Z",codex-agent,"Review round 3 at 4a77bc0: pulled remote head; origin/main is already contained. Complete GraphQL pagination reports four resolved threads and zero unresolved threads, with all comment pages complete. Latest nonempty CodeRabbit review is 09:03:38 UTC; its two outside-diff event/trust findings are already covered by trusted pull_request_target and independent scheduled/manual audit tests. Orchestrator load finding is valid: both caller and README template use a five-minute schedule. Change both to daily plus workflow_dispatch, document the hourly fleet ceiling, and enforce parsed minute/hour intervals including five-minute, fifteen-minute, hourly and daily examples. Self-review the full PR for matching schedule/template defects; record isolated revert proofs and run unchanged release gates serially." + "2026-11-05T11:20:22.374Z",codex-agent,"Implemented daily 04:23 UTC audit plus workflow_dispatch in both the real caller and README template; fleet ceiling is explicitly hourly with daily preferred and staggered times advised. Added numeric cron minute/hour expansion (lists, ranges, wildcards, steps), circular day spacing and aggregate entry checks: five-minute and fifteen-minute schedules refuse; hourly and daily schedules pass. Isolated non-git copy revert proofs with unchanged new tests: restoring only caller five-minute cron fails 1/1; restoring only README five-minute template fails 1/1; removing only hourly documentation fails 1/1. Restored fixed copy passes 1/1. Full PR self-review found no other schedule/template instances. Initial 115-case focused run passed 114 with only the existing 500-ms CLI child-start timing test failing under host load (counter 2 instead of 3); retry through linked pm runner is pending. Typecheck, lint, docstrings (272 declarations) and zero-threshold duplication pass." + "2026-11-05T11:20:42.702Z",codex-agent,"Pinned pm test --run --progress passed both linked commands: Node 115/115 and native Bun 115/115, zero failures/skips. The initial host-load timeout counter failure passed on retry without source changes. Existing test-result tracking remains disabled; outcomes are recorded here. Starting full npm run release:check then bun run release:check serially with isolated Git configuration and HOME, public npm configuration, inherited credential/session variables removed, unchanged thresholds and two-worker concurrency. No dist files are committed in this repository." + "2026-11-05T11:40:11.952Z",codex-agent,"Full local npm release validation is progressing through the real workflow fixture corpus with no reported test failures so far, including the previously problematic scheduled closeout fixture. Prechecks and the object-store privacy audit passed. The complete gate is still pending; Bun full release validation remains queued serially. This is progress evidence only, not a gate pass. No dependencies, thresholds, runtime services or live workflow settings changed." + "2026-11-05T12:02:03.387Z",codex-agent,"Source changes are ready for review: three isolated reverts fail as expected; linked Node and native Bun suites each pass 115/115; test typecheck, lint, documented declarations, zero-threshold duplication and strict tracker health pass. Local full npm release remains in progress through slow workflow fixtures with no reported failures so far; full Bun is queued serially. Preparing a source push to start exact-head CI for both required complete release commands on the supported Node lines. No incomplete gate is counted as passing; continue one local gate only and observe the 30-minute CI cutoff." +files[7]: - path: .agents/pm/chores/pm-github-dependabot-activation.toon scope: project - path: .github/workflows/dependabot-auto-merge.yml @@ -60,6 +67,8 @@ files[6]: - path: CHANGELOG.md scope: project note: Regenerated unreleased security entry + - path: README.md + scope: project - path: scripts/dependabot-merge.ts scope: project - path: test/dependabot-merge.test.ts diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 0046c361..dda4ba96 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -54,3 +54,10 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T11:10:00.551Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/38","value":{"created_at":"2026-11-05T11:10:00.550Z","author":"codex-agent","text":"Cutoff receipt for source 48abcd82b0c0e18e2b2409a631030e984d445af8: complete bun run release:check PASSED on Node 26 at 11:07:47 UTC, job https://github.com/unbraind/pm-github/actions/runs/42012432073/job/127194531337 . Downloaded completed-job logs verify 1754 tests: 1752 passed, zero failed/cancelled, two pre-existing SDK 2026.11.4 lifecycle-compensation quarantines for pm-cli#1656. Full inventory: 30 authored modules at exact 100/100/100/100 coverage, zero production audit vulnerabilities and all remaining release steps passed. At the 30-minute observation cutoff, full npm gates on Node 22/26 and Bun on Node 22 were still running: https://github.com/unbraind/pm-github/actions/runs/42012432073 . Those three are pending, not passing evidence; no further CI polling. CodeQL and Semgrep passed. Local Node 26 full npm attempt passed prechecks and privacy, then progressed without reported failures through real closeout fixtures, including the previously problematic first scenario after fresh npm ci. Stopped its owned process tree at handoff; this unfinished attempt is not a pass. No local test/watch process remains. Separate production audit, package dry run/build, changelog check and publication attestation passed. No dependency, threshold or quarantine changes were made."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:10:00.551Z"}],"before_hash":"200c1f6de8475b1dced688c31dd61bd4c4ce046da07cb246ddac3b10335a6e04","after_hash":"ae8269f563d7b920b186c68fa3a1d5f1a2c32294e065e2d5dbfc94d05a9724e0","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":"aae01047c03f6ee81b991472e62c0c3a4ecb8f7e07387651f01ec2e6ca95a8cc"} {"hash_algorithm":"sha256","ts":"2026-11-05T11:10:06.900Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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/39","value":{"created_at":"2026-11-05T11:10:06.899Z","author":"codex-agent","text":"Final review refresh found only original unresolved thread PRRT_kwDOTbN4pM6z_gYw and no newer CodeRabbit review than 09:03:38 UTC. Its tracker fix and both outside-diff security fixes are committed in 48abcd8. Four isolated revert variants produced 1, 1, 1 and 2 expected failures; restored suite passed 105. Both linked Node/Bun commands passed 105 each. No review replies/resolutions, mentions, merges, releases, workflow-state changes or secret changes. No new PM CLI/SDK bug was established. Final receipt commit changes tracker evidence only, so prior source-head CI is not final-head approval: fresh final-head CI remains required. Releasing only my feature claim and leaving both items open for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:10:06.900Z"}],"before_hash":"ae8269f563d7b920b186c68fa3a1d5f1a2c32294e065e2d5dbfc94d05a9724e0","after_hash":"bea4b234a928f4d88d500b80cab1224f5cb6d9a51fafe42a9873e030eac8a14c","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":"810407895e6498350b7f73aaea600448641bf387e8def36ab6f6b6c7cdc6de4d"} {"hash_algorithm":"sha256","ts":"2026-11-05T11:10:12.745Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"10a9898a1f703e00b80e65ec","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-05T11:10:12.745Z"}],"before_hash":"bea4b234a928f4d88d500b80cab1224f5cb6d9a51fafe42a9873e030eac8a14c","after_hash":"2d222f6e6c20ddab9e42eb73c0ea4ba865ed74830be125e704b485cda0ff1db3","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":"dc30e65b5f0b709edbd9791f749ab52870ab436751adb538668f34c5efc213f7"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:18:09.057Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T11:18:09.057Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"2d222f6e6c20ddab9e42eb73c0ea4ba865ed74830be125e704b485cda0ff1db3","after_hash":"623899c43303c3bba0d270e95bc3e72ac57e9d453de35a5a6d7c9c9c799902cd","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":"865ef5c33046e448df0b62d01bf32e72715900971faf0b1086651bf1dba61daa"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:18:15.877Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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/40","value":{"created_at":"2026-11-05T11:18:15.876Z","author":"codex-agent","text":"Review round 3 at 4a77bc0: pulled remote head; origin/main is already contained. Complete GraphQL pagination reports four resolved threads and zero unresolved threads, with all comment pages complete. Latest nonempty CodeRabbit review is 09:03:38 UTC; its two outside-diff event/trust findings are already covered by trusted pull_request_target and independent scheduled/manual audit tests. Orchestrator load finding is valid: both caller and README template use a five-minute schedule. Change both to daily plus workflow_dispatch, document the hourly fleet ceiling, and enforce parsed minute/hour intervals including five-minute, fifteen-minute, hourly and daily examples. Self-review the full PR for matching schedule/template defects; record isolated revert proofs and run unchanged release gates serially."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:18:15.877Z"}],"before_hash":"623899c43303c3bba0d270e95bc3e72ac57e9d453de35a5a6d7c9c9c799902cd","after_hash":"e1847f45682a6b8a9975b4e0dc3d7da19355dd95943783dd480a861f9becd7fb","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":"da52c0d30de95faa13ade4ad8d8cd1a9155daf7a22bd6ef4cde4fc084bb643ee"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:19:58.407Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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/5/path","value":"scripts/dependabot-merge.ts"},{"op":"replace","path":"/metadata/files/4/path","value":"README.md"},{"op":"add","path":"/metadata/files/6","value":{"path":"test/dependabot-merge.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:19:58.407Z"}],"before_hash":"e1847f45682a6b8a9975b4e0dc3d7da19355dd95943783dd480a861f9becd7fb","after_hash":"78c9fb65456c6b1118765e1c54f42d0190d6594a1fda6ff239b57fa41ea56ff9","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":"859f20a7ca690427b02d090697948126a5f2acab85f3a4c1b34d9004fbbbef96"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:20:22.374Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T11:20:22.374Z","author":"codex-agent","text":"Implemented daily 04:23 UTC audit plus workflow_dispatch in both the real caller and README template; fleet ceiling is explicitly hourly with daily preferred and staggered times advised. Added numeric cron minute/hour expansion (lists, ranges, wildcards, steps), circular day spacing and aggregate entry checks: five-minute and fifteen-minute schedules refuse; hourly and daily schedules pass. Isolated non-git copy revert proofs with unchanged new tests: restoring only caller five-minute cron fails 1/1; restoring only README five-minute template fails 1/1; removing only hourly documentation fails 1/1. Restored fixed copy passes 1/1. Full PR self-review found no other schedule/template instances. Initial 115-case focused run passed 114 with only the existing 500-ms CLI child-start timing test failing under host load (counter 2 instead of 3); retry through linked pm runner is pending. Typecheck, lint, docstrings (272 declarations) and zero-threshold duplication pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:20:22.374Z"}],"before_hash":"78c9fb65456c6b1118765e1c54f42d0190d6594a1fda6ff239b57fa41ea56ff9","after_hash":"1e541b21c3f178ce4e2bba14d7546aba6e9a54166a8fe16a2254d0a53261a58e","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":"8d97dba3651e6f59a99114b2e4e59cb9e34dd837e6bb48014b5f9eef4645010e"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:20:42.703Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T11:20:42.702Z","author":"codex-agent","text":"Pinned pm test --run --progress passed both linked commands: Node 115/115 and native Bun 115/115, zero failures/skips. The initial host-load timeout counter failure passed on retry without source changes. Existing test-result tracking remains disabled; outcomes are recorded here. Starting full npm run release:check then bun run release:check serially with isolated Git configuration and HOME, public npm configuration, inherited credential/session variables removed, unchanged thresholds and two-worker concurrency. No dist files are committed in this repository."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:20:42.703Z"}],"before_hash":"1e541b21c3f178ce4e2bba14d7546aba6e9a54166a8fe16a2254d0a53261a58e","after_hash":"5d6a8ecb32b08233bf17f47dedfe1fd26e033e6603ccb67299c737ec71701f12","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":"2893c75731f9f3ff4bb08d368f8a766ad086f335977c412e69bb7c802a522294"} +{"hash_algorithm":"sha256","ts":"2026-11-05T11:40:11.954Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T11:40:11.952Z","author":"codex-agent","text":"Full local npm release validation is progressing through the real workflow fixture corpus with no reported test failures so far, including the previously problematic scheduled closeout fixture. Prechecks and the object-store privacy audit passed. The complete gate is still pending; Bun full release validation remains queued serially. This is progress evidence only, not a gate pass. No dependencies, thresholds, runtime services or live workflow settings changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:40:11.954Z"}],"before_hash":"5d6a8ecb32b08233bf17f47dedfe1fd26e033e6603ccb67299c737ec71701f12","after_hash":"ff7701f2743d8bb448b9e12ec295b7d2101f9be803e0218b1b03f8ffb92c071c","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":"dee719094ec80c57792abc5bbf3df3607dd8d5e1837b362741b5ea133b16d629"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:02:03.387Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:02:03.387Z","author":"codex-agent","text":"Source changes are ready for review: three isolated reverts fail as expected; linked Node and native Bun suites each pass 115/115; test typecheck, lint, documented declarations, zero-threshold duplication and strict tracker health pass. Local full npm release remains in progress through slow workflow fixtures with no reported failures so far; full Bun is queued serially. Preparing a source push to start exact-head CI for both required complete release commands on the supported Node lines. No incomplete gate is counted as passing; continue one local gate only and observe the 30-minute CI cutoff."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:02:03.387Z"}],"before_hash":"ff7701f2743d8bb448b9e12ec295b7d2101f9be803e0218b1b03f8ffb92c071c","after_hash":"b15157a96555097eb6c09c2047223c46f1c89c4de9f62e0966a1fec52813f312","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":"a490468e85a2796a6271725849363370c4fb1b2a88db8a21dd3efdf7102592f8"} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index ac7e52eb..89e7c380 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -6,8 +6,9 @@ on: types: [opened, reopened, synchronize, ready_for_review, edited] # Independent of GITHUB_TOKEN-generated synchronize runs awaiting approval. + # Daily plus manual dispatch; fleet callers must not schedule more than hourly. schedule: - - cron: "3-58/5 * * * *" + - cron: "23 4 * * *" workflow_dispatch: permissions: {} diff --git a/README.md b/README.md index 50ddbe8e..e6ee1485 100644 --- a/README.md +++ b/README.md @@ -859,7 +859,7 @@ on: pull_request_target: types: [opened, reopened, synchronize, ready_for_review, edited] schedule: - - cron: "3-58/5 * * * *" + - cron: "23 4 * * *" workflow_dispatch: permissions: {} jobs: @@ -935,9 +935,12 @@ trigger it for every open PR. This repository uses the supported local path. Keep the scheduled audit job when adopting the caller. GitHub can suppress workflow-generated events or park their `pull_request` runs awaiting approval; changing only the PR trigger does not establish an independent revocation path. -The five-minute schedule and manual dispatch audit every armed Dependabot PR, +The daily schedule and manual dispatch audit every armed Dependabot PR, including every page of its commits, with the same provenance validator used to arm. Unsafe or unverifiable snapshots are revoked; valid requests are preserved. +Fleet callers must not schedule the audit more often than hourly; prefer daily +plus `workflow_dispatch` to preserve shared runner capacity. Stagger daily run +times across repositories. Use manual dispatch when an immediate sweep is needed. Audits never arm requests or depend on PR actor/metadata. Listing, policy-load or revocation failures fail visibly. Scheduled runs can be delayed or disabled by GitHub, so this is eventual revocation and requires operational monitoring, diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 7ab3c82d..394fcd72 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -417,12 +417,86 @@ test("independent audit preserves verified requests and never arms them", () => assert.ok(!a.f.calls.some(args => args[0] === "pr")); }); +/** Expand a numeric cron field so frequency checks cover lists, ranges and steps. */ +function cronField(field: string, maximum: number): number[] { + const values = new Set(); + for (const part of field.split(",")) { + const match = /^(\*|\d+(?:-\d+)?)(?:\/(\d+))?$/.exec(part); + assert.ok(match, `Unsupported cron field: ${field}`); + const step = Number(match[2] ?? 1); + const range = match[1]!.split("-").map(Number); + const start = match[1] === "*" ? 0 : range[0]!; + const end = match[1] === "*" || (range.length === 1 && match[2]) ? maximum : range.at(-1)!; + assert.ok(step >= 1 && step <= maximum + 1 && start >= 0 && start <= end && end <= maximum, `Invalid cron field: ${field}`); + for (let value = start; value <= end; value += step) values.add(value); + } + return [...values]; +} + +/** Refuse caller schedules whose minute/hour fields permit runs less than an hour apart. */ +function assertHourlyAuditCeiling(schedules: readonly { cron: string }[]): void { + const times: number[] = []; + for (const { cron } of schedules) { + const fields = cron.trim().split(/\s+/); + assert.equal(fields.length, 5, "Expected five cron fields"); + const minutes = cronField(fields[0]!, 59); + const hours = cronField(fields[1]!, 23); + for (const hour of hours) for (const minute of minutes) times.push(hour * 60 + minute); + } + times.sort((a, b) => a - b); + assert.ok(times.length > 0, "Expected an audit schedule"); + for (let i = 0; i < times.length; i++) { + const next = times[i + 1] ?? times[0]! + 24 * 60; + assert.ok(next - times[i]! >= 60, "Fleet audit schedules must not run more often than hourly"); + } +} + +for (const [name, cron, allowed] of [ + ["five-minute", "3-58/5 * * * *", false], + ["fifteen-minute", "*/15 * * * *", false], + ["hourly", "23 * * * *", true], + ["daily", "23 4 * * *", true], + ["minute-list", "0,30 * * * *", false], + ["cross-midnight", "0,59 0,23 * * *", false], + ["two-hour-range", "23 0-22/2 * * *", true], + ["stepped-start", "5/15 * * * *", false], +] as const) { + test(`caller schedule policy: ${name}`, () => { + const check = () => assertHourlyAuditCeiling([{ cron }]); + if (allowed) check(); + else assert.throws(check, /must not run more often than hourly/); + }); +} + +test("caller schedule policy combines all entries and rejects malformed fields", () => { + assert.throws(() => assertHourlyAuditCeiling([{ cron: "0 * * * *" }, { cron: "30 * * * *" }]), /more often than hourly/); + for (const cron of ["@daily", "60 * * * *", "0 24 * * *", "*/0 * * * *", "4-2 * * * *", "bad * * * *"]) { + assert.throws(() => assertHourlyAuditCeiling([{ cron }])); + } + assert.throws(() => assertHourlyAuditCeiling([]), /Expected an audit schedule/); +}); + +test("caller and README template keep audits daily and never more often than hourly", () => { + const readme = readFileSync(join(root, "README.md"), "utf8"); + const section = readme.split("## Verified Dependabot auto-merge\n")[1]!.split("## ")[0]!; + const template = section.split("```yaml\n")[1]!.split("```")[0]!; + for (const source of [readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8"), template]) { + const caller = parse(source) as { on: { schedule: { cron: string }[]; workflow_dispatch: unknown } }; + assertHourlyAuditCeiling(caller.on.schedule); + assert.deepEqual(caller.on.schedule, [{ cron: "23 4 * * *" }]); + assert.ok(Object.hasOwn(caller.on, "workflow_dispatch")); + } + assert.match(section, /Fleet callers must not schedule the audit more often than hourly/); + assert.match(section, /prefer daily\s+plus `workflow_dispatch`/); + assert.doesNotMatch(section, /five-minute schedule/); +}); + test("scheduled audit revokes a foreign push when its synchronize run never executes", () => { const caller = parse(readFileSync(join(root, ".github/workflows/dependabot-auto-merge.yml"), "utf8")) as { on: { schedule: { cron: string }[]; workflow_dispatch: unknown; pull_request_target: { types: string[] } }; jobs: Record; }; - assert.equal(caller.on.schedule[0]!.cron, "3-58/5 * * * *"); + assertHourlyAuditCeiling(caller.on.schedule); assert.ok(Object.hasOwn(caller.on, "workflow_dispatch")); assert.equal(caller.jobs["audit-armed-requests"]!.if, "github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"); assert.equal(caller.jobs["audit-armed-requests"]!.with!["audit-only"], true); From b4b9cbeb463409b5790fd84fc386ca05744a1cc3 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 13:30:25 +0100 Subject: [PATCH 15/19] docs(pm): record audit policy proofs and pending release gates --- .../features/pm-github-dependabot-merge.toon | 18 +++++++++++++----- .../history/pm-github-dependabot-merge.jsonl | 4 ++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 0fea959b..47f98473 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,10 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T12:02:03.387Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-11-05T12:29:33.037Z" author: codex-agent acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." -comments[45]{created_at,author,text}: +comments[47]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -57,6 +55,8 @@ comments[45]{created_at,author,text}: "2026-11-05T11:20:42.702Z",codex-agent,"Pinned pm test --run --progress passed both linked commands: Node 115/115 and native Bun 115/115, zero failures/skips. The initial host-load timeout counter failure passed on retry without source changes. Existing test-result tracking remains disabled; outcomes are recorded here. Starting full npm run release:check then bun run release:check serially with isolated Git configuration and HOME, public npm configuration, inherited credential/session variables removed, unchanged thresholds and two-worker concurrency. No dist files are committed in this repository." "2026-11-05T11:40:11.952Z",codex-agent,"Full local npm release validation is progressing through the real workflow fixture corpus with no reported test failures so far, including the previously problematic scheduled closeout fixture. Prechecks and the object-store privacy audit passed. The complete gate is still pending; Bun full release validation remains queued serially. This is progress evidence only, not a gate pass. No dependencies, thresholds, runtime services or live workflow settings changed." "2026-11-05T12:02:03.387Z",codex-agent,"Source changes are ready for review: three isolated reverts fail as expected; linked Node and native Bun suites each pass 115/115; test typecheck, lint, documented declarations, zero-threshold duplication and strict tracker health pass. Local full npm release remains in progress through slow workflow fixtures with no reported failures so far; full Bun is queued serially. Preparing a source push to start exact-head CI for both required complete release commands on the supported Node lines. No incomplete gate is counted as passing; continue one local gate only and observe the 30-minute CI cutoff." + "2026-11-05T12:28:02.039Z",codex-agent,"Final standalone evidence for source 37049e008505c46880c205c828f7b9faee8129ed: privacy audited 8917 Git objects with all identities approved and no secret/host-path findings; production audit found zero vulnerabilities; package dry run rebuilt and enumerated 75 package files; publication attestation verified the single publish invocation carries provenance. These commands passed, as did the changelog check. Added an exact focused caller-policy linked command and ran it through pinned pm: 11/11 tests, zero failures. Existing linked full focused suites passed Node 115/115 and native Bun 115/115. Tracking-disabled policy is unchanged. Latest complete review refresh still finds zero unresolved threads and no newer nonempty CodeRabbit review. Full npm/Bun release approval remains pending: local npm coverage is unfinished under host load and all four source CI release jobs remain queued at the last observation. No incomplete or queued check is counted as passing." + "2026-11-05T12:29:29.429Z",codex-agent,"Handoff receipt: schedule fix is committed and pushed in 37049e008505c46880c205c828f7b9faee8129ed. Three isolated configuration/documentation reverts each fail one regression; restored copy passes, and linked Node/native Bun suites pass 115/115 each plus the focused caller-policy command 11/11. Initial 500-ms CLI timeout-counter failure passed on retry without code changes. Full release gates completed this round: 0/2. The isolated local npm release attempt passed prechecks/privacy and continued through real workflow cases without reported failures, then its verified owned process tree was stopped to reduce host pressure near handoff; this interrupted attempt is not a pass. Local Bun full gate did not start because runs were serial. Source CI https://github.com/unbraind/pm-github/actions/runs/42023811823 last reported Bun on Node 26 in progress and npm on Node 22/26 plus Bun on Node 22 queued. CodeQL https://github.com/unbraind/pm-github/actions/runs/42023811850 was queued. Preserve these pending checks at the original 30-minute cutoff; fresh final-head CI is required after the tracker receipt push. All tracker writes finish before that final-head verification. No owned local test/watch process remains. No new PM CLI/SDK defect was established. No thread replies/resolutions, mentions, merges, releases, secret or live workflow-setting changes were performed. Releasing only my feature claim; the item stays open for orchestrator verification, with live/fleet acceptance separate." files[7]: - path: .agents/pm/chores/pm-github-dependabot-activation.toon scope: project @@ -73,7 +73,7 @@ files[7]: scope: project - path: test/dependabot-merge.test.ts scope: project -tests[2]: +tests[3]: - command: nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts scope: project timeout_seconds: 120 @@ -89,6 +89,14 @@ tests[2]: created_at: "2026-11-05T08:18:21.359Z" source_kind: local_mutation source_ref: feat/reusable-dependabot-merge + - command: nice -n 10 node --test --test-concurrency=2 --test-name-pattern=caller test/dependabot-merge.test.ts + scope: project + timeout_seconds: 120 + provenance: + author: codex-agent + created_at: "2026-11-05T12:22:38.534Z" + source_kind: local_mutation + source_ref: feat/reusable-dependabot-merge docs[1]{path,scope,note}: README.md,project,Caller migration token requirements and asynchronous auto-merge limits body: "" diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index dda4ba96..5e6e5ba6 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -61,3 +61,7 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T11:20:42.703Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T11:20:42.702Z","author":"codex-agent","text":"Pinned pm test --run --progress passed both linked commands: Node 115/115 and native Bun 115/115, zero failures/skips. The initial host-load timeout counter failure passed on retry without source changes. Existing test-result tracking remains disabled; outcomes are recorded here. Starting full npm run release:check then bun run release:check serially with isolated Git configuration and HOME, public npm configuration, inherited credential/session variables removed, unchanged thresholds and two-worker concurrency. No dist files are committed in this repository."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:20:42.703Z"}],"before_hash":"1e541b21c3f178ce4e2bba14d7546aba6e9a54166a8fe16a2254d0a53261a58e","after_hash":"5d6a8ecb32b08233bf17f47dedfe1fd26e033e6603ccb67299c737ec71701f12","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":"2893c75731f9f3ff4bb08d368f8a766ad086f335977c412e69bb7c802a522294"} {"hash_algorithm":"sha256","ts":"2026-11-05T11:40:11.954Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T11:40:11.952Z","author":"codex-agent","text":"Full local npm release validation is progressing through the real workflow fixture corpus with no reported test failures so far, including the previously problematic scheduled closeout fixture. Prechecks and the object-store privacy audit passed. The complete gate is still pending; Bun full release validation remains queued serially. This is progress evidence only, not a gate pass. No dependencies, thresholds, runtime services or live workflow settings changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T11:40:11.954Z"}],"before_hash":"5d6a8ecb32b08233bf17f47dedfe1fd26e033e6603ccb67299c737ec71701f12","after_hash":"ff7701f2743d8bb448b9e12ec295b7d2101f9be803e0218b1b03f8ffb92c071c","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":"dee719094ec80c57792abc5bbf3df3607dd8d5e1837b362741b5ea133b16d629"} {"hash_algorithm":"sha256","ts":"2026-11-05T12:02:03.387Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:02:03.387Z","author":"codex-agent","text":"Source changes are ready for review: three isolated reverts fail as expected; linked Node and native Bun suites each pass 115/115; test typecheck, lint, documented declarations, zero-threshold duplication and strict tracker health pass. Local full npm release remains in progress through slow workflow fixtures with no reported failures so far; full Bun is queued serially. Preparing a source push to start exact-head CI for both required complete release commands on the supported Node lines. No incomplete gate is counted as passing; continue one local gate only and observe the 30-minute CI cutoff."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:02:03.387Z"}],"before_hash":"ff7701f2743d8bb448b9e12ec295b7d2101f9be803e0218b1b03f8ffb92c071c","after_hash":"b15157a96555097eb6c09c2047223c46f1c89c4de9f62e0966a1fec52813f312","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":"a490468e85a2796a6271725849363370c4fb1b2a88db8a21dd3efdf7102592f8"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:22:39.022Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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/2","value":{"command":"nice -n 10 node --test --test-concurrency=2 --test-name-pattern=caller test/dependabot-merge.test.ts","scope":"project","timeout_seconds":120,"provenance":{"author":"codex-agent","created_at":"2026-11-05T12:22:38.534Z","source_kind":"local_mutation","source_ref":"feat/reusable-dependabot-merge"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:22:39.022Z"}],"before_hash":"b15157a96555097eb6c09c2047223c46f1c89c4de9f62e0966a1fec52813f312","after_hash":"5936ce8e17ecf2df2fccf0965678bea7cc10e768d993902eeb3f02bd86db48e1","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":"f9a31671368e30048944b2ecfe0bf1867c62ae45e0b4a170c11081ba2e7f9e16"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:28:02.051Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:28:02.039Z","author":"codex-agent","text":"Final standalone evidence for source 37049e008505c46880c205c828f7b9faee8129ed: privacy audited 8917 Git objects with all identities approved and no secret/host-path findings; production audit found zero vulnerabilities; package dry run rebuilt and enumerated 75 package files; publication attestation verified the single publish invocation carries provenance. These commands passed, as did the changelog check. Added an exact focused caller-policy linked command and ran it through pinned pm: 11/11 tests, zero failures. Existing linked full focused suites passed Node 115/115 and native Bun 115/115. Tracking-disabled policy is unchanged. Latest complete review refresh still finds zero unresolved threads and no newer nonempty CodeRabbit review. Full npm/Bun release approval remains pending: local npm coverage is unfinished under host load and all four source CI release jobs remain queued at the last observation. No incomplete or queued check is counted as passing."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:28:02.051Z"}],"before_hash":"5936ce8e17ecf2df2fccf0965678bea7cc10e768d993902eeb3f02bd86db48e1","after_hash":"7bd3c61ffc66c578fd1e76413e5fb920c093e111f4d14582578cc734157b41a0","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":"39428db0e87636dd01d19d2f9ef2bdef0a5b645506442e38edc34344f572da38"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:29:29.430Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:29:29.429Z","author":"codex-agent","text":"Handoff receipt: schedule fix is committed and pushed in 37049e008505c46880c205c828f7b9faee8129ed. Three isolated configuration/documentation reverts each fail one regression; restored copy passes, and linked Node/native Bun suites pass 115/115 each plus the focused caller-policy command 11/11. Initial 500-ms CLI timeout-counter failure passed on retry without code changes. Full release gates completed this round: 0/2. The isolated local npm release attempt passed prechecks/privacy and continued through real workflow cases without reported failures, then its verified owned process tree was stopped to reduce host pressure near handoff; this interrupted attempt is not a pass. Local Bun full gate did not start because runs were serial. Source CI https://github.com/unbraind/pm-github/actions/runs/42023811823 last reported Bun on Node 26 in progress and npm on Node 22/26 plus Bun on Node 22 queued. CodeQL https://github.com/unbraind/pm-github/actions/runs/42023811850 was queued. Preserve these pending checks at the original 30-minute cutoff; fresh final-head CI is required after the tracker receipt push. All tracker writes finish before that final-head verification. No owned local test/watch process remains. No new PM CLI/SDK defect was established. No thread replies/resolutions, mentions, merges, releases, secret or live workflow-setting changes were performed. Releasing only my feature claim; the item stays open for orchestrator verification, with live/fleet acceptance separate."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:29:29.430Z"}],"before_hash":"7bd3c61ffc66c578fd1e76413e5fb920c093e111f4d14582578cc734157b41a0","after_hash":"316a1c8bf94f2dc8262bef40cbdaacbc166651ac438fae482919db3e6143f49e","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":"93fe3854ab1f4daef191fb232d1fc1b8f44dba642a79219728c1ee2f5f608d82"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:29:33.037Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:29:33.037Z"}],"before_hash":"316a1c8bf94f2dc8262bef40cbdaacbc166651ac438fae482919db3e6143f49e","after_hash":"fb5841d4d495e888233779ceb21474c12a2b5f469cbf5aed08b0b3ad4b62dbc2","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":"951550bedf3e5e863bdb9951428185fa24c8916ddcefe08be45c96a9d0942125"} From 028db825bfd4d055ecf2949556c504b965715b4b Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 14:06:12 +0100 Subject: [PATCH 16/19] test: make Dependabot timeout receipts independent of startup load --- .../features/pm-github-dependabot-merge.toon | 11 +- .../history/pm-github-dependabot-merge.jsonl | 6 + .../workflows/pm-github-dependabot-merge.yml | 16 +- README.md | 5 +- scripts/dependabot-merge.ts | 20 ++- test/dependabot-merge.test.ts | 148 +++++++++++++++--- 6 files changed, 174 insertions(+), 32 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 47f98473..39d9113a 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,10 +6,12 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T12:29:33.037Z" +updated_at: "2026-11-05T13:05:53.863Z" +assignee: codex-agent +claim_principal: codex-agent author: codex-agent acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." -comments[47]{created_at,author,text}: +comments[52]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -57,6 +59,11 @@ comments[47]{created_at,author,text}: "2026-11-05T12:02:03.387Z",codex-agent,"Source changes are ready for review: three isolated reverts fail as expected; linked Node and native Bun suites each pass 115/115; test typecheck, lint, documented declarations, zero-threshold duplication and strict tracker health pass. Local full npm release remains in progress through slow workflow fixtures with no reported failures so far; full Bun is queued serially. Preparing a source push to start exact-head CI for both required complete release commands on the supported Node lines. No incomplete gate is counted as passing; continue one local gate only and observe the 30-minute CI cutoff." "2026-11-05T12:28:02.039Z",codex-agent,"Final standalone evidence for source 37049e008505c46880c205c828f7b9faee8129ed: privacy audited 8917 Git objects with all identities approved and no secret/host-path findings; production audit found zero vulnerabilities; package dry run rebuilt and enumerated 75 package files; publication attestation verified the single publish invocation carries provenance. These commands passed, as did the changelog check. Added an exact focused caller-policy linked command and ran it through pinned pm: 11/11 tests, zero failures. Existing linked full focused suites passed Node 115/115 and native Bun 115/115. Tracking-disabled policy is unchanged. Latest complete review refresh still finds zero unresolved threads and no newer nonempty CodeRabbit review. Full npm/Bun release approval remains pending: local npm coverage is unfinished under host load and all four source CI release jobs remain queued at the last observation. No incomplete or queued check is counted as passing." "2026-11-05T12:29:29.429Z",codex-agent,"Handoff receipt: schedule fix is committed and pushed in 37049e008505c46880c205c828f7b9faee8129ed. Three isolated configuration/documentation reverts each fail one regression; restored copy passes, and linked Node/native Bun suites pass 115/115 each plus the focused caller-policy command 11/11. Initial 500-ms CLI timeout-counter failure passed on retry without code changes. Full release gates completed this round: 0/2. The isolated local npm release attempt passed prechecks/privacy and continued through real workflow cases without reported failures, then its verified owned process tree was stopped to reduce host pressure near handoff; this interrupted attempt is not a pass. Local Bun full gate did not start because runs were serial. Source CI https://github.com/unbraind/pm-github/actions/runs/42023811823 last reported Bun on Node 26 in progress and npm on Node 22/26 plus Bun on Node 22 queued. CodeQL https://github.com/unbraind/pm-github/actions/runs/42023811850 was queued. Preserve these pending checks at the original 30-minute cutoff; fresh final-head CI is required after the tracker receipt push. All tracker writes finish before that final-head verification. No owned local test/watch process remains. No new PM CLI/SDK defect was established. No thread replies/resolutions, mentions, merges, releases, secret or live workflow-setting changes were performed. Releasing only my feature claim; the item stays open for orchestrator verification, with live/fleet acceptance separate." + "2026-11-05T12:49:58.199Z",codex-agent,"Review round 4: pulled remote b4b9cbe; origin/main is contained. Complete GraphQL review-thread and comment pagination finds exactly PRRT_kwDOTbN4pM60Ecwk and PRRT_kwDOTbN4pM60Ecw_ unresolved. Latest nonempty CodeRabbit review has no outside-diff findings. Both startup timing races are valid. Replace elapsed startup deadlines with deterministic timeout/kill receipts, retain default 60-second bounded calls with injectable configuration, and cover retry counter failure plus every shell hang path. Review full diff for related timing defects; record behavioral reverts in disposable copies, run unchanged npm and Bun release gates serially, then release claim without closure." + "2026-11-05T12:55:07.192Z",codex-agent,"Implemented runtime deadline binding DEPENDABOT_GH_TIMEOUT_SECONDS (default 60, positive integer at most 60) in both workflow shells and the TypeScript adapter, retaining three fail-closed attempts and SIGKILL. Shell fixtures now block on an open pipe, signal readiness, and are killed only after readiness; all five hang paths require three actual SIGKILL close receipts. Node timeout attempts are counted before child startup and verify ETIMEDOUT plus SIGKILL for each permanently blocking child, eliminating the former startup counter race. Retry/exhaustion tests retain partial-output checks with the production deadline. Self-reviewed the entire PR diff for matching clocks, startup counters and shell timeout paths; added default/override and refusal-before-call corpora." + "2026-11-05T12:55:10.793Z",codex-agent,"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: reverting only bounded_gh deadline use to hardcoded 60s fails all 5 selected loader/revocation hang cases; restoring only githubCli from b4b9cbe fails both selected CLI deadline/kill cases; reverting only its kill signal to SIGTERM fails the blocking-child test. Fixed implementation restored in finally; all 7 selected tests then pass. Initial full focused Node and native Bun runs each passed 116/116; expanded default and invalid-shell configuration corpus is now being checked. No package-worktree production function was temporarily reverted." + "2026-11-05T12:56:42.028Z",codex-agent,"Final focused validation passed through pinned pm test --run --progress: Node 118/118, native Bun 118/118, caller-policy subset 11/11; no failures or skips. Existing tracker policy disables automatic test-result persistence, so receipts are recorded here. Test typecheck and lint pass; docstrings cover 274 declarations. c8 policy-only run has exact 100/100/100/100. All changed files are already linked to this item. Starting complete npm then Bun release gates serially under Node 26 with isolated HOME/Git config, an allowlisted credential-free child environment, public npm configuration, cache preference and unchanged coverage/duplication thresholds. No dist files are tracked by this repository." + "2026-11-05T13:05:53.861Z",codex-agent,"Source ready for review: linked Node/native Bun focused commands passed 118/118 each and caller subset 11/11. Three isolated behavioral reverts fail as expected, then restored tests pass. Full local npm release passed prechecks and privacy (8925 objects) and is progressing through the existing integration corpus without reported failures; Bun full release remains serially queued. Push source plus this tracker evidence now to start exact-head CI while local gates continue. Neither unfinished gate is counted as passing." files[7]: - path: .agents/pm/chores/pm-github-dependabot-activation.toon scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 5e6e5ba6..398b94d8 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -65,3 +65,9 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T12:28:02.051Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:28:02.039Z","author":"codex-agent","text":"Final standalone evidence for source 37049e008505c46880c205c828f7b9faee8129ed: privacy audited 8917 Git objects with all identities approved and no secret/host-path findings; production audit found zero vulnerabilities; package dry run rebuilt and enumerated 75 package files; publication attestation verified the single publish invocation carries provenance. These commands passed, as did the changelog check. Added an exact focused caller-policy linked command and ran it through pinned pm: 11/11 tests, zero failures. Existing linked full focused suites passed Node 115/115 and native Bun 115/115. Tracking-disabled policy is unchanged. Latest complete review refresh still finds zero unresolved threads and no newer nonempty CodeRabbit review. Full npm/Bun release approval remains pending: local npm coverage is unfinished under host load and all four source CI release jobs remain queued at the last observation. No incomplete or queued check is counted as passing."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:28:02.051Z"}],"before_hash":"5936ce8e17ecf2df2fccf0965678bea7cc10e768d993902eeb3f02bd86db48e1","after_hash":"7bd3c61ffc66c578fd1e76413e5fb920c093e111f4d14582578cc734157b41a0","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":"39428db0e87636dd01d19d2f9ef2bdef0a5b645506442e38edc34344f572da38"} {"hash_algorithm":"sha256","ts":"2026-11-05T12:29:29.430Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:29:29.429Z","author":"codex-agent","text":"Handoff receipt: schedule fix is committed and pushed in 37049e008505c46880c205c828f7b9faee8129ed. Three isolated configuration/documentation reverts each fail one regression; restored copy passes, and linked Node/native Bun suites pass 115/115 each plus the focused caller-policy command 11/11. Initial 500-ms CLI timeout-counter failure passed on retry without code changes. Full release gates completed this round: 0/2. The isolated local npm release attempt passed prechecks/privacy and continued through real workflow cases without reported failures, then its verified owned process tree was stopped to reduce host pressure near handoff; this interrupted attempt is not a pass. Local Bun full gate did not start because runs were serial. Source CI https://github.com/unbraind/pm-github/actions/runs/42023811823 last reported Bun on Node 26 in progress and npm on Node 22/26 plus Bun on Node 22 queued. CodeQL https://github.com/unbraind/pm-github/actions/runs/42023811850 was queued. Preserve these pending checks at the original 30-minute cutoff; fresh final-head CI is required after the tracker receipt push. All tracker writes finish before that final-head verification. No owned local test/watch process remains. No new PM CLI/SDK defect was established. No thread replies/resolutions, mentions, merges, releases, secret or live workflow-setting changes were performed. Releasing only my feature claim; the item stays open for orchestrator verification, with live/fleet acceptance separate."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:29:29.430Z"}],"before_hash":"7bd3c61ffc66c578fd1e76413e5fb920c093e111f4d14582578cc734157b41a0","after_hash":"316a1c8bf94f2dc8262bef40cbdaacbc166651ac438fae482919db3e6143f49e","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":"93fe3854ab1f4daef191fb232d1fc1b8f44dba642a79219728c1ee2f5f608d82"} {"hash_algorithm":"sha256","ts":"2026-11-05T12:29:33.037Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"30a25d6cecfa63ddf34964dd","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-05T12:29:33.037Z"}],"before_hash":"316a1c8bf94f2dc8262bef40cbdaacbc166651ac438fae482919db3e6143f49e","after_hash":"fb5841d4d495e888233779ceb21474c12a2b5f469cbf5aed08b0b3ad4b62dbc2","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":"951550bedf3e5e863bdb9951428185fa24c8916ddcefe08be45c96a9d0942125"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:49:50.278Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T12:49:50.278Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"fb5841d4d495e888233779ceb21474c12a2b5f469cbf5aed08b0b3ad4b62dbc2","after_hash":"87cd6b116c672b9fd8658eb4bb27a11b9c5f652a9d2b73248bd45d2a46d0218f","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":"11eca7d1b15627b3ea190482d987089a6aa40723281e5572b720e3668f9ac86a"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:49:58.202Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T12:49:58.199Z","author":"codex-agent","text":"Review round 4: pulled remote b4b9cbe; origin/main is contained. Complete GraphQL review-thread and comment pagination finds exactly PRRT_kwDOTbN4pM60Ecwk and PRRT_kwDOTbN4pM60Ecw_ unresolved. Latest nonempty CodeRabbit review has no outside-diff findings. Both startup timing races are valid. Replace elapsed startup deadlines with deterministic timeout/kill receipts, retain default 60-second bounded calls with injectable configuration, and cover retry counter failure plus every shell hang path. Review full diff for related timing defects; record behavioral reverts in disposable copies, run unchanged npm and Bun release gates serially, then release claim without closure."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:49:58.202Z"}],"before_hash":"87cd6b116c672b9fd8658eb4bb27a11b9c5f652a9d2b73248bd45d2a46d0218f","after_hash":"f1a694994d3c0718047e427f767f2591fff7edca7016c92a931ef597a643fca9","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":"7a3e8f7e6b8b4adc0ac7e33ec47100505cfe7b03cb0ca5c7414e2290d0246fb3"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:55:07.192Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T12:55:07.192Z","author":"codex-agent","text":"Implemented runtime deadline binding DEPENDABOT_GH_TIMEOUT_SECONDS (default 60, positive integer at most 60) in both workflow shells and the TypeScript adapter, retaining three fail-closed attempts and SIGKILL. Shell fixtures now block on an open pipe, signal readiness, and are killed only after readiness; all five hang paths require three actual SIGKILL close receipts. Node timeout attempts are counted before child startup and verify ETIMEDOUT plus SIGKILL for each permanently blocking child, eliminating the former startup counter race. Retry/exhaustion tests retain partial-output checks with the production deadline. Self-reviewed the entire PR diff for matching clocks, startup counters and shell timeout paths; added default/override and refusal-before-call corpora."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:55:07.192Z"}],"before_hash":"f1a694994d3c0718047e427f767f2591fff7edca7016c92a931ef597a643fca9","after_hash":"2376c244215a672abe408a5679611c301a6ceb8e9aed3dcc25ee3a760cbdc080","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":"4dc68deaa2c459daca34f3c593435cb71819fc3e888f828e6052d132ddad26fc"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:55:10.795Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T12:55:10.793Z","author":"codex-agent","text":"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: reverting only bounded_gh deadline use to hardcoded 60s fails all 5 selected loader/revocation hang cases; restoring only githubCli from b4b9cbe fails both selected CLI deadline/kill cases; reverting only its kill signal to SIGTERM fails the blocking-child test. Fixed implementation restored in finally; all 7 selected tests then pass. Initial full focused Node and native Bun runs each passed 116/116; expanded default and invalid-shell configuration corpus is now being checked. No package-worktree production function was temporarily reverted."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:55:10.795Z"}],"before_hash":"2376c244215a672abe408a5679611c301a6ceb8e9aed3dcc25ee3a760cbdc080","after_hash":"939b55c08e2fd6eac949410bb0931a2baaccfd097d655f7e04e3d0a91d361ae4","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":"c3391504fd0942cd67083d26abaa7311a151bd35eefae3648e9160a6034939ba"} +{"hash_algorithm":"sha256","ts":"2026-11-05T12:56:42.028Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T12:56:42.028Z","author":"codex-agent","text":"Final focused validation passed through pinned pm test --run --progress: Node 118/118, native Bun 118/118, caller-policy subset 11/11; no failures or skips. Existing tracker policy disables automatic test-result persistence, so receipts are recorded here. Test typecheck and lint pass; docstrings cover 274 declarations. c8 policy-only run has exact 100/100/100/100. All changed files are already linked to this item. Starting complete npm then Bun release gates serially under Node 26 with isolated HOME/Git config, an allowlisted credential-free child environment, public npm configuration, cache preference and unchanged coverage/duplication thresholds. No dist files are tracked by this repository."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:56:42.028Z"}],"before_hash":"939b55c08e2fd6eac949410bb0931a2baaccfd097d655f7e04e3d0a91d361ae4","after_hash":"128bc8d82c7481ea8fc3185af5b174b29911ca3f73e959ed1d80052f1a0e89a9","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":"e13c928fb7c861b146bf67df7cc5a027b5d6ef6f34ebb5904d1d0e6e277c7410"} +{"hash_algorithm":"sha256","ts":"2026-11-05T13:05:53.863Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T13:05:53.861Z","author":"codex-agent","text":"Source ready for review: linked Node/native Bun focused commands passed 118/118 each and caller subset 11/11. Three isolated behavioral reverts fail as expected, then restored tests pass. Full local npm release passed prechecks and privacy (8925 objects) and is progressing through the existing integration corpus without reported failures; Bun full release remains serially queued. Push source plus this tracker evidence now to start exact-head CI while local gates continue. Neither unfinished gate is counted as passing."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:05:53.863Z"}],"before_hash":"128bc8d82c7481ea8fc3185af5b174b29911ca3f73e959ed1d80052f1a0e89a9","after_hash":"0ca7065abb357bf8fd1a71a4ec1405dd943f788cc7080120d50df78997098246","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":"c0053644752382eb38a24889e535f7b972e684c32aeea06d4b7f31213cc97308"} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml index 484cbec7..a3f15484 100644 --- a/.github/workflows/pm-github-dependabot-merge.yml +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -51,11 +51,17 @@ jobs: shell: bash run: | set -euo pipefail + # A trusted runtime override may shorten the deadline, never remove its bound. + gh_timeout="${DEPENDABOT_GH_TIMEOUT_SECONDS-60}" + if [[ ! "${gh_timeout}" =~ ^[1-9][0-9]?$ ]] || (( gh_timeout > 60 )); then + echo '::error::GitHub CLI timeout must be an integer from 1 to 60 seconds.' + exit 1 + fi # Keep retries bounded and discard partial output from failed attempts. bounded_gh() { local attempt output for attempt in 1 2 3; do - if output="$(timeout --signal=KILL 60s gh "$@" 2>/dev/null)"; then + if output="$(timeout --signal=KILL "${gh_timeout}s" gh "$@" 2>/dev/null)"; then printf '%s' "${output}" return 0 fi @@ -85,11 +91,17 @@ jobs: shell: bash run: | set -euo pipefail + # A trusted runtime override may shorten the deadline, never remove its bound. + gh_timeout="${DEPENDABOT_GH_TIMEOUT_SECONDS-60}" + if [[ ! "${gh_timeout}" =~ ^[1-9][0-9]?$ ]] || (( gh_timeout > 60 )); then + echo '::error::GitHub CLI timeout must be an integer from 1 to 60 seconds.' + exit 1 + fi # Keep retries bounded and discard partial output from failed attempts. bounded_gh() { local attempt output for attempt in 1 2 3; do - if output="$(timeout --signal=KILL 60s gh "$@" 2>/dev/null)"; then + if output="$(timeout --signal=KILL "${gh_timeout}s" gh "$@" 2>/dev/null)"; then printf '%s' "${output}" return 0 fi diff --git a/README.md b/README.md index e6ee1485..73a1d3db 100644 --- a/README.md +++ b/README.md @@ -904,7 +904,10 @@ Foreign commits revoke auto-merge and produce a successful job, a summary and `::notice` listing **short SHA and author login only**. Disallowed metadata, unsupported arm events (including `pull_request`), API failures and incomplete pagination also leave auto-merge unarmed with a clear notice. Every CLI call is -limited to three attempts of 60 seconds each; failed attempts discard partial +limited to three attempts of 60 seconds each by default. Trusted runtime bindings +may shorten each deadline with `DEPENDABOT_GH_TIMEOUT_SECONDS` (integer 1–60); +the policy adapter also accepts a millisecond timeout option. Invalid deadlines +fail before starting a CLI call. Failed attempts discard partial output. A timed-out state read still attempts revocation. An inability to revoke is an operational failure that warns the request may remain armed, rather than a false successful safety claim. diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts index fdd06f89..7778c9b8 100644 --- a/scripts/dependabot-merge.ts +++ b/scripts/dependabot-merge.ts @@ -1,5 +1,5 @@ /** Standalone policy loaded from the reusable workflow SHA, never from a PR. */ -import { execFileSync } from "node:child_process"; +import { execFileSync, type ExecFileSyncOptionsWithStringEncoding } from "node:child_process"; /** Minimal GitHub PR fields needed to prove a stable, complete snapshot. */ interface PullRequest { @@ -39,15 +39,27 @@ export interface MergeResult { message: string; } +/** Execute one bounded child; injectable so tests can observe actual kill receipts. */ +export type GithubCommandRunner = (executable: string, args: readonly string[], options: ExecFileSyncOptionsWithStringEncoding) => string; + +/** Read the trusted runtime deadline; invalid overrides fail before starting a child. */ +export function githubCallTimeout(seconds = process.env.DEPENDABOT_GH_TIMEOUT_SECONDS ?? "60"): number { + if (!/^[1-9][0-9]*$/.test(seconds) || Number(seconds) > 60) { + throw new Error("GitHub CLI timeout must be an integer from 1 to 60 seconds."); + } + return Number(seconds) * 1000; +} + /** Bound each CLI attempt, discard failed output, and retry at most three times without a shell. */ -export function githubCli(args: readonly string[], executable = "gh", timeout = 60_000): string { +export function githubCli(args: readonly string[], executable = "gh", timeout = githubCallTimeout(), execute: GithubCommandRunner = execFileSync): string { + if (!Number.isInteger(timeout) || timeout < 1 || timeout > 60_000) throw new Error("Invalid GitHub CLI timeout."); for (let attempt = 1; attempt <= 3; attempt++) { try { - return execFileSync(executable, args, { encoding: "utf8", env: { ...process.env }, maxBuffer: 32 * 1024 * 1024, + return execute(executable, args, { encoding: "utf8", env: { ...process.env }, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"], timeout, killSignal: "SIGKILL" }); } catch { /* A timeout or failed attempt must never expose partial output. */ } } - throw new Error("GitHub CLI failed or timed out after 3 bounded attempts (60 seconds each in production)."); + throw new Error("GitHub CLI failed or timed out after 3 bounded attempts."); } const bot = "dependabot[bot]"; diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 394fcd72..cda68f54 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -1,12 +1,12 @@ /** Execute the production merge policy against API receipts and its real workflow launcher. */ import assert from "node:assert/strict"; -import { spawnSync } from "node:child_process"; -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { execFileSync, spawnSync } from "node:child_process"; +import { existsSync, mkdtempSync, readFileSync, 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"; -import { auditDependabotMerges, githubCli, runDependabotMerge, type MergeInput } from "../scripts/dependabot-merge.ts"; +import { auditDependabotMerges, githubCallTimeout, githubCli, runDependabotMerge, type GithubCommandRunner, type MergeInput } from "../scripts/dependabot-merge.ts"; const root = resolve(import.meta.dirname, ".."); const bot = "dependabot[bot]"; @@ -237,21 +237,51 @@ test("custom-token arm event preserves the request; a foreign synchronize event assert.equal(f.state.auto_merge, null, "foreign synchronize must still revoke the request"); }); -/** Exercise the production timeout arguments with a shorter real clock in shell fixtures. */ +/** Kill only after the blocking child signals readiness; host startup time is irrelevant. */ function installFixtureTimeout(dir: string): void { writeFileSync(join(dir, "timeout"), `#!/bin/bash -[[ "$1" == --signal=KILL && "$2" == 60s ]] || exit 97 -shift 2 -duration=60s -case "$MODE" in *hang*) duration=0.3s ;; esac -exec /usr/bin/timeout --signal=KILL "$duration" "$@" +[[ "$1" == --signal=KILL && "$2" == "\${DEPENDABOT_GH_TIMEOUT_SECONDS-60}s" ]] || exit 97 +case "$MODE" in + *hang*) shift 2; exec "${process.execPath}" "$TIMEOUT_SUPERVISOR" "$@" ;; + *) exec /usr/bin/timeout "$@" ;; +esac `, { mode: 0o755 }); + writeFileSync(join(dir, "timeout-supervisor.cjs"), ` +const assert = require('node:assert/strict'); +const { spawn } = require('node:child_process'); +const { appendFileSync } = require('node:fs'); +const child = spawn(process.argv[2], process.argv.slice(3), { stdio: ['ignore', 'inherit', 'inherit', 'pipe', 'pipe'] }); +let ready = false; +child.stdio[3].once('data', () => { + ready = true; + assert.equal(child.kill('SIGKILL'), true); +}); +child.on('close', (code, signal) => { + if (ready) { + assert.equal(signal, 'SIGKILL'); + appendFileSync(process.env.KILL_RECEIPTS, 'SIGKILL\\n'); + process.exit(137); + } + assert.equal(signal, null); + process.exit(code); +}); +`); +} + +/** Runtime bindings for the readiness-controlled deadline fixture. */ +function timeoutEnvironment(dir: string, mode: string): Record { + return { DEPENDABOT_GH_TIMEOUT_SECONDS: mode === "default-timeout" ? "60" : "7", TIMEOUT_SUPERVISOR: join(dir, "timeout-supervisor.cjs"), KILL_RECEIPTS: join(dir, "kills") }; +} + +/** Require actual child termination, not merely an invocation or elapsed time. */ +function assertHangKills(dir: string, mode: string): void { + if (mode.includes("hang")) assert.deepEqual(readFileSync(join(dir, "kills"), "utf8").trim().split("\n"), ["SIGKILL", "SIGKILL", "SIGKILL"]); } for (const mode of ["external", "local", "remote-self", "audit", "audit-load-failed", "load-failed", "invalid-sha", "load-hang", "fallback-read-hang", "fallback-disable-hang", "load-retry"]) { test(`real workflow loader/launcher: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-policy-")); - const env = { ...process.env, PATH: dir + ":" + process.env.PATH, RUNNER_TEMP: dir, + const env = { ...process.env, ...timeoutEnvironment(dir, mode), PATH: dir + ":" + process.env.PATH, RUNNER_TEMP: dir, RETRY_MARKER: join(dir, "retried"), POLICY_SOURCE: join(root, "scripts/dependabot-merge.ts"), WORKFLOW_SHA: mode === "invalid-sha" ? "bad" : mode === "local" ? trusted : head, WORKFLOW_REF: `unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@${mode.startsWith("local") ? "refs/pull/7/merge" : head}`, CALLER_REF: "refs/pull/7/merge", @@ -266,19 +296,20 @@ printf '%s\\n' "$*" >> "$RECEIPTS" case "$*" in *git/ref/heads/main*) [[ "$MODE" != local-ref-failed ]] || exit 1; printf '%s' '${trusted}' ;; *contents/scripts*) - [[ "$MODE" != load-hang ]] || { trap '' TERM; sleep 2; exit 0; } + [[ "$MODE" != load-hang ]] || { printf ready >&3; read -r -u 4; exit 98; } [[ "$MODE" != load-retry || -f "$RETRY_MARKER" ]] || { touch "$RETRY_MARKER"; printf partial; exit 1; } [[ "$MODE" != load-failed && "$MODE" != audit-load-failed && "$MODE" != fallback-* ]] || exit 1; cat "$POLICY_SOURCE" ;; *'/pulls?'*) printf '%s' '{"number":7,"user":{"login":"${bot}"},"auto_merge":{}}' ;; *'/commits?'*) printf '%s' '{"sha":"${head}","author":{"login":"outside"},"committer":{"login":"outside"}}' ;; - 'api '*) [[ "$MODE" != fallback-read-hang ]] || { trap '' TERM; sleep 2; exit 0; }; printf '%s' '{"state":"open","user":{"login":"${bot}"},"head":{"sha":"${head}"},"commits":1,"auto_merge":null}' ;; - 'pr merge '*) [[ "$MODE" != fallback-disable-hang ]] || { trap '' TERM; sleep 2; exit 0; } ;; + 'api '*) [[ "$MODE" != fallback-read-hang ]] || { printf ready >&3; read -r -u 4; exit 98; }; printf '%s' '{"state":"open","user":{"login":"${bot}"},"head":{"sha":"${head}"},"commits":1,"auto_merge":null}' ;; + 'pr merge '*) [[ "$MODE" != fallback-disable-hang ]] || { printf ready >&3; read -r -u 4; exit 98; } ;; *) exit 2 ;; esac `, { mode: 0o755 }); - const loaded = spawnSync("bash", ["-c", loader()], { cwd: dir, env, encoding: "utf8", timeout: 10_000, killSignal: "SIGKILL" }); + const loaded = spawnSync("bash", ["-c", loader()], { cwd: dir, env, encoding: "utf8", timeout: 120_000, killSignal: "SIGKILL" }); const failed = ["load-failed", "invalid-sha", "load-hang", "fallback-read-hang"].includes(mode); assert.equal(loaded.status, mode === "fallback-disable-hang" || mode === "audit-load-failed" ? 1 : 0, loaded.stderr); + assertHangKills(dir, mode); const receipts = readFileSync(env.RECEIPTS, "utf8"); if (mode === "audit-load-failed") { assert.match(loaded.stdout, /Cannot load trusted audit policy/); @@ -327,7 +358,7 @@ test("CLI adapter resolves a unique fixture command against the current PATH", ( } finally { process.env.PATH = old; rmSync(dir, { recursive: true, force: true }); } }); -for (const mode of ["armed", "clear", "closed", "malformed-api", "api-error", "denied", "hang-read", "hang-disable", "retry-disable"]) { +for (const mode of ["default-timeout", "armed", "clear", "closed", "malformed-api", "api-error", "denied", "hang-read", "hang-disable", "retry-disable"]) { test(`pre-tooling revocation shell: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-revoke-")); try { @@ -337,7 +368,7 @@ for (const mode of ["armed", "clear", "closed", "malformed-api", "api-error", "d writeFileSync(join(dir, "gh"), `#!/bin/bash printf '%s\\n' "$*" >> "$RECEIPTS" if [[ "$1" == api ]]; then - [[ "$MODE" != hang-read ]] || { trap '' TERM; sleep 2; exit 0; } + [[ "$MODE" != hang-read ]] || { printf ready >&3; read -r -u 4; exit 98; } [[ "$MODE" != api-error ]] || exit 1 case "$MODE" in clear) payload='{"state":"open","auto_merge":null}' ;; @@ -347,18 +378,20 @@ if [[ "$1" == api ]]; then esac printf '%s' "$payload" | jq -r "$4" else - [[ "$MODE" != hang-disable ]] || { trap '' TERM; sleep 2; exit 0; } + [[ "$MODE" != hang-disable ]] || { printf ready >&3; read -r -u 4; exit 98; } [[ "$MODE" != retry-disable || -f "$RETRY_MARKER" ]] || { touch "$RETRY_MARKER"; exit 1; } [[ "$MODE" != denied ]] fi `, { mode: 0o755 }); installFixtureTimeout(dir); - const env = { ...process.env, PATH: dir + ":" + process.env.PATH, RETRY_MARKER: join(dir, "retried"), REPOSITORY: "acme/widgets", PR_NUMBER: "7", + const env = { ...process.env, ...timeoutEnvironment(dir, mode), PATH: dir + ":" + process.env.PATH, RETRY_MARKER: join(dir, "retried"), REPOSITORY: "acme/widgets", PR_NUMBER: "7", MODE: mode, RECEIPTS: join(dir, "receipts"), GITHUB_STEP_SUMMARY: join(dir, "summary") }; - const result = spawnSync("bash", ["-c", shell], { cwd: dir, env, encoding: "utf8", timeout: 10_000, killSignal: "SIGKILL" }); + if (mode === "default-timeout") delete (env as NodeJS.ProcessEnv).DEPENDABOT_GH_TIMEOUT_SECONDS; + const result = spawnSync("bash", ["-c", shell], { cwd: dir, env, encoding: "utf8", timeout: 120_000, killSignal: "SIGKILL" }); assert.equal(result.status, mode === "denied" || mode === "hang-disable" ? 1 : 0, result.stderr); if (mode === "denied" || mode === "hang-disable") assert.match(result.stdout, /Cannot revoke auto-merge/); else assert.match(readFileSync(env.GITHUB_STEP_SUMMARY, "utf8"), /cleared before tooling/); + assertHangKills(dir, mode); const receipts = readFileSync(env.RECEIPTS, "utf8"); if (mode === "hang-read") assert.equal(receipts.split("\n").filter(line => line.startsWith("api ")).length, 3); if (mode === "hang-disable" || mode === "denied") assert.equal(receipts.split("\n").filter(line => line.startsWith("pr merge ")).length, 3); @@ -370,13 +403,35 @@ fi } + +test("both workflow shells reject invalid deadline overrides before any CLI call", () => { + const source = parse(readFileSync(join(root, ".github/workflows/pm-github-dependabot-merge.yml"), "utf8")) as { + jobs: Record; + }; + const dir = mkdtempSync(join(tmpdir(), "dependabot-invalid-deadline-")); + try { + const receipt = join(dir, "called"); + writeFileSync(join(dir, "gh"), `#!/bin/bash\nprintf called > "$CLI_RECEIPT"\n`, { mode: 0o755 }); + for (const step of source.jobs["dependabot-merge"]!.steps.filter(step => step.name === "Revoke previous auto-merge before tooling" || step.name === "Load trusted merge policy")) { + for (const invalid of ["", "0", "-1", "1.5", "61", "Infinity", "1e2", " 7", "07", "7\n"]) { + const result = spawnSync("bash", ["-c", step.run!], { encoding: "utf8", env: { + ...process.env, PATH: dir + ":" + process.env.PATH, DEPENDABOT_GH_TIMEOUT_SECONDS: invalid, CLI_RECEIPT: receipt, + } }); + assert.equal(result.status, 1, result.stderr); + assert.match(result.stdout, /timeout must be an integer from 1 to 60 seconds/); + assert.equal(existsSync(receipt), false); + } + } + } finally { rmSync(dir, { recursive: true, force: true }); } +}); + test("README describes the actual author guard without an owner restriction", () => { const readme = readFileSync(join(root, "README.md"), "utf8"); assert.match(readme, /workflow checks the Dependabot author, then the\s+metadata group/); assert.doesNotMatch(readme, /Dependabot author and repository\s+owner/); }); -for (const mode of ["retry", "exhausted", "timeout"]) { +for (const mode of ["retry", "exhausted"]) { test(`real CLI bounded attempts discard partial output: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-bounded-")); try { @@ -384,10 +439,9 @@ for (const mode of ["retry", "exhausted", "timeout"]) { const script = `const fs = require('node:fs'); const p = process.argv[1]; const n = fs.existsSync(p) ? Number(fs.readFileSync(p, 'utf8')) + 1 : 1; fs.writeFileSync(p, String(n)); - if (process.argv[2] === 'timeout') { process.stdout.write('partial'); process.on('SIGTERM', () => {}); setTimeout(() => { process.stdout.write('late'); process.exit(0); }, 2000); } - else if (process.argv[2] === 'retry' && n === 3) process.stdout.write('complete'); + if (process.argv[2] === 'retry' && n === 3) process.stdout.write('complete'); else { process.stdout.write('partial'); process.exit(1); }`; - const run = () => githubCli(["-e", script, counter, mode], process.execPath, mode === "timeout" ? 500 : 5000); + const run = () => githubCli(["-e", script, counter, mode], process.execPath); if (mode === "retry") assert.equal(run(), "complete"); else assert.throws(run, /failed or timed out after 3 bounded attempts/); assert.equal(readFileSync(counter, "utf8"), "3"); @@ -396,6 +450,54 @@ for (const mode of ["retry", "exhausted", "timeout"]) { } +test("CLI deadline defaults to 60 seconds, reads runtime overrides and rejects unbounded values", () => { + const previous = process.env.DEPENDABOT_GH_TIMEOUT_SECONDS; + const timeouts: number[] = []; + const execute: GithubCommandRunner = (_executable, _args, options) => { + timeouts.push(options.timeout!); + assert.equal(options.killSignal, "SIGKILL"); + return "complete"; + }; + try { + delete process.env.DEPENDABOT_GH_TIMEOUT_SECONDS; + assert.equal(githubCli([], "fixture-command", undefined, execute), "complete"); + process.env.DEPENDABOT_GH_TIMEOUT_SECONDS = "7"; + assert.equal(githubCli([], "fixture-command", undefined, execute), "complete"); + assert.deepEqual(timeouts, [60_000, 7000]); + for (const invalid of ["", "0", "-1", "1.5", "61", "Infinity", "1e2", " 7", "07", "7\n"]) { + assert.throws(() => githubCallTimeout(invalid), /integer from 1 to 60/); + } + for (const invalid of [0, -1, NaN, Infinity, 0.5, 60_001]) { + assert.throws(() => githubCli([], "fixture-command", invalid, execute), /Invalid GitHub CLI timeout/); + } + assert.equal(timeouts.length, 2, "invalid deadlines must not start a child"); + } finally { + if (previous === undefined) delete process.env.DEPENDABOT_GH_TIMEOUT_SECONDS; + else process.env.DEPENDABOT_GH_TIMEOUT_SECONDS = previous; + } +}); + +test("real CLI timeout kills every blocking child and fails closed without startup counters", () => { + let attempts = 0; + let killed = 0; + const execute: GithubCommandRunner = (executable, args, options) => { + attempts++; // Parent-side receipt exists before the child can start or time out. + try { return execFileSync(executable, args, options); } + catch (error) { + const receipt = error as Error & { code?: string; signal?: string }; + assert.equal(receipt.code, "ETIMEDOUT"); + assert.equal(receipt.signal, "SIGKILL"); + killed++; + throw error; + } + }; + const block = "process.stdout.write('partial'); Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0);"; + assert.throws(() => githubCli(["-e", block], process.execPath, 1, execute), /^Error: GitHub CLI failed or timed out after 3 bounded attempts\.$/); + assert.equal(attempts, 3); + assert.equal(killed, 3); +}); + + /** Supply paginated PR-list receipts while exercising the shared production provenance checks. */ function auditFixture() { const f = fixture(); From fadf64d558620e42de2831563b0bd00c12fea338 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 14:46:33 +0100 Subject: [PATCH 17/19] fix: keep bounded shell calls verifiable by attestation --- .../features/pm-github-dependabot-merge.toon | 10 +++--- .../history/pm-github-dependabot-merge.jsonl | 5 +++ .../workflows/pm-github-dependabot-merge.yml | 16 ++------- README.md | 9 ++--- test/dependabot-merge.test.ts | 36 ++++--------------- 5 files changed, 25 insertions(+), 51 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 39d9113a..c43cbe18 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,10 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T13:05:53.863Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-11-05T13:45:39.879Z" author: codex-agent acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." -comments[52]{created_at,author,text}: +comments[56]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -64,6 +62,10 @@ comments[52]{created_at,author,text}: "2026-11-05T12:55:10.793Z",codex-agent,"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: reverting only bounded_gh deadline use to hardcoded 60s fails all 5 selected loader/revocation hang cases; restoring only githubCli from b4b9cbe fails both selected CLI deadline/kill cases; reverting only its kill signal to SIGTERM fails the blocking-child test. Fixed implementation restored in finally; all 7 selected tests then pass. Initial full focused Node and native Bun runs each passed 116/116; expanded default and invalid-shell configuration corpus is now being checked. No package-worktree production function was temporarily reverted." "2026-11-05T12:56:42.028Z",codex-agent,"Final focused validation passed through pinned pm test --run --progress: Node 118/118, native Bun 118/118, caller-policy subset 11/11; no failures or skips. Existing tracker policy disables automatic test-result persistence, so receipts are recorded here. Test typecheck and lint pass; docstrings cover 274 declarations. c8 policy-only run has exact 100/100/100/100. All changed files are already linked to this item. Starting complete npm then Bun release gates serially under Node 26 with isolated HOME/Git config, an allowlisted credential-free child environment, public npm configuration, cache preference and unchanged coverage/duplication thresholds. No dist files are tracked by this repository." "2026-11-05T13:05:53.861Z",codex-agent,"Source ready for review: linked Node/native Bun focused commands passed 118/118 each and caller subset 11/11. Three isolated behavioral reverts fail as expected, then restored tests pass. Full local npm release passed prechecks and privacy (8925 objects) and is progressing through the existing integration corpus without reported failures; Bun full release remains serially queued. Push source plus this tracker evidence now to start exact-head CI while local gates continue. Neither unfinished gate is counted as passing." + "2026-11-05T13:26:10.904Z",codex-agent,"Source pushed at 028db825bfd4d055ecf2949556c504b965715b4b with code and append-only tracker history; worktree was clean and every branch commit email matched the required identity. Exact-head CI: https://github.com/unbraind/pm-github/actions/runs/42032806093 ; CodeQL: https://github.com/unbraind/pm-github/actions/runs/42032806238 . Release CI jobs remain queued during the observation window. Local npm coverage has passed over 370 tests including the scheduled closeout, numeric pagination, malicious-content privacy refusal, zero-close, token-denial and SDK-receipt workflow fixtures, with no reported failures. Full gates remain pending; continue serially. Review refresh still finds only the two original unresolved threads and no newer nonempty CodeRabbit review. PR description exact readback verified after using REST because installed gh pr edit requested the obsolete projectCards GraphQL field; no PM CLI/SDK defect was established." + "2026-11-05T13:42:02.005Z",codex-agent,"Final standalone publication-attestation check found a real integration regression in the first source push: the canonical auditor treats a variable timeout duration as an unresolved executable position. Corrected the workflow to retain its literal 60-second pre-tooling and loader deadline; TypeScript policy timeout remains injectable through the validated runtime binding or millisecond option. Shell hang fixtures remain readiness-controlled and verify actual kills, so they need no short startup deadline. Removed the now-inapplicable shell-override cases and updated documentation. This supersedes the earlier statement that the runtime override applies to both shells. Production audit and package dry run passed. No attestation scanner policy or dependency was changed." + "2026-11-05T13:45:30.757Z",codex-agent,"Final behavioral proofs supersede the earlier shell-injection variant: restoring only installFixtureTimeout from b4b9cbe makes all 5 shell hang cases fail their actual-kill receipts; restoring only githubCli makes both selected timeout/configuration tests fail, including when the current sanitized error wording is retained; changing only SIGKILL to SIGTERM fails the blocking-child case. Restored policy passes all 7 selected cases. In a separate disposable Git copy, the existing own-workflows attestation test passes fixed baseline, fails 1/1 when the variable shell duration from 028db82 is restored, then passes 1/1 after restoration. All mutations were isolated from the package worktree." + "2026-11-05T13:45:34.798Z",codex-agent,"Final focused evidence: pinned pm runner passed Node 116/116, native Bun 116/116, caller subset 11/11; combined policy and publication-attestation Node suite passed 124/124, zero failures/skips. Policy-only c8 is exact 100/100/100/100. Test typecheck, lint, 274 documented declarations, zero-threshold duplication, production audit (zero vulnerabilities), package dry run (75 files) and publication attestation pass. Source CI and CodeQL remained queued at the 30-minute observation cutoff (13:36 UTC): https://github.com/unbraind/pm-github/actions/runs/42032806093 and https://github.com/unbraind/pm-github/actions/runs/42032806238 . Full release gates verified this round: 0/2. The local npm gate passed prechecks/privacy and progressed through workflow fixtures, then its positively identified process tree was stopped at cutoff; termination receipts are not a test or coverage pass. The serial local Bun release gate did not start. Neither incomplete gate is approval; fresh final-head CI remains required. No further source-CI polling. Both original review findings are fixed; latest complete review refresh has no newer findings or outside-diff additions. No replies, resolutions, mentions, merges or releases. No new PM CLI/SDK defect was established. Release only this agent claim and retain the item in progress for orchestrator verification." files[7]: - path: .agents/pm/chores/pm-github-dependabot-activation.toon scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index 398b94d8..f11bed88 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -71,3 +71,8 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T12:55:10.795Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T12:55:10.793Z","author":"codex-agent","text":"Behavioral revert proofs in a disposable non-git copy with unchanged new tests: reverting only bounded_gh deadline use to hardcoded 60s fails all 5 selected loader/revocation hang cases; restoring only githubCli from b4b9cbe fails both selected CLI deadline/kill cases; reverting only its kill signal to SIGTERM fails the blocking-child test. Fixed implementation restored in finally; all 7 selected tests then pass. Initial full focused Node and native Bun runs each passed 116/116; expanded default and invalid-shell configuration corpus is now being checked. No package-worktree production function was temporarily reverted."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:55:10.795Z"}],"before_hash":"2376c244215a672abe408a5679611c301a6ceb8e9aed3dcc25ee3a760cbdc080","after_hash":"939b55c08e2fd6eac949410bb0931a2baaccfd097d655f7e04e3d0a91d361ae4","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":"c3391504fd0942cd67083d26abaa7311a151bd35eefae3648e9160a6034939ba"} {"hash_algorithm":"sha256","ts":"2026-11-05T12:56:42.028Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T12:56:42.028Z","author":"codex-agent","text":"Final focused validation passed through pinned pm test --run --progress: Node 118/118, native Bun 118/118, caller-policy subset 11/11; no failures or skips. Existing tracker policy disables automatic test-result persistence, so receipts are recorded here. Test typecheck and lint pass; docstrings cover 274 declarations. c8 policy-only run has exact 100/100/100/100. All changed files are already linked to this item. Starting complete npm then Bun release gates serially under Node 26 with isolated HOME/Git config, an allowlisted credential-free child environment, public npm configuration, cache preference and unchanged coverage/duplication thresholds. No dist files are tracked by this repository."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T12:56:42.028Z"}],"before_hash":"939b55c08e2fd6eac949410bb0931a2baaccfd097d655f7e04e3d0a91d361ae4","after_hash":"128bc8d82c7481ea8fc3185af5b174b29911ca3f73e959ed1d80052f1a0e89a9","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":"e13c928fb7c861b146bf67df7cc5a027b5d6ef6f34ebb5904d1d0e6e277c7410"} {"hash_algorithm":"sha256","ts":"2026-11-05T13:05:53.863Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T13:05:53.861Z","author":"codex-agent","text":"Source ready for review: linked Node/native Bun focused commands passed 118/118 each and caller subset 11/11. Three isolated behavioral reverts fail as expected, then restored tests pass. Full local npm release passed prechecks and privacy (8925 objects) and is progressing through the existing integration corpus without reported failures; Bun full release remains serially queued. Push source plus this tracker evidence now to start exact-head CI while local gates continue. Neither unfinished gate is counted as passing."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:05:53.863Z"}],"before_hash":"128bc8d82c7481ea8fc3185af5b174b29911ca3f73e959ed1d80052f1a0e89a9","after_hash":"0ca7065abb357bf8fd1a71a4ec1405dd943f788cc7080120d50df78997098246","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":"c0053644752382eb38a24889e535f7b972e684c32aeea06d4b7f31213cc97308"} +{"hash_algorithm":"sha256","ts":"2026-11-05T13:26:10.905Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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/52","value":{"created_at":"2026-11-05T13:26:10.904Z","author":"codex-agent","text":"Source pushed at 028db825bfd4d055ecf2949556c504b965715b4b with code and append-only tracker history; worktree was clean and every branch commit email matched the required identity. Exact-head CI: https://github.com/unbraind/pm-github/actions/runs/42032806093 ; CodeQL: https://github.com/unbraind/pm-github/actions/runs/42032806238 . Release CI jobs remain queued during the observation window. Local npm coverage has passed over 370 tests including the scheduled closeout, numeric pagination, malicious-content privacy refusal, zero-close, token-denial and SDK-receipt workflow fixtures, with no reported failures. Full gates remain pending; continue serially. Review refresh still finds only the two original unresolved threads and no newer nonempty CodeRabbit review. PR description exact readback verified after using REST because installed gh pr edit requested the obsolete projectCards GraphQL field; no PM CLI/SDK defect was established."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:26:10.905Z"}],"before_hash":"0ca7065abb357bf8fd1a71a4ec1405dd943f788cc7080120d50df78997098246","after_hash":"1ce925ac16664833d7d48fa081c53a7c53183f8b1d2a24e2f8191ecc3f0985b4","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":"a6fa92d847745e41fdeb310e27c14ed75e3f9409a5554e742084286182b06427"} +{"hash_algorithm":"sha256","ts":"2026-11-05T13:42:02.006Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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/53","value":{"created_at":"2026-11-05T13:42:02.005Z","author":"codex-agent","text":"Final standalone publication-attestation check found a real integration regression in the first source push: the canonical auditor treats a variable timeout duration as an unresolved executable position. Corrected the workflow to retain its literal 60-second pre-tooling and loader deadline; TypeScript policy timeout remains injectable through the validated runtime binding or millisecond option. Shell hang fixtures remain readiness-controlled and verify actual kills, so they need no short startup deadline. Removed the now-inapplicable shell-override cases and updated documentation. This supersedes the earlier statement that the runtime override applies to both shells. Production audit and package dry run passed. No attestation scanner policy or dependency was changed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:42:02.006Z"}],"before_hash":"1ce925ac16664833d7d48fa081c53a7c53183f8b1d2a24e2f8191ecc3f0985b4","after_hash":"ecf7293e966015fa5a5b8b2c4afa2be6d9977a0cc9af6dc654d94e62b3aab9cb","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":"00ae7c418a56e5c74f8159c3a4b14d271d1bcae33e9c915c4d2e27d4ae089a95"} +{"hash_algorithm":"sha256","ts":"2026-11-05T13:45:30.757Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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/54","value":{"created_at":"2026-11-05T13:45:30.757Z","author":"codex-agent","text":"Final behavioral proofs supersede the earlier shell-injection variant: restoring only installFixtureTimeout from b4b9cbe makes all 5 shell hang cases fail their actual-kill receipts; restoring only githubCli makes both selected timeout/configuration tests fail, including when the current sanitized error wording is retained; changing only SIGKILL to SIGTERM fails the blocking-child case. Restored policy passes all 7 selected cases. In a separate disposable Git copy, the existing own-workflows attestation test passes fixed baseline, fails 1/1 when the variable shell duration from 028db82 is restored, then passes 1/1 after restoration. All mutations were isolated from the package worktree."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:45:30.757Z"}],"before_hash":"ecf7293e966015fa5a5b8b2c4afa2be6d9977a0cc9af6dc654d94e62b3aab9cb","after_hash":"f711520d1f1c6abe5f11472d724c133ebee2098a30c031ffe647e50bfd8cca72","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":"c6eda1ad7caee8f8cd7d82edfe80e1cf7c3ac83c1e59f34a1b6be707758dbad6"} +{"hash_algorithm":"sha256","ts":"2026-11-05T13:45:34.798Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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/55","value":{"created_at":"2026-11-05T13:45:34.798Z","author":"codex-agent","text":"Final focused evidence: pinned pm runner passed Node 116/116, native Bun 116/116, caller subset 11/11; combined policy and publication-attestation Node suite passed 124/124, zero failures/skips. Policy-only c8 is exact 100/100/100/100. Test typecheck, lint, 274 documented declarations, zero-threshold duplication, production audit (zero vulnerabilities), package dry run (75 files) and publication attestation pass. Source CI and CodeQL remained queued at the 30-minute observation cutoff (13:36 UTC): https://github.com/unbraind/pm-github/actions/runs/42032806093 and https://github.com/unbraind/pm-github/actions/runs/42032806238 . Full release gates verified this round: 0/2. The local npm gate passed prechecks/privacy and progressed through workflow fixtures, then its positively identified process tree was stopped at cutoff; termination receipts are not a test or coverage pass. The serial local Bun release gate did not start. Neither incomplete gate is approval; fresh final-head CI remains required. No further source-CI polling. Both original review findings are fixed; latest complete review refresh has no newer findings or outside-diff additions. No replies, resolutions, mentions, merges or releases. No new PM CLI/SDK defect was established. Release only this agent claim and retain the item in progress for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:45:34.798Z"}],"before_hash":"f711520d1f1c6abe5f11472d724c133ebee2098a30c031ffe647e50bfd8cca72","after_hash":"1081625a22df3b517af060e50b90aed1a77733b11d86f2395959ed27aa858197","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":"a67bfca3d1f7c3faf82c3c812eb199fe111986be13ec919f4b5ad4a3b547fb39"} +{"hash_algorithm":"sha256","ts":"2026-11-05T13:45:39.879Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T13:45:39.879Z"}],"before_hash":"1081625a22df3b517af060e50b90aed1a77733b11d86f2395959ed27aa858197","after_hash":"d5193e46dd40d70baffc63201bf5ba58ebeaa374839106f87d05d5f6c8d3560d","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":"777a67505d5a9b78062d5671af00a48c34951100a8d45bd417efda55408c61c9"} diff --git a/.github/workflows/pm-github-dependabot-merge.yml b/.github/workflows/pm-github-dependabot-merge.yml index a3f15484..484cbec7 100644 --- a/.github/workflows/pm-github-dependabot-merge.yml +++ b/.github/workflows/pm-github-dependabot-merge.yml @@ -51,17 +51,11 @@ jobs: shell: bash run: | set -euo pipefail - # A trusted runtime override may shorten the deadline, never remove its bound. - gh_timeout="${DEPENDABOT_GH_TIMEOUT_SECONDS-60}" - if [[ ! "${gh_timeout}" =~ ^[1-9][0-9]?$ ]] || (( gh_timeout > 60 )); then - echo '::error::GitHub CLI timeout must be an integer from 1 to 60 seconds.' - exit 1 - fi # Keep retries bounded and discard partial output from failed attempts. bounded_gh() { local attempt output for attempt in 1 2 3; do - if output="$(timeout --signal=KILL "${gh_timeout}s" gh "$@" 2>/dev/null)"; then + if output="$(timeout --signal=KILL 60s gh "$@" 2>/dev/null)"; then printf '%s' "${output}" return 0 fi @@ -91,17 +85,11 @@ jobs: shell: bash run: | set -euo pipefail - # A trusted runtime override may shorten the deadline, never remove its bound. - gh_timeout="${DEPENDABOT_GH_TIMEOUT_SECONDS-60}" - if [[ ! "${gh_timeout}" =~ ^[1-9][0-9]?$ ]] || (( gh_timeout > 60 )); then - echo '::error::GitHub CLI timeout must be an integer from 1 to 60 seconds.' - exit 1 - fi # Keep retries bounded and discard partial output from failed attempts. bounded_gh() { local attempt output for attempt in 1 2 3; do - if output="$(timeout --signal=KILL "${gh_timeout}s" gh "$@" 2>/dev/null)"; then + if output="$(timeout --signal=KILL 60s gh "$@" 2>/dev/null)"; then printf '%s' "${output}" return 0 fi diff --git a/README.md b/README.md index 73a1d3db..5cf31f5d 100644 --- a/README.md +++ b/README.md @@ -904,10 +904,11 @@ Foreign commits revoke auto-merge and produce a successful job, a summary and `::notice` listing **short SHA and author login only**. Disallowed metadata, unsupported arm events (including `pull_request`), API failures and incomplete pagination also leave auto-merge unarmed with a clear notice. Every CLI call is -limited to three attempts of 60 seconds each by default. Trusted runtime bindings -may shorten each deadline with `DEPENDABOT_GH_TIMEOUT_SECONDS` (integer 1–60); -the policy adapter also accepts a millisecond timeout option. Invalid deadlines -fail before starting a CLI call. Failed attempts discard partial +limited to three attempts of 60 seconds each by default. The TypeScript policy +adapter reads `DEPENDABOT_GH_TIMEOUT_SECONDS` (integer 1–60, default 60) and also +accepts a millisecond timeout option. Invalid adapter deadlines fail before +starting a CLI call. Pre-tooling shell calls retain a fixed 60-second deadline. +Failed attempts discard partial output. A timed-out state read still attempts revocation. An inability to revoke is an operational failure that warns the request may remain armed, rather than a false successful safety claim. diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index cda68f54..7d9cb51c 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -1,7 +1,7 @@ /** Execute the production merge policy against API receipts and its real workflow launcher. */ import assert from "node:assert/strict"; import { execFileSync, spawnSync } from "node:child_process"; -import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import test from "node:test"; @@ -240,7 +240,7 @@ test("custom-token arm event preserves the request; a foreign synchronize event /** Kill only after the blocking child signals readiness; host startup time is irrelevant. */ function installFixtureTimeout(dir: string): void { writeFileSync(join(dir, "timeout"), `#!/bin/bash -[[ "$1" == --signal=KILL && "$2" == "\${DEPENDABOT_GH_TIMEOUT_SECONDS-60}s" ]] || exit 97 +[[ "$1" == --signal=KILL && "$2" == 60s ]] || exit 97 case "$MODE" in *hang*) shift 2; exec "${process.execPath}" "$TIMEOUT_SUPERVISOR" "$@" ;; *) exec /usr/bin/timeout "$@" ;; @@ -269,8 +269,8 @@ child.on('close', (code, signal) => { } /** Runtime bindings for the readiness-controlled deadline fixture. */ -function timeoutEnvironment(dir: string, mode: string): Record { - return { DEPENDABOT_GH_TIMEOUT_SECONDS: mode === "default-timeout" ? "60" : "7", TIMEOUT_SUPERVISOR: join(dir, "timeout-supervisor.cjs"), KILL_RECEIPTS: join(dir, "kills") }; +function timeoutEnvironment(dir: string): Record { + return { TIMEOUT_SUPERVISOR: join(dir, "timeout-supervisor.cjs"), KILL_RECEIPTS: join(dir, "kills") }; } /** Require actual child termination, not merely an invocation or elapsed time. */ @@ -281,7 +281,7 @@ function assertHangKills(dir: string, mode: string): void { for (const mode of ["external", "local", "remote-self", "audit", "audit-load-failed", "load-failed", "invalid-sha", "load-hang", "fallback-read-hang", "fallback-disable-hang", "load-retry"]) { test(`real workflow loader/launcher: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-policy-")); - const env = { ...process.env, ...timeoutEnvironment(dir, mode), PATH: dir + ":" + process.env.PATH, RUNNER_TEMP: dir, + const env = { ...process.env, ...timeoutEnvironment(dir), PATH: dir + ":" + process.env.PATH, RUNNER_TEMP: dir, RETRY_MARKER: join(dir, "retried"), POLICY_SOURCE: join(root, "scripts/dependabot-merge.ts"), WORKFLOW_SHA: mode === "invalid-sha" ? "bad" : mode === "local" ? trusted : head, WORKFLOW_REF: `unbraind/pm-github/.github/workflows/pm-github-dependabot-merge.yml@${mode.startsWith("local") ? "refs/pull/7/merge" : head}`, CALLER_REF: "refs/pull/7/merge", @@ -358,7 +358,7 @@ test("CLI adapter resolves a unique fixture command against the current PATH", ( } finally { process.env.PATH = old; rmSync(dir, { recursive: true, force: true }); } }); -for (const mode of ["default-timeout", "armed", "clear", "closed", "malformed-api", "api-error", "denied", "hang-read", "hang-disable", "retry-disable"]) { +for (const mode of ["armed", "clear", "closed", "malformed-api", "api-error", "denied", "hang-read", "hang-disable", "retry-disable"]) { test(`pre-tooling revocation shell: ${mode}`, () => { const dir = mkdtempSync(join(tmpdir(), "dependabot-revoke-")); try { @@ -384,9 +384,8 @@ else fi `, { mode: 0o755 }); installFixtureTimeout(dir); - const env = { ...process.env, ...timeoutEnvironment(dir, mode), PATH: dir + ":" + process.env.PATH, RETRY_MARKER: join(dir, "retried"), REPOSITORY: "acme/widgets", PR_NUMBER: "7", + const env = { ...process.env, ...timeoutEnvironment(dir), PATH: dir + ":" + process.env.PATH, RETRY_MARKER: join(dir, "retried"), REPOSITORY: "acme/widgets", PR_NUMBER: "7", MODE: mode, RECEIPTS: join(dir, "receipts"), GITHUB_STEP_SUMMARY: join(dir, "summary") }; - if (mode === "default-timeout") delete (env as NodeJS.ProcessEnv).DEPENDABOT_GH_TIMEOUT_SECONDS; const result = spawnSync("bash", ["-c", shell], { cwd: dir, env, encoding: "utf8", timeout: 120_000, killSignal: "SIGKILL" }); assert.equal(result.status, mode === "denied" || mode === "hang-disable" ? 1 : 0, result.stderr); if (mode === "denied" || mode === "hang-disable") assert.match(result.stdout, /Cannot revoke auto-merge/); @@ -404,27 +403,6 @@ fi -test("both workflow shells reject invalid deadline overrides before any CLI call", () => { - const source = parse(readFileSync(join(root, ".github/workflows/pm-github-dependabot-merge.yml"), "utf8")) as { - jobs: Record; - }; - const dir = mkdtempSync(join(tmpdir(), "dependabot-invalid-deadline-")); - try { - const receipt = join(dir, "called"); - writeFileSync(join(dir, "gh"), `#!/bin/bash\nprintf called > "$CLI_RECEIPT"\n`, { mode: 0o755 }); - for (const step of source.jobs["dependabot-merge"]!.steps.filter(step => step.name === "Revoke previous auto-merge before tooling" || step.name === "Load trusted merge policy")) { - for (const invalid of ["", "0", "-1", "1.5", "61", "Infinity", "1e2", " 7", "07", "7\n"]) { - const result = spawnSync("bash", ["-c", step.run!], { encoding: "utf8", env: { - ...process.env, PATH: dir + ":" + process.env.PATH, DEPENDABOT_GH_TIMEOUT_SECONDS: invalid, CLI_RECEIPT: receipt, - } }); - assert.equal(result.status, 1, result.stderr); - assert.match(result.stdout, /timeout must be an integer from 1 to 60 seconds/); - assert.equal(existsSync(receipt), false); - } - } - } finally { rmSync(dir, { recursive: true, force: true }); } -}); - test("README describes the actual author guard without an owner restriction", () => { const readme = readFileSync(join(root, "README.md"), "utf8"); assert.match(readme, /workflow checks the Dependabot author, then the\s+metadata group/); From 324c390e154dad11269e27ebbcfabf91d2b39fe3 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:41:38 +0100 Subject: [PATCH 18/19] fix: identify failed audit revocations and correct baseline evidence --- .../features/pm-github-dependabot-merge.toon | 25 ++++++++++--- .../history/pm-github-dependabot-merge.jsonl | 11 ++++++ .agents/pm/history/pm-github-gaq8.jsonl | 8 +++++ .agents/pm/issues/pm-github-gaq8.toon | 10 ++++-- scripts/dependabot-merge.ts | 10 ++++-- test/dependabot-merge.test.ts | 36 ++++++++++++++++--- 6 files changed, 86 insertions(+), 14 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index c43cbe18..0ad77c9e 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,10 +6,12 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T13:45:39.879Z" +updated_at: "2026-11-05T14:41:17.485Z" +assignee: codex-agent +claim_principal: codex-agent author: codex-agent acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." -comments[56]{created_at,author,text}: +comments[62]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -66,6 +68,12 @@ comments[56]{created_at,author,text}: "2026-11-05T13:42:02.005Z",codex-agent,"Final standalone publication-attestation check found a real integration regression in the first source push: the canonical auditor treats a variable timeout duration as an unresolved executable position. Corrected the workflow to retain its literal 60-second pre-tooling and loader deadline; TypeScript policy timeout remains injectable through the validated runtime binding or millisecond option. Shell hang fixtures remain readiness-controlled and verify actual kills, so they need no short startup deadline. Removed the now-inapplicable shell-override cases and updated documentation. This supersedes the earlier statement that the runtime override applies to both shells. Production audit and package dry run passed. No attestation scanner policy or dependency was changed." "2026-11-05T13:45:30.757Z",codex-agent,"Final behavioral proofs supersede the earlier shell-injection variant: restoring only installFixtureTimeout from b4b9cbe makes all 5 shell hang cases fail their actual-kill receipts; restoring only githubCli makes both selected timeout/configuration tests fail, including when the current sanitized error wording is retained; changing only SIGKILL to SIGTERM fails the blocking-child case. Restored policy passes all 7 selected cases. In a separate disposable Git copy, the existing own-workflows attestation test passes fixed baseline, fails 1/1 when the variable shell duration from 028db82 is restored, then passes 1/1 after restoration. All mutations were isolated from the package worktree." "2026-11-05T13:45:34.798Z",codex-agent,"Final focused evidence: pinned pm runner passed Node 116/116, native Bun 116/116, caller subset 11/11; combined policy and publication-attestation Node suite passed 124/124, zero failures/skips. Policy-only c8 is exact 100/100/100/100. Test typecheck, lint, 274 documented declarations, zero-threshold duplication, production audit (zero vulnerabilities), package dry run (75 files) and publication attestation pass. Source CI and CodeQL remained queued at the 30-minute observation cutoff (13:36 UTC): https://github.com/unbraind/pm-github/actions/runs/42032806093 and https://github.com/unbraind/pm-github/actions/runs/42032806238 . Full release gates verified this round: 0/2. The local npm gate passed prechecks/privacy and progressed through workflow fixtures, then its positively identified process tree was stopped at cutoff; termination receipts are not a test or coverage pass. The serial local Bun release gate did not start. Neither incomplete gate is approval; fresh final-head CI remains required. No further source-CI polling. Both original review findings are fixed; latest complete review refresh has no newer findings or outside-diff additions. No replies, resolutions, mentions, merges or releases. No new PM CLI/SDK defect was established. Release only this agent claim and retain the item in progress for orchestrator verification." + "2026-11-05T14:11:08.055Z",codex-agent,"Round 5 at fadf64d: fetched all review threads and complete comments; exactly PRRT_kwDOTbN4pM60GSm9 and PRRT_kwDOTbN4pM60GSnR remain unresolved, with no outside-diff findings in the latest CodeRabbit review. Remote head is current and includes origin/main. Plan: append an evidence-based correction to pm-github-gaq8 without changing existing history bytes, include the first 20 failed audit PR numbers plus the omitted count in the operational error, test single/multiple/mixed/bounded failures and behavioral revert, then run unchanged npm and Bun release gates. No thread replies, resolution, merge or release." + "2026-11-05T14:12:13.699Z",codex-agent,"Self-review found the same unsupported npm ci attribution in this feature item baseline comment at 08:35:31. That attribution is also withdrawn: the closeout fixture shim intercepts ci --ignore-scripts; the original failing executable and invocation were not captured, so no specific EUSAGE cause is established. The original feature and pm-github-gaq8 history events remain intact. The error review found the audit aggregation is the only multi-PR revocation path discarding failed target identities; it now includes up to 20 validated PR numbers and an omitted count while retaining the total, message prefix and retry warning." + "2026-11-05T14:14:01.770Z",codex-agent,"Round 5 regression evidence: node --test --test-concurrency=2 test/dependabot-merge.test.ts passed 120/120, zero failures/skips. Pinned pm test --run --progress --match=--test-name-pattern=audit passed 25/25 audit cases; result tracking remains disabled and was not changed. Five new diagnostics regressions pass fixed (5/5), fail with only auditDependabotMerges restored to the reviewed fadf64d implementation (0/5 pass, 5/5 fail), then pass after restoration (5/5), all in a disposable non-git copy. History prefix verification against both reviewed HEAD and origin/main passed for every changed history stream; original records were not edited. Full PR self-review checked workflow, policy, README, tests and tracker records for the same discarded-target and unsupported-attribution classes; the feature-item correction covers the only additional attribution instance. Test TypeScript build passes; committed dist inventory is empty." + "2026-11-05T14:16:14.178Z",codex-agent,"Native Bun focused suite also passed 120/120 with zero failures. npm run changelog:full and npm run changelog:check passed after the append-only corrections. npm full release gate has passed typecheck, build, 274 documented declarations, lint and zero-threshold duplication; full gates are serial with isolated HOME/Git configuration, public npm config and inherited credentials removed. Existing SDK compensation quarantines remain unchanged; any skipped tests will be reported separately from passes." + "2026-11-05T14:37:54.878Z",codex-agent,"Full npm gate progress: the unchanged schedule without repository payload closeout workflow case passed (226775 ms). This new passing receipt is recorded on pm-github-gaq8; it does not restore the withdrawn historical EUSAGE attribution. The complete suite remains in progress with no reported failures." + "2026-11-05T14:41:17.484Z",codex-agent,"Preparing the source push so exact-head CI can run while the serial local release gates continue. Focused Node/Bun suites pass 120/120 each; linked audit suite passes 25/25; behavioral revert fails all five new diagnostics cases and restoration passes all five. Both round-5 threads are addressed, including append-only corrections on both affected PM items. Strict health passes all ten checks with zero warnings. Full npm gate is still running through existing closeout workflow integrations; no complete release gate is counted as passed yet. CI will be observed within the 30-minute cap and any pending checks reported honestly." files[7]: - path: .agents/pm/chores/pm-github-dependabot-activation.toon scope: project @@ -80,9 +88,11 @@ files[7]: scope: project - path: scripts/dependabot-merge.ts scope: project + note: Bounded failed audit target diagnostics - path: test/dependabot-merge.test.ts scope: project -tests[3]: + note: Single multiple boundary overflow and mixed audit failures +tests[4]: - command: nice -n 10 node --test --test-concurrency=2 test/dependabot-merge.test.ts scope: project timeout_seconds: 120 @@ -106,6 +116,13 @@ tests[3]: created_at: "2026-11-05T12:22:38.534Z" source_kind: local_mutation source_ref: feat/reusable-dependabot-merge + - command: node --test --test-concurrency=2 --test-name-pattern=audit test/dependabot-merge.test.ts + scope: project + provenance: + author: codex-agent + created_at: "2026-11-05T14:12:44.957Z" + source_kind: local_mutation + source_ref: feat/reusable-dependabot-merge docs[1]{path,scope,note}: - README.md,project,Caller migration token requirements and asynchronous auto-merge limits + README.md,project,Trusted caller and eventual revocation contract body: "" diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index f11bed88..f6998c8b 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -76,3 +76,14 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T13:45:30.757Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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/54","value":{"created_at":"2026-11-05T13:45:30.757Z","author":"codex-agent","text":"Final behavioral proofs supersede the earlier shell-injection variant: restoring only installFixtureTimeout from b4b9cbe makes all 5 shell hang cases fail their actual-kill receipts; restoring only githubCli makes both selected timeout/configuration tests fail, including when the current sanitized error wording is retained; changing only SIGKILL to SIGTERM fails the blocking-child case. Restored policy passes all 7 selected cases. In a separate disposable Git copy, the existing own-workflows attestation test passes fixed baseline, fails 1/1 when the variable shell duration from 028db82 is restored, then passes 1/1 after restoration. All mutations were isolated from the package worktree."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:45:30.757Z"}],"before_hash":"ecf7293e966015fa5a5b8b2c4afa2be6d9977a0cc9af6dc654d94e62b3aab9cb","after_hash":"f711520d1f1c6abe5f11472d724c133ebee2098a30c031ffe647e50bfd8cca72","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":"c6eda1ad7caee8f8cd7d82edfe80e1cf7c3ac83c1e59f34a1b6be707758dbad6"} {"hash_algorithm":"sha256","ts":"2026-11-05T13:45:34.798Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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/55","value":{"created_at":"2026-11-05T13:45:34.798Z","author":"codex-agent","text":"Final focused evidence: pinned pm runner passed Node 116/116, native Bun 116/116, caller subset 11/11; combined policy and publication-attestation Node suite passed 124/124, zero failures/skips. Policy-only c8 is exact 100/100/100/100. Test typecheck, lint, 274 documented declarations, zero-threshold duplication, production audit (zero vulnerabilities), package dry run (75 files) and publication attestation pass. Source CI and CodeQL remained queued at the 30-minute observation cutoff (13:36 UTC): https://github.com/unbraind/pm-github/actions/runs/42032806093 and https://github.com/unbraind/pm-github/actions/runs/42032806238 . Full release gates verified this round: 0/2. The local npm gate passed prechecks/privacy and progressed through workflow fixtures, then its positively identified process tree was stopped at cutoff; termination receipts are not a test or coverage pass. The serial local Bun release gate did not start. Neither incomplete gate is approval; fresh final-head CI remains required. No further source-CI polling. Both original review findings are fixed; latest complete review refresh has no newer findings or outside-diff additions. No replies, resolutions, mentions, merges or releases. No new PM CLI/SDK defect was established. Release only this agent claim and retain the item in progress for orchestrator verification."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T13:45:34.798Z"}],"before_hash":"f711520d1f1c6abe5f11472d724c133ebee2098a30c031ffe647e50bfd8cca72","after_hash":"1081625a22df3b517af060e50b90aed1a77733b11d86f2395959ed27aa858197","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":"a67bfca3d1f7c3faf82c3c812eb199fe111986be13ec919f4b5ad4a3b547fb39"} {"hash_algorithm":"sha256","ts":"2026-11-05T13:45:39.879Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"bce5bea7edf8ec9ad4745451","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-05T13:45:39.879Z"}],"before_hash":"1081625a22df3b517af060e50b90aed1a77733b11d86f2395959ed27aa858197","after_hash":"d5193e46dd40d70baffc63201bf5ba58ebeaa374839106f87d05d5f6c8d3560d","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":"777a67505d5a9b78062d5671af00a48c34951100a8d45bd417efda55408c61c9"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:10:59.239Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:10:59.239Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"d5193e46dd40d70baffc63201bf5ba58ebeaa374839106f87d05d5f6c8d3560d","after_hash":"b3b6c102572af87c533024c4e24e3e77d73cf85ebae05ec7d22d62ae838d994c","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":"15b1c93e3de37bb8b88cb7b8cf4a8508a09246b1c3018fc1e977d3139f79c12f"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:11:08.056Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/56","value":{"created_at":"2026-11-05T14:11:08.055Z","author":"codex-agent","text":"Round 5 at fadf64d: fetched all review threads and complete comments; exactly PRRT_kwDOTbN4pM60GSm9 and PRRT_kwDOTbN4pM60GSnR remain unresolved, with no outside-diff findings in the latest CodeRabbit review. Remote head is current and includes origin/main. Plan: append an evidence-based correction to pm-github-gaq8 without changing existing history bytes, include the first 20 failed audit PR numbers plus the omitted count in the operational error, test single/multiple/mixed/bounded failures and behavioral revert, then run unchanged npm and Bun release gates. No thread replies, resolution, merge or release."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:11:08.056Z"}],"before_hash":"b3b6c102572af87c533024c4e24e3e77d73cf85ebae05ec7d22d62ae838d994c","after_hash":"8de749d5169885d75b6a1667c578b6abddd915029a49c804fa2ff7acc1e05a7e","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":"d73c6f0cb1841399045746d59ed4b9d0a4c7a5d9856ca7e81e08e991668c4b48"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:12:13.699Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/57","value":{"created_at":"2026-11-05T14:12:13.699Z","author":"codex-agent","text":"Self-review found the same unsupported npm ci attribution in this feature item baseline comment at 08:35:31. That attribution is also withdrawn: the closeout fixture shim intercepts ci --ignore-scripts; the original failing executable and invocation were not captured, so no specific EUSAGE cause is established. The original feature and pm-github-gaq8 history events remain intact. The error review found the audit aggregation is the only multi-PR revocation path discarding failed target identities; it now includes up to 20 validated PR numbers and an omitted count while retaining the total, message prefix and retry warning."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:12:13.699Z"}],"before_hash":"8de749d5169885d75b6a1667c578b6abddd915029a49c804fa2ff7acc1e05a7e","after_hash":"d2abc7d6871339ccbae6b50af755af4b5de7f84a542afbaa01bf1def4bb7cdd0","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":"24133f8e46f42d68477c9dae2c74a3f227b9a5afdfb522c2c662b265213369f7"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:12:18.082Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/5/note","value":"Bounded failed audit target diagnostics"},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:12:18.082Z"}],"before_hash":"d2abc7d6871339ccbae6b50af755af4b5de7f84a542afbaa01bf1def4bb7cdd0","after_hash":"3b238c8d56cb94716e1942bd1a926c44e0969bd7f187c84c65a9594dcb3688aa","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":"0038d0da737570f4423f87ec925ef489e03f8705f78d9e769906c27daf6a8e46"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:12:21.780Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/6/note","value":"Single multiple boundary overflow and mixed audit failures"},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:12:21.780Z"}],"before_hash":"3b238c8d56cb94716e1942bd1a926c44e0969bd7f187c84c65a9594dcb3688aa","after_hash":"b9b7e4a0e73fcaabf809e33d170d3a427056367edd30b54afd5f1e6d082e6bc0","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":"ce2944d5955c9f2bbc0ae2b8cc295520a7a664b5831f84990eb11d492f5564ab"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:12:25.781Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/0/note","value":"Trusted caller and eventual revocation contract"},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:12:25.781Z"}],"before_hash":"b9b7e4a0e73fcaabf809e33d170d3a427056367edd30b54afd5f1e6d082e6bc0","after_hash":"5c13b9b7132385c34a0d132d2f14a4f225f7bee4c07e365fed65588622acf527","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":"fee38f9f6a5e8f209d8f4c8a8478a1dd02f87bc5ae68c9b0a799448211405e9d"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:12:45.508Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/3","value":{"command":"node --test --test-concurrency=2 --test-name-pattern=audit test/dependabot-merge.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-11-05T14:12:44.957Z","source_kind":"local_mutation","source_ref":"feat/reusable-dependabot-merge"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:12:45.508Z"}],"before_hash":"5c13b9b7132385c34a0d132d2f14a4f225f7bee4c07e365fed65588622acf527","after_hash":"9ba339150bcda05bb7c7a079de4df10ec8a4804e1db5edee05ef4e798cbcc9f8","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":"5456808722c895d22c5f78bcd35436b3c92792918e1e928cdf90a0a0126e8a21"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:14:01.770Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/58","value":{"created_at":"2026-11-05T14:14:01.770Z","author":"codex-agent","text":"Round 5 regression evidence: node --test --test-concurrency=2 test/dependabot-merge.test.ts passed 120/120, zero failures/skips. Pinned pm test --run --progress --match=--test-name-pattern=audit passed 25/25 audit cases; result tracking remains disabled and was not changed. Five new diagnostics regressions pass fixed (5/5), fail with only auditDependabotMerges restored to the reviewed fadf64d implementation (0/5 pass, 5/5 fail), then pass after restoration (5/5), all in a disposable non-git copy. History prefix verification against both reviewed HEAD and origin/main passed for every changed history stream; original records were not edited. Full PR self-review checked workflow, policy, README, tests and tracker records for the same discarded-target and unsupported-attribution classes; the feature-item correction covers the only additional attribution instance. Test TypeScript build passes; committed dist inventory is empty."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:14:01.770Z"}],"before_hash":"9ba339150bcda05bb7c7a079de4df10ec8a4804e1db5edee05ef4e798cbcc9f8","after_hash":"55910bec75ff6d21a396c5d98284a4e45faaad23234f485e38e9aa513eb21ffb","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":"e89853e017ede4453f3ac24bd4df9d7029e246e4df9de53fbb8c411fa588bb57"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:16:14.196Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/59","value":{"created_at":"2026-11-05T14:16:14.178Z","author":"codex-agent","text":"Native Bun focused suite also passed 120/120 with zero failures. npm run changelog:full and npm run changelog:check passed after the append-only corrections. npm full release gate has passed typecheck, build, 274 documented declarations, lint and zero-threshold duplication; full gates are serial with isolated HOME/Git configuration, public npm config and inherited credentials removed. Existing SDK compensation quarantines remain unchanged; any skipped tests will be reported separately from passes."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:16:14.196Z"}],"before_hash":"55910bec75ff6d21a396c5d98284a4e45faaad23234f485e38e9aa513eb21ffb","after_hash":"d01e5f576fe913d17c85b59ea8a3e029aecbf64e13c58e25e8a71e131b32327c","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":"88d3259ff110567d695ce0343039da01838a7889d4b806aadf3b6aaf040c746c"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:37:54.880Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/60","value":{"created_at":"2026-11-05T14:37:54.878Z","author":"codex-agent","text":"Full npm gate progress: the unchanged schedule without repository payload closeout workflow case passed (226775 ms). This new passing receipt is recorded on pm-github-gaq8; it does not restore the withdrawn historical EUSAGE attribution. The complete suite remains in progress with no reported failures."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:37:54.880Z"}],"before_hash":"d01e5f576fe913d17c85b59ea8a3e029aecbf64e13c58e25e8a71e131b32327c","after_hash":"43aff9c15ecc6be9e6b9d5f137eb76fcb6c7fc9ab0ab2dbb1071f9adb5aec773","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":"e1b520a842baf7fab404eaa411a8f6b2ecc62665312d833ea218f9d7ff0ef7a6"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:41:17.485Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/61","value":{"created_at":"2026-11-05T14:41:17.484Z","author":"codex-agent","text":"Preparing the source push so exact-head CI can run while the serial local release gates continue. Focused Node/Bun suites pass 120/120 each; linked audit suite passes 25/25; behavioral revert fails all five new diagnostics cases and restoration passes all five. Both round-5 threads are addressed, including append-only corrections on both affected PM items. Strict health passes all ten checks with zero warnings. Full npm gate is still running through existing closeout workflow integrations; no complete release gate is counted as passed yet. CI will be observed within the 30-minute cap and any pending checks reported honestly."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:41:17.485Z"}],"before_hash":"43aff9c15ecc6be9e6b9d5f137eb76fcb6c7fc9ab0ab2dbb1071f9adb5aec773","after_hash":"46f4c81ff1c13e686995bfe8207257e13775dd12119010ad3568d2d964aebe0c","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":"64fe97804eadb46cfc9ae96262eb3e0080d273abf7a51af4b24547ad21d25376"} diff --git a/.agents/pm/history/pm-github-gaq8.jsonl b/.agents/pm/history/pm-github-gaq8.jsonl index 569ff3d2..4ca6c1a8 100644 --- a/.agents/pm/history/pm-github-gaq8.jsonl +++ b/.agents/pm/history/pm-github-gaq8.jsonl @@ -1,2 +1,10 @@ {"hash_algorithm":"sha256","ts":"2026-11-03T01:36:20.521Z","author":"pi-ollama","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.3:cloud","agent_model_source":"environment","agent_instance":"fffd5142cf13337359aa8b1f","agent_provenance":{"model":{"value":"glm-5.3:cloud","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-gaq8"},{"op":"add","path":"/metadata/title","value":"closeout-workflow.test.ts fails wholesale on the local shared host while CI stays green on main"},{"op":"add","path":"/metadata/description","value":"Running the closeout workflow suite on the package worktree fails nearly every test on pristine main (assertions receive git branch-switch output and a pm 'Command failed' receipt instead of expected diagnostics), while the CI workflow on the same main commit is green. The suite also exceeds 4 minutes locally and hits local timeouts. Root-cause the host-local incompatibility (host git version prints advice output the fixtures do not expect) or make the fixtures version-tolerant; out of scope for the release-tag normalization work."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["regression","test"]},{"op":"add","path":"/metadata/created_at","value":"2026-11-03T01:36:20.521Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-11-03T01:36:20.521Z"},{"op":"add","path":"/metadata/author","value":"pi-ollama"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"94886e60dad04ae37f0050f2f0582027306c31074b7fb23634b8a012b21c389c","item_hash_version":3,"message":"","event_class":"substantive","record_hash_version":1,"record_hash":"9929569a61b7b9df20da0249aa2ab88e008c563faab6fbbf8669388ac95f3f6a"} {"hash_algorithm":"sha256","ts":"2026-11-05T08:35:28.981Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f8c646e54f65d0e947913d44","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-05T08:35:28.981Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-11-05T08:35:28.981Z","author":"codex-agent","text":"PR 221 review-round baseline confirmation: with Node 26.13.0/npm 11.20.0, public npm config and isolated Git config, the single schedule-without-repository-payload scenario fails on both the branch and a disposable origin/main snapshot (650797d). Exact command: nice -n 10 node --test --test-concurrency=2 --test-name-pattern=\"schedule without repository payload\" test/closeout-workflow.test.ts . Failure is npm EUSAGE: npm ci requires a package-lock in the synthetic caller, despite the fixture npm shim. The closeout workflow and test are byte-identical between branch and main. This is an environment-specific baseline fixture failure, not attributed to the Dependabot policy changes. No fixture or threshold was weakened. Full release evidence is being collected separately in CI on c21d40e."}]}],"before_hash":"94886e60dad04ae37f0050f2f0582027306c31074b7fb23634b8a012b21c389c","after_hash":"de918bd8e1ccf4e2f5c9cba799eaa7312d6dc5f58d4b90b2bb3d498b75aac931","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":"0b4307510d3e4f871ecbefcda313bfd0f6bfda4513b307aea012f5e5179e8625"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:11:12.379Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:11:12.379Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"de918bd8e1ccf4e2f5c9cba799eaa7312d6dc5f58d4b90b2bb3d498b75aac931","after_hash":"5f2175d05021c273f93d2bb13ccd64136fc6a7f0b8be7b6b7b4ccbd3081c4c14","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":"df1085282acd9f8b721929ee3c97f56fc8636f1a147e25382e01e8e6575b160b"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:11:16.577Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:11:16.577Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"5f2175d05021c273f93d2bb13ccd64136fc6a7f0b8be7b6b7b4ccbd3081c4c14","after_hash":"4291c2da835beb03d65648ee74ecd06e893240284283c48f419a617f0a8b9216","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":"512de60619196cfdd9aecfc6ba3d19b4876d2575790d9d07967edf4832458401"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:11:20.359Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:11:20.358Z","author":"codex-agent","text":"Correction to the 2026-11-05 baseline comment: withdraw the attribution of npm EUSAGE to the closeout workflow npm ci call. At reviewed head fadf64d, test/closeout-workflow.test.ts creates an npm shim that exits zero for ci --ignore-scripts and prepends its command directory to the workflow PATH; the workflow uses that exact invocation. The historical failing process invocation and resolved executable were not captured, so its actual cause is unestablished. The lockfile diagnosis and environment-specific causal claim are unsupported. This correction retains the original hashed event unchanged; the existing baseline issue remains open for investigation."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:11:20.359Z"}],"before_hash":"4291c2da835beb03d65648ee74ecd06e893240284283c48f419a617f0a8b9216","after_hash":"3b73ded9162d2060cc16d7803b0838f5c03a411cd491bf2b357bacb6916c7f59","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":"154e3c3cbc4614d290bd06d325cfc9c21fca60f9fcde63e69476dceff9251815"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:12:31.740Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:12:31.740Z"},{"op":"add","path":"/metadata/files","value":[{"path":"test/closeout-workflow.test.ts","scope":"project","note":"Evidence for npm shim and workflow PATH"}]}],"before_hash":"3b73ded9162d2060cc16d7803b0838f5c03a411cd491bf2b357bacb6916c7f59","after_hash":"608b1547b6f24a585d716a30415ca704809df4bba530a1a39838945515742307","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":"5af94f5fcddf9cff4b5e966d9506a8b924083e16460d31393618672a9cb3cb13"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:12:38.969Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:12:38.969Z"},{"op":"add","path":"/metadata/docs","value":[{"path":".github/workflows/pm-github-closeout.yml","scope":"project","note":"Exact npm ci invocation intercepted by fixture"}]}],"before_hash":"608b1547b6f24a585d716a30415ca704809df4bba530a1a39838945515742307","after_hash":"19ff95113f4f60fa9afca462ec26112a6950e64be89d525fa1c1d2d1ea3339de","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":"b2199c60e0b05ef685130eb02a4250dd8ce5528380c3807d25850f13a084bcc3"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:14:08.421Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:14:08.421Z"},{"op":"replace","path":"/metadata/status","value":"open"}],"before_hash":"19ff95113f4f60fa9afca462ec26112a6950e64be89d525fa1c1d2d1ea3339de","after_hash":"975553a3cc5cc4108937615e67e64caafb29355b59509047389f20f34b2be958","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":"12664f520f36e7f0f7bbb81856dc94fc958e2aa728ddb7a29b0113bd6fb28298"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:14:12.106Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:14:12.106Z"}],"before_hash":"975553a3cc5cc4108937615e67e64caafb29355b59509047389f20f34b2be958","after_hash":"baebd6c4c5d1f4a757bb5fc7fe4f1533879ab52d2740c04e2022bc1a04fb1e29","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":"5b1f6672355fc4f4eebc7205e5d232d98ff0a09e8970330d0258b963ba70040c"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:37:47.768Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T14:37:47.768Z","author":"codex-agent","text":"Current verification following the append-only correction: npm run release:check on Node 24.19.0/npm 11.17.0 with isolated HOME/Git configuration and public npm config passed the unchanged schedule without repository payload scenario (226775 ms), including its two closeout publication runs. The full gate is still running. This is a present-run pass, not a reconstruction or diagnosis of the historical EUSAGE process; its invocation and resolved executable remain unknown."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:37:47.768Z"}],"before_hash":"baebd6c4c5d1f4a757bb5fc7fe4f1533879ab52d2740c04e2022bc1a04fb1e29","after_hash":"04dfb477ebd91b3237e7e6dd1e9744fada59b1cdf7c466ee10b0d0c8ca021976","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":"831a2827b8aa02d941e0793f5c2079505070bf540316c1eea0d84accb76f5249"} diff --git a/.agents/pm/issues/pm-github-gaq8.toon b/.agents/pm/issues/pm-github-gaq8.toon index 62491bc5..f5cc72e2 100644 --- a/.agents/pm/issues/pm-github-gaq8.toon +++ b/.agents/pm/issues/pm-github-gaq8.toon @@ -6,8 +6,14 @@ status: open priority: 2 tags[2]: regression,test created_at: "2026-11-03T01:36:20.521Z" -updated_at: "2026-11-05T08:35:28.981Z" +updated_at: "2026-11-05T14:37:47.768Z" author: pi-ollama -comments[1]{created_at,author,text}: +comments[3]{created_at,author,text}: "2026-11-05T08:35:28.981Z",codex-agent,"PR 221 review-round baseline confirmation: with Node 26.13.0/npm 11.20.0, public npm config and isolated Git config, the single schedule-without-repository-payload scenario fails on both the branch and a disposable origin/main snapshot (650797d). Exact command: nice -n 10 node --test --test-concurrency=2 --test-name-pattern=\"schedule without repository payload\" test/closeout-workflow.test.ts . Failure is npm EUSAGE: npm ci requires a package-lock in the synthetic caller, despite the fixture npm shim. The closeout workflow and test are byte-identical between branch and main. This is an environment-specific baseline fixture failure, not attributed to the Dependabot policy changes. No fixture or threshold was weakened. Full release evidence is being collected separately in CI on c21d40e." + "2026-11-05T14:11:20.358Z",codex-agent,"Correction to the 2026-11-05 baseline comment: withdraw the attribution of npm EUSAGE to the closeout workflow npm ci call. At reviewed head fadf64d, test/closeout-workflow.test.ts creates an npm shim that exits zero for ci --ignore-scripts and prepends its command directory to the workflow PATH; the workflow uses that exact invocation. The historical failing process invocation and resolved executable were not captured, so its actual cause is unestablished. The lockfile diagnosis and environment-specific causal claim are unsupported. This correction retains the original hashed event unchanged; the existing baseline issue remains open for investigation." + "2026-11-05T14:37:47.768Z",codex-agent,"Current verification following the append-only correction: npm run release:check on Node 24.19.0/npm 11.17.0 with isolated HOME/Git configuration and public npm config passed the unchanged schedule without repository payload scenario (226775 ms), including its two closeout publication runs. The full gate is still running. This is a present-run pass, not a reconstruction or diagnosis of the historical EUSAGE process; its invocation and resolved executable remain unknown." +files[1]{path,scope,note}: + test/closeout-workflow.test.ts,project,Evidence for npm shim and workflow PATH +docs[1]{path,scope,note}: + .github/workflows/pm-github-closeout.yml,project,Exact npm ci invocation intercepted by fixture body: "" diff --git a/scripts/dependabot-merge.ts b/scripts/dependabot-merge.ts index 7778c9b8..96aa145f 100644 --- a/scripts/dependabot-merge.ts +++ b/scripts/dependabot-merge.ts @@ -153,14 +153,18 @@ export function auditDependabotMerges(repository: string, gh: (args: readonly st throw new Error("Incomplete PR audit listing; retry immediately."); } const results: string[] = []; - let failures = 0; + const failed: number[] = []; for (const pr of pulls.filter(pr => pr.user.login === bot && pr.auto_merge !== null)) { try { const result = runDependabotMerge({ repository, number: String(pr.number), phase: "audit", event: "schedule", actor: "", triggeringActor: "", author: bot, head: "", metadataOutcome: "", group: "", updateType: "", extraGroups: "" }, gh); results.push(`#${pr.number}: ${result.message}`); - } catch { failures++; } + } catch { failed.push(pr.number); } + } + if (failed.length > 0) { + const numbers = failed.slice(0, 20).map(number => `#${number}`).join(", "); + const remaining = failed.length > 20 ? `, plus ${failed.length - 20} more` : ""; + throw new Error(`Cannot complete ${failed.length} auto-merge revocations (${numbers}${remaining}); requests may still be armed. Retry immediately.`); } - if (failures > 0) throw new Error(`Cannot complete ${failures} auto-merge revocations; requests may still be armed. Retry immediately.`); return { armed: false, message: results.join("\n") || "No armed Dependabot requests to audit." }; } diff --git a/test/dependabot-merge.test.ts b/test/dependabot-merge.test.ts index 7d9cb51c..994a0ae6 100644 --- a/test/dependabot-merge.test.ts +++ b/test/dependabot-merge.test.ts @@ -646,11 +646,37 @@ test("audit listing errors and malformed pagination fail with sanitized messages assert.throws(() => auditDependabotMerges("acme/widgets", () => { throw new Error("private remote error"); }), /Cannot read complete PR audit listing/); }); -test("audit continues other PRs after denied revocation and reports operational failure", () => { - const a = auditFixture(); a.pulls.push({ number: 8, user: { login: bot }, auto_merge: {} }); - a.f.commit.author.login = "outside"; a.f.failRevoke = true; - assert.throws(a.execute, /Cannot complete 2 auto-merge revocations/); - assert.equal(a.f.calls.filter(args => args.includes("--disable-auto")).length, 2); +for (const count of [1, 2, 20, 23]) { + test(`audit reports failed PR numbers with a bounded list and continues all ${count} revocations`, () => { + const a = auditFixture(); + const numbers = Array.from({ length: count }, (_, index) => index + 7); + a.pulls.splice(0, a.pulls.length, ...numbers.map(number => ({ number, user: { login: bot }, auto_merge: {} }))); + a.f.commit.author.login = "outside"; a.f.failRevoke = true; + const listed = numbers.slice(0, 20).map(number => `#${number}`).join(", "); + assert.throws(a.execute, { + message: `Cannot complete ${count} auto-merge revocations (${listed}${count > 20 ? `, plus ${count - 20} more` : ""}); requests may still be armed. Retry immediately.`, + }); + assert.deepEqual(a.f.calls.filter(args => args.includes("--disable-auto")).map(args => Number(args[2])), numbers); + }); +} + +test("audit failure report excludes successful revocations and preserved requests", () => { + const requests = new Map([7, 8, 9, 10].map(number => [number, fixture()])); + for (const [number, f] of requests) { + if (number !== 9) f.commit.author.login = "outside"; + f.failRevoke = number === 7 || number === 10; + } + const gh = (args: readonly string[]): string => { + if (args[1]?.includes("/pulls?")) return [...requests.keys()].map(number => JSON.stringify({ number, user: { login: bot }, auto_merge: {} })).join("\n"); + const number = Number(args[0] === "api" ? args[1]!.split("/pulls/")[1]!.split("/")[0] : args[2]); + return requests.get(number)!.gh(args); + }; + assert.throws(() => auditDependabotMerges("acme/widgets", gh), { + message: "Cannot complete 2 auto-merge revocations (#7, #10); requests may still be armed. Retry immediately.", + }); + assert.equal(requests.get(8)!.state.auto_merge, null); + assert.notEqual(requests.get(9)!.state.auto_merge, null); + for (const f of requests.values()) assert.ok(!f.calls.some(args => args.includes("--auto"))); }); test("activation tracker reflects active workflow and keeps fleet acceptance separate", () => { From 83abb9e2946a5cdfa7263f7d97d41ec172b8f2df Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Thu, 5 Nov 2026 16:19:38 +0100 Subject: [PATCH 19/19] docs: record round-five verification and pending release gates --- .agents/pm/features/pm-github-dependabot-merge.toon | 10 ++++++---- .agents/pm/history/pm-github-dependabot-merge.jsonl | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.agents/pm/features/pm-github-dependabot-merge.toon b/.agents/pm/features/pm-github-dependabot-merge.toon index 0ad77c9e..532b25b5 100644 --- a/.agents/pm/features/pm-github-dependabot-merge.toon +++ b/.agents/pm/features/pm-github-dependabot-merge.toon @@ -6,12 +6,10 @@ status: in_progress priority: 2 tags[6]: changelog-unreleased,ci,dependabot,"gh:unbraind/pm-github#221",security,workflows created_at: "2026-11-05T00:19:21.862Z" -updated_at: "2026-11-05T14:41:17.485Z" -assignee: codex-agent -claim_principal: codex-agent +updated_at: "2026-11-05T15:19:27.697Z" author: codex-agent acceptance_criteria: "Foreign authors or committers, metadata failures, API errors, stale heads and unsupported events cannot arm; existing requests are revoked with sanitized notices and summary.; Execute regression cases for opens, rebases, foreign author, foreign committer, major update, over 100 commits and API failures; verify real fleet commit provenance.; Migrate the own caller, document release SHA pinning and extra groups, regenerate changelog and run package gates.; Only trusted pull_request_target events with Dependabot author and actor plus a complete trusted commit list and allowed metadata can arm squash auto-merge.; Scheduled audits independently revoke unsafe requests without arming." -comments[62]{created_at,author,text}: +comments[66]{created_at,author,text}: "2026-11-05T00:19:41.701Z",codex-agent,"No duplicate open item found. Plan: revoke before metadata, load the standalone TypeScript policy only from the reusable workflow commit, check every paginated commit with verified web-flow exception, then arm using an expected-head lease. Real API read of pm-context#211 confirms Dependabot author with verified web-flow committer and a foreign-author/foreign-committer addition. No PR source is checked out or executed. GitHub auto-merge is asynchronous: callers must retain protected-branch checks; revocation on synchronize cannot provide an atomic server-side invariant for future pushes." "2026-11-05T00:33:47.214Z",codex-agent,"Implemented trusted-policy loading, pre-tooling revocation, verified web-flow handling, complete paginated commit/count/head proofs, metadata allowlist and head-bound squash requests. Focused suite: 66 passed. Behavioral revert proof in a disposable copy: removing only the foreign-commit filter made 12 of 61 tests fail; restored suite passed. Read-only production-policy execution against real pm-context#211 refused its foreign addition; all mutation calls were receipts only. Older local gh lacks --slurp, so production uses --paginate with JSON-line output. Documented write-token requirements, first-release pin placeholders and the asynchronous GitHub auto-merge limitation. Full npm tests now running serially before coverage." "2026-11-05T00:39:54.549Z",codex-agent,"The linked pm test run passed all 67 focused cases in plain temporary directories. Strengthened provenance to require valid GitHub verification for both accepted committer accounts, because account attribution can be forged via Git email fields. The 101-commit case now keeps the first page independent and puts the foreign committer only on page two. Pre-tooling revocation and policy-load failures are executable workflow tests. A policy-load API failure produces a successful refusal with summary/notice; inability to revoke is an explicit operational failure. Typecheck, test typecheck, ESLint, docstrings, zero duplication, zero production audit vulnerabilities and the existing object-store privacy audit passed. Changelog regenerated after fetching tags; check passed." @@ -74,6 +72,10 @@ comments[62]{created_at,author,text}: "2026-11-05T14:16:14.178Z",codex-agent,"Native Bun focused suite also passed 120/120 with zero failures. npm run changelog:full and npm run changelog:check passed after the append-only corrections. npm full release gate has passed typecheck, build, 274 documented declarations, lint and zero-threshold duplication; full gates are serial with isolated HOME/Git configuration, public npm config and inherited credentials removed. Existing SDK compensation quarantines remain unchanged; any skipped tests will be reported separately from passes." "2026-11-05T14:37:54.878Z",codex-agent,"Full npm gate progress: the unchanged schedule without repository payload closeout workflow case passed (226775 ms). This new passing receipt is recorded on pm-github-gaq8; it does not restore the withdrawn historical EUSAGE attribution. The complete suite remains in progress with no reported failures." "2026-11-05T14:41:17.484Z",codex-agent,"Preparing the source push so exact-head CI can run while the serial local release gates continue. Focused Node/Bun suites pass 120/120 each; linked audit suite passes 25/25; behavioral revert fails all five new diagnostics cases and restoration passes all five. Both round-5 threads are addressed, including append-only corrections on both affected PM items. Strict health passes all ten checks with zero warnings. Full npm gate is still running through existing closeout workflow integrations; no complete release gate is counted as passed yet. CI will be observed within the 30-minute cap and any pending checks reported honestly." + "2026-11-05T14:47:31.362Z",codex-agent,"Pushed source commit 324c390e154dad11269e27ebbcfabf91d2b39fe3 and updated the PR description with fixes, revert evidence and pending-gate boundaries. Fresh source CI is queued at https://github.com/unbraind/pm-github/actions/runs/42048268193 ; CodeQL is queued at https://github.com/unbraind/pm-github/actions/runs/42048267816 . The source-CI observation deadline is 15:16 UTC; queued jobs are not approval. Committed-object privacy audit passed 8967 objects and approved identities. The local serial release runner continues independently of the CI observation cutoff." + "2026-11-05T15:19:22.771Z",codex-agent,"Round 5 final verification receipt for source 324c390e154dad11269e27ebbcfabf91d2b39fe3: Node and native Bun focused suites each pass 120/120, zero failures/skips; pinned pm linked audit command passes 25/25. Five diagnostics regressions fail 5/5 when only the audit function is reverted and pass 5/5 after restoration in a disposable non-git copy. Policy-only coverage command: node node_modules/c8/bin/c8.js --merge-async --100 --all --include=scripts/dependabot-merge.ts --reports-dir=coverage/review-round5-policy --temp-directory=coverage/review-round5-policy/v8 --reporter=text --reporter=json-summary node --test --test-concurrency=2 test/dependabot-merge.test.ts ; result 120/120 with exact 100/100/100/100 for the policy module only. This does not certify package-wide coverage. Standalone npm run audit:prod, npm run pack:dry-run, npm run changelog:check and npm run verify:release-publish-attestation pass; production audit reports zero vulnerabilities. No dist files are tracked." + "2026-11-05T15:19:24.269Z",codex-agent,"Required CI-cutoff handoff: at the last observation immediately before 15:16 UTC, all four source CI jobs (npm and Bun release checks on Node 22/26) remained queued at https://github.com/unbraind/pm-github/actions/runs/42048268193 ; CodeQL remained queued at https://github.com/unbraind/pm-github/actions/runs/42048267816 . No further source-CI polling. Complete package release gates verified this round: 0/2. Local npm run release:check passed prechecks/privacy and progressed through existing closeout integration cases, including the disputed baseline scenario, with no reported test failures; its independently verified owned process tree was stopped at the required cutoff (13 processes, zero remaining active). This interrupted run is not a passing gate. The serial local bun run release:check did not start. No dependency, threshold or quarantine was weakened. Final receipt push requires fresh exact-head CI and will be reported as pending if queued." + "2026-11-05T15:19:26.106Z",codex-agent,"Final review refresh again fetched both unresolved threads with all comments and complete pagination receipts; exactly PRRT_kwDOTbN4pM60GSm9 and PRRT_kwDOTbN4pM60GSnR remain, with no newer nonempty CodeRabbit review or outside-diff findings. Both valid findings are fixed; existing histories remain byte-prefix extensions. Tool receipt clarification: installed gh pr edit hit a retired GraphQL projectCards field; the subsequent REST body update succeeded and its exact text was read back and verified. No new PM CLI/SDK defect was established. Releasing only my implementation claim; implementation stays in progress and pm-github-gaq8 stays open for orchestrator verification. No review replies/resolutions, mentions, merges, releases or live settings changes were performed. No owned test/watch process remains." files[7]: - path: .agents/pm/chores/pm-github-dependabot-activation.toon scope: project diff --git a/.agents/pm/history/pm-github-dependabot-merge.jsonl b/.agents/pm/history/pm-github-dependabot-merge.jsonl index f6998c8b..a797ea66 100644 --- a/.agents/pm/history/pm-github-dependabot-merge.jsonl +++ b/.agents/pm/history/pm-github-dependabot-merge.jsonl @@ -87,3 +87,8 @@ {"hash_algorithm":"sha256","ts":"2026-11-05T14:16:14.196Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/59","value":{"created_at":"2026-11-05T14:16:14.178Z","author":"codex-agent","text":"Native Bun focused suite also passed 120/120 with zero failures. npm run changelog:full and npm run changelog:check passed after the append-only corrections. npm full release gate has passed typecheck, build, 274 documented declarations, lint and zero-threshold duplication; full gates are serial with isolated HOME/Git configuration, public npm config and inherited credentials removed. Existing SDK compensation quarantines remain unchanged; any skipped tests will be reported separately from passes."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:16:14.196Z"}],"before_hash":"55910bec75ff6d21a396c5d98284a4e45faaad23234f485e38e9aa513eb21ffb","after_hash":"d01e5f576fe913d17c85b59ea8a3e029aecbf64e13c58e25e8a71e131b32327c","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":"88d3259ff110567d695ce0343039da01838a7889d4b806aadf3b6aaf040c746c"} {"hash_algorithm":"sha256","ts":"2026-11-05T14:37:54.880Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/60","value":{"created_at":"2026-11-05T14:37:54.878Z","author":"codex-agent","text":"Full npm gate progress: the unchanged schedule without repository payload closeout workflow case passed (226775 ms). This new passing receipt is recorded on pm-github-gaq8; it does not restore the withdrawn historical EUSAGE attribution. The complete suite remains in progress with no reported failures."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:37:54.880Z"}],"before_hash":"d01e5f576fe913d17c85b59ea8a3e029aecbf64e13c58e25e8a71e131b32327c","after_hash":"43aff9c15ecc6be9e6b9d5f137eb76fcb6c7fc9ab0ab2dbb1071f9adb5aec773","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":"e1b520a842baf7fab404eaa411a8f6b2ecc62665312d833ea218f9d7ff0ef7a6"} {"hash_algorithm":"sha256","ts":"2026-11-05T14:41:17.485Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/61","value":{"created_at":"2026-11-05T14:41:17.484Z","author":"codex-agent","text":"Preparing the source push so exact-head CI can run while the serial local release gates continue. Focused Node/Bun suites pass 120/120 each; linked audit suite passes 25/25; behavioral revert fails all five new diagnostics cases and restoration passes all five. Both round-5 threads are addressed, including append-only corrections on both affected PM items. Strict health passes all ten checks with zero warnings. Full npm gate is still running through existing closeout workflow integrations; no complete release gate is counted as passed yet. CI will be observed within the 30-minute cap and any pending checks reported honestly."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:41:17.485Z"}],"before_hash":"43aff9c15ecc6be9e6b9d5f137eb76fcb6c7fc9ab0ab2dbb1071f9adb5aec773","after_hash":"46f4c81ff1c13e686995bfe8207257e13775dd12119010ad3568d2d964aebe0c","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":"64fe97804eadb46cfc9ae96262eb3e0080d273abf7a51af4b24547ad21d25376"} +{"hash_algorithm":"sha256","ts":"2026-11-05T14:47:31.362Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/62","value":{"created_at":"2026-11-05T14:47:31.362Z","author":"codex-agent","text":"Pushed source commit 324c390e154dad11269e27ebbcfabf91d2b39fe3 and updated the PR description with fixes, revert evidence and pending-gate boundaries. Fresh source CI is queued at https://github.com/unbraind/pm-github/actions/runs/42048268193 ; CodeQL is queued at https://github.com/unbraind/pm-github/actions/runs/42048267816 . The source-CI observation deadline is 15:16 UTC; queued jobs are not approval. Committed-object privacy audit passed 8967 objects and approved identities. The local serial release runner continues independently of the CI observation cutoff."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T14:47:31.362Z"}],"before_hash":"46f4c81ff1c13e686995bfe8207257e13775dd12119010ad3568d2d964aebe0c","after_hash":"c83243fe5fb62b64d8833b462502041dfbfeec995b151911a3926457675ef0f4","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":"82e6f713ef61538b5b310f943a2f6fb8684c27898256d63df946ca3806e42f57"} +{"hash_algorithm":"sha256","ts":"2026-11-05T15:19:22.774Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/63","value":{"created_at":"2026-11-05T15:19:22.771Z","author":"codex-agent","text":"Round 5 final verification receipt for source 324c390e154dad11269e27ebbcfabf91d2b39fe3: Node and native Bun focused suites each pass 120/120, zero failures/skips; pinned pm linked audit command passes 25/25. Five diagnostics regressions fail 5/5 when only the audit function is reverted and pass 5/5 after restoration in a disposable non-git copy. Policy-only coverage command: node node_modules/c8/bin/c8.js --merge-async --100 --all --include=scripts/dependabot-merge.ts --reports-dir=coverage/review-round5-policy --temp-directory=coverage/review-round5-policy/v8 --reporter=text --reporter=json-summary node --test --test-concurrency=2 test/dependabot-merge.test.ts ; result 120/120 with exact 100/100/100/100 for the policy module only. This does not certify package-wide coverage. Standalone npm run audit:prod, npm run pack:dry-run, npm run changelog:check and npm run verify:release-publish-attestation pass; production audit reports zero vulnerabilities. No dist files are tracked."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T15:19:22.774Z"}],"before_hash":"c83243fe5fb62b64d8833b462502041dfbfeec995b151911a3926457675ef0f4","after_hash":"dfbd4851e1904a62e5409a8f710ecf01b6993747a1b46ed5e00ca0cba1c39d47","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":"d9a951a5336ad16fc0d7103de61d193358dbc13cda5e5f9282b8855797c1932c"} +{"hash_algorithm":"sha256","ts":"2026-11-05T15:19:24.269Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/64","value":{"created_at":"2026-11-05T15:19:24.269Z","author":"codex-agent","text":"Required CI-cutoff handoff: at the last observation immediately before 15:16 UTC, all four source CI jobs (npm and Bun release checks on Node 22/26) remained queued at https://github.com/unbraind/pm-github/actions/runs/42048268193 ; CodeQL remained queued at https://github.com/unbraind/pm-github/actions/runs/42048267816 . No further source-CI polling. Complete package release gates verified this round: 0/2. Local npm run release:check passed prechecks/privacy and progressed through existing closeout integration cases, including the disputed baseline scenario, with no reported test failures; its independently verified owned process tree was stopped at the required cutoff (13 processes, zero remaining active). This interrupted run is not a passing gate. The serial local bun run release:check did not start. No dependency, threshold or quarantine was weakened. Final receipt push requires fresh exact-head CI and will be reported as pending if queued."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T15:19:24.269Z"}],"before_hash":"dfbd4851e1904a62e5409a8f710ecf01b6993747a1b46ed5e00ca0cba1c39d47","after_hash":"2c6a7ab67805a1c2d32828b0398551ce2a6ffcc8dabaa9a72bd908a3aaf86b75","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":"abe87a477d34ba01eda3389826a2b891de84db02536f5aa1cf6002f00e883f1d"} +{"hash_algorithm":"sha256","ts":"2026-11-05T15:19:26.107Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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/65","value":{"created_at":"2026-11-05T15:19:26.106Z","author":"codex-agent","text":"Final review refresh again fetched both unresolved threads with all comments and complete pagination receipts; exactly PRRT_kwDOTbN4pM60GSm9 and PRRT_kwDOTbN4pM60GSnR remain, with no newer nonempty CodeRabbit review or outside-diff findings. Both valid findings are fixed; existing histories remain byte-prefix extensions. Tool receipt clarification: installed gh pr edit hit a retired GraphQL projectCards field; the subsequent REST body update succeeded and its exact text was read back and verified. No new PM CLI/SDK defect was established. Releasing only my implementation claim; implementation stays in progress and pm-github-gaq8 stays open for orchestrator verification. No review replies/resolutions, mentions, merges, releases or live settings changes were performed. No owned test/watch process remains."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-05T15:19:26.107Z"}],"before_hash":"2c6a7ab67805a1c2d32828b0398551ce2a6ffcc8dabaa9a72bd908a3aaf86b75","after_hash":"a160c7261e1bedef6aee2f4d5e5866c72b62cbbe4d23f46b9e399068f674ba45","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":"38711d44e352b4714cf30f835c9c162ede2a73bc978f235b49587306232eec7a"} +{"hash_algorithm":"sha256","ts":"2026-11-05T15:19:27.697Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"e3988a40b22786ba4c8da608","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-05T15:19:27.697Z"}],"before_hash":"a160c7261e1bedef6aee2f4d5e5866c72b62cbbe4d23f46b9e399068f674ba45","after_hash":"6ef8add0d5531898ae2e92b6f035ad4c69151c6e6441c498334cc189b619c864","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":"574b1aff09770b36d14e7b07909562d736ed2c382a2f2afd65a4004e57851a4e"}