diff --git a/.agents/pm/features/pm-github-wrmt.toon b/.agents/pm/features/pm-github-wrmt.toon new file mode 100644 index 00000000..762d273b --- /dev/null +++ b/.agents/pm/features/pm-github-wrmt.toon @@ -0,0 +1,43 @@ +id: pm-github-wrmt +title: Regenerate caller changelogs and dispatch required CI for gated sync PRs +description: "Make gated sync reviews independently mergeable with an overridable caller npm script, a final privacy scan including CHANGELOG.md, and explicit caller CI workflow dispatch after PR creation or update. Fleet adoption remains tracked by pm-github-aiok." +type: Feature +status: in_progress +priority: 2 +tags[1]: changelog-unreleased +created_at: "2026-10-14T11:43:07.998Z" +updated_at: "2026-10-14T18:53:48.691Z" +author: codex-agent +comments[20]{created_at,author,text}: + "2026-10-14T11:43:26.402Z",codex-agent,"Measured fleet use: pm-gantt-chart#142 was blocked because bot PR workflows ended action_required and imported closed issues changed the generated changelog. Plan: run caller post-import command, stage CHANGELOG.md with tracker data, scan an explicit combined diff before health and publication, then dispatch configured caller workflows on the pushed branch. Test with real local git repositories and HTTP fixtures; record behavioural revert proofs. No duplicate feature found; fleet rollout is separate in pm-github-aiok." + "2026-10-14T11:48:40.689Z",codex-agent,"Implemented post-import-run and ci-workflows with documented defaults, actions: write, combined tracker/changelog diff scanning, dispatch after successful PR publication, and approval-required run URL notices. Typecheck, test compilation, lint, and generated changelog check pass. Initial linked whole-file run exceeded the runner default 120-second budget; link the focused workflow regressions with an explicit 900-second command budget. Required release gates retain unchanged thresholds and internal import deadlines." + "2026-10-14T11:53:36.296Z",codex-agent,"Focused workflow regressions: 4/4 pass, zero failures/cancellations/skips/todo. Behavioural revert proofs (workflow shell only, unchanged tests): removing the post-import execution fails the published changelog assertion (0/1 pass); excluding CHANGELOG.md from the explicit final diff fails because the secret-bearing local sync succeeds (0/1 pass); removing gh workflow run fails for missing dispatch receipts (0/1 pass). All changes restored. Fixtures use real git repositories, bare remotes, the packed candidate with the pinned host CLI, and local HTTP responses; no live GitHub test writes." + "2026-10-14T12:04:50.070Z",codex-agent,"The complete standalone workflow/import-gate suite passed 34/34 with zero failures, cancellations, skips, or todo, including the native Bun repeat-import deadlines. The final linked focused run (pm test pm-github-wrmt --run --progress) passed 4/4 with an explicit 900-second command budget and no failures/cancellations/skips/todo. Strict health with merge-driver verification passed; one pre-existing advisory covers four stale in-progress items. Full isolated npm and Bun release gates remain required before delivery." + "2026-10-14T12:21:35.364Z",codex-agent,"Initial isolated npm gate: 583/584 tests passed; the sole failure was the canonical publish-attestation auditor rejecting opaque Bash execution of a caller command. Keep that safety gate unchanged. Final input is post-import-script, default changelog:full, invoked through explicit npm run --if-present with a validated script name; callers override the npm script name or disable with an empty string. Updated fixtures use caller package scripts, including failing and secret-generating scripts. Typecheck, lint and attestation verification pass; repeat focused tests, revert proofs and both isolated release gates on this final design." + "2026-10-14T12:27:24.824Z",codex-agent,"Final named-script design: linked pm test run passed 5/5, with zero failures/cancellations/skips/todo. Four behavioural reverts with unchanged tests each failed (0/1, exit 1): remove npm script execution -> stale published changelog; exclude CHANGELOG.md from final diff -> synthetic secret reaches local push; remove gh workflow run -> missing dispatch receipts; remove script-name validation -> raw-command input succeeds. Restored all changes. Canonical attestation check passes without exceptions or threshold changes. Repeating both full release gates under isolated HOME, unset PM_PATH/PM_AUTHOR, and disabled global/system git config." + "2026-10-14T12:47:18.176Z",codex-agent,"Final isolated npm release:check passed (exit 0): 584/584 tests, zero failures/cancellations/skips/todo; full coverage gate passed at unchanged four 100-percent thresholds; source duplication zero; privacy, docstrings, typecheck, build, lint, production audit, pack, changelog check and canonical publish attestation all passed. Next gate is isolated bun run release:check on unchanged implementation." + "2026-10-14T13:10:16.298Z",codex-agent,"Final isolated bun run release:check passed (exit 0): 584/584 tests, zero failures/cancellations/skips/todo. Both npm and Bun measured 12 authored modules with 9584/9584 lines and statements, 234/234 functions, and 2557/2557 branches: exact 100/100/100/100, no skipped source. Both release gates passed typecheck, build, docstrings, lint, zero duplication, privacy, production audit (zero vulnerabilities), pack, changelog check and canonical attestation at unchanged thresholds. Final linked workflow regressions passed 5/5; all four behavioural reverts failed as expected. Delivery is the fix/sync-pr-ci-and-changelog review branch; callers must repin the workflow and package release, grant actions: write and expose matching required checks via workflow_dispatch. Release the claim without closing this Feature; the orchestrator verifies and closes it." + "2026-10-14T13:47:16.164Z",codex-agent,"CodeRabbit round 1: confirmed fail-open publication when caller post-import scripts change paths outside the final privacy scan. Plan: explicit tracker and CHANGELOG.md scope, complete NUL-delimited status checks after post-import and immediately before commit, exact commit diff rescanning, restricted commit pathspec, and real Git adversarial tests with a behavioural revert proof." + "2026-10-14T13:58:52.217Z",codex-agent,"Implemented SDK-resolved scope, complete NUL-delimited status enumeration including rename sources, exact file-list staging and privacy rescanning, and commit --only with literal NUL pathspecs. The allowlist and setup-file restoration policy are documented in README.md. Added six caller-scope cases, five late-change cases, and an independent commit-pathspec bypass control using real Git repositories and local bare remotes. Caller fixture dependency and runtime ignores now match normal package checkouts." + "2026-10-14T14:04:37.784Z",codex-agent,"Positive workflow controls passed 3/3: clean tracker plus changelog publication and repeat update; install-only settings never committed; restricted commit excludes unrelated staged content when scope checks are bypassed. Scope-only behavioural revert in an isolated checkout failed the unchanged regression (0/1, exit 1): the staged-extra caller returned success and pushed a sync branch. The explicit commit pathspec independently prevented the extra file from being included. Running a second revert to reproduce the original unrestricted-commit leak; full isolated release gates and linked regressions remain in progress." + "2026-10-14T14:07:36.027Z",codex-agent,"Second behavioural revert confirmed the original leak: keeping the unchanged bypass-control test and restoring the unrestricted git commit failed (0/1, exit 1) because git cat-file found extra.txt on the bare remote sync branch (status 0 instead of expected 128). Scope-only revert also pushed a sync branch and failed its unchanged no-publication regression. Both safeguards and tests remain restored in the package worktree; only an isolated proof checkout was mutated." + "2026-10-14T14:19:52.798Z",codex-agent,"Final linked regression run passed 4/4 with zero failures, cancellations, skips or todo: six caller out-of-scope cases each failed closed with no remote change, five late-change cases each blocked publication, clean tracker plus changelog publication and repeat update passed, and restricted commit excluded unrelated staged data when checks were bypassed. Exact command: node --test --test-name-pattern=\"caller post-import changes|publication rechecks|restricted commit|pushes only a clean\" test/import-gate.test.ts. Ran via pm test pm-github-wrmt --run --only-last --progress with the linked 1800-second budget. Automatic test-result recording is disabled by repository policy; this comment preserves the verified result without changing that policy." + "2026-10-14T14:30:05.372Z",codex-agent,"Initial isolated npm release:check completed all 587/587 tests with zero failures/cancellations/skips/todo, then failed during c8 coverage aggregation at Node's default 4 GiB heap. No release gate is claimed passed from that attempt. Re-run both complete release commands with NODE_OPTIONS=--max-old-space-size=12288, preserving identity isolation, source inventory and all four 100-percent coverage thresholds. Linked regressions and both behavioural revert proofs already passed." + "2026-10-14T14:33:41.625Z",codex-agent,"Default-heap coverage aggregation follow-up is pm-github-vhi6 (open, no implementation changes in this security fix). The initial full test suite passed 587/587 before c8 ran out of heap; complete npm and Bun release gates are being rerun with a larger process heap and unchanged identity isolation, source scope and thresholds." + "2026-10-14T15:04:26.288Z",codex-agent,"Final acceptance: complete npm run release:check and bun run release:check each passed (exit 0), 587/587 tests with zero failures/cancellations/skips/todo. Both used env -u PM_PATH -u PM_AUTHOR, fresh HOME, GIT_CONFIG_GLOBAL=/dev/null, GIT_CONFIG_NOSYSTEM=1, plus NODE_OPTIONS=--max-old-space-size=12288 after the documented default-heap aggregation failure. All 11 release checks passed at unchanged thresholds: typecheck, build, docstrings, lint, duplication, privacy, coverage, production audit, pack, changelog and attestation. Exact coverage: 12 authored modules, 9584/9584 lines and statements, 234/234 functions, 2557/2557 branches; no skipped source. Both stored linked commands passed via pm test pm-github-wrmt --run --progress with the pinned CLI on PATH: 5/5 and 4/4 test executions, zero failures/cancellations/skips/todo. Both isolated behavioural reverts failed as expected (0/1 each): removing scope checks pushed a sync branch; restoring unrestricted commit published extra.txt. Existing history is preserved; default-heap/CI resource acceptance remains open in pm-github-vhi6. No thread replies, bot commands, release or merge." + "2026-10-14T18:25:45.155Z",codex-agent,"Round 3: a no-op gated import must discard regenerated CHANGELOG.md drift, emit a notice that the caller changelog:check owns it, close an owned open sync PR, and exit successfully. Add real-git pipeline regressions for stale changelog no-op and tracker-plus-changelog publication, then prove the old behavior fails and run isolated npm and Bun release gates." + "2026-10-14T18:29:30.272Z",codex-agent,"Implemented tracker-only no-op detection before publication. Regenerated changelog drift is restored from HEAD (or removed when absent from HEAD), a notice delegates drift to caller changelog:check, and the existing owned-PR closure exits successfully. Focused direct and pinned pm linked runs each passed 6/6 tests, zero failures/cancellations/skips/todo, including stale and newly generated changelogs, clean checkout, no push/dispatch, own/fork PR handling, and tracker-plus-changelog publication. Behavioural revert: restored only the workflow to its previous implementation, retaining the new tests; the stale-changelog regression failed 0/1, exit 1, with the original no-item-link error. Fixed workflow restored. Run complete npm and Bun release gates with identity isolation and unchanged thresholds." + "2026-10-14T18:53:20.471Z",codex-agent,"Final round-3 acceptance: env -u PM_PATH -u PM_AUTHOR HOME=$(mktemp -d) GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 npm run release:check and the same isolated prefix with bun run release:check each passed exit 0: all 11 checks, 590/590 tests, zero failures/cancellations/skips/todo. Exact coverage across 12 authored modules: 9592/9592 lines and statements, 234/234 functions, 2557/2557 branches, no skipped source. Production audit: zero vulnerabilities; source duplication: zero. No added NODE_OPTIONS override. Focused direct test and pm test pm-github-wrmt --run --only-last --progress each passed 6/6; the linked command is stored on this item. Reverting only the workflow reproduced the stale-changelog failure (0/1, exit 1, original no-item-link error); fix restored. Changelog drift is restored or removed on no-op with a notice; owned stale PR closure is preserved; tracker-plus-changelog publication remains passing. README documents ownership by caller changelog:check. No out-of-scope defect or PM CLI bug found. Leave item in progress and release claim for orchestrator verification; no review replies, bot commands, release or merge." + "2026-10-14T18:53:48.691Z",codex-agent,"Final post-write changelog regeneration and changelog:check passed; output was unchanged. Strict pm health with required merge drivers passed exit 0, with the existing stale-in-progress advisory only. History changes are append-only. Claim is released and item remains in_progress for orchestrator verification. Commit the explicit workflow, regression test, README and paired tracker item/history paths as SteveBot, then audit the committed Git objects and push the existing PR branch." +files[2]{path,scope}: + .github/workflows/pm-github-sync.yml,project + test/import-gate.test.ts,project +tests[3]{command,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: + "node --test --test-name-pattern=\"the executable reusable workflow pushes only a clean|post-import changelog findings|post-import script can|dispatch handles|workflow input shell diagnoses\" test/import-gate.test.ts",project,900,codex-agent,"2026-10-14T12:21:43.966Z",local_mutation,fix/sync-pr-ci-and-changelog + "node --test --test-name-pattern=\"caller post-import changes|publication rechecks|restricted commit|pushes only a clean\" test/import-gate.test.ts",project,1800,codex-agent,"2026-10-14T13:58:56.773Z",local_mutation,fix/sync-pr-ci-and-changelog + "node --test --test-name-pattern=\"no-op import discards|pushes only a clean import|no-change run|install-only settings\" test/import-gate.test.ts",project,600,codex-agent,"2026-10-14T18:27:01.453Z",local_mutation,fix/sync-pr-ci-and-changelog +docs[3]{path,scope}: + CHANGELOG.md,project + docs/sync-workflow-caller.yml,project + README.md,project +body: "" diff --git a/.agents/pm/history/pm-github-vhi6.jsonl b/.agents/pm/history/pm-github-vhi6.jsonl new file mode 100644 index 00000000..96e3d3fc --- /dev/null +++ b/.agents/pm/history/pm-github-vhi6.jsonl @@ -0,0 +1,4 @@ +{"hash_algorithm":"sha256","ts":"2026-10-14T14:32:34.672Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","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-vhi6"},{"op":"add","path":"/metadata/title","value":"Bound coverage aggregation memory and compare default-heap CI behavior"},{"op":"add","path":"/metadata/description","value":"During the PR #122 privacy-scope regression work, an identity-isolated npm release:check completed 587/587 tests with zero failures, skips or cancellations, then c8 report aggregation exhausted Node's default 4 GiB heap. Native coverage receipts occupied approximately 2.9 GiB. The security fix is being validated with NODE_OPTIONS=--max-old-space-size=12288 and unchanged source inventory and four 100-percent thresholds. Compare default-heap CI behavior, investigate bounded report aggregation or documented resource requirements, and retain complete authored-source and real CLI coverage. Earlier local heap-exhaustion observations also appear in pm-github-fmrf; this task tracks current default-heap acceptance separately from the scope fix in pm-github-wrmt."},{"op":"add","path":"/metadata/type","value":"Task"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["coverage","follow-up","gates"]},{"op":"add","path":"/metadata/created_at","value":"2026-10-14T14:32:34.672Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-14T14:32:34.672Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"e042ad93a45aecedbee4768a205aac55b311a4ae649f3646f25e36ed532004dc","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":"477442770fe93358fed9b49a7b65e59005b880561537fd30cafe89d1604c13be"} +{"hash_algorithm":"sha256","ts":"2026-10-14T14:33:37.749Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T14:33:37.749Z"},{"op":"add","path":"/metadata/files","value":[{"path":"scripts/coverage-gate.ts","scope":"project"}]}],"before_hash":"e042ad93a45aecedbee4768a205aac55b311a4ae649f3646f25e36ed532004dc","after_hash":"0c9c2124cb804d21b63f5e79d99eeb71f1eeab2a1a7da0b05ec57167d47e5c3f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"881d648f5c871728b5422265648b2d77ef2f1c215b358b2cc529010d7468030a"} +{"hash_algorithm":"sha256","ts":"2026-10-14T14:33:39.357Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","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/1","value":{"path":"test/import-gate.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T14:33:39.357Z"}],"before_hash":"0c9c2124cb804d21b63f5e79d99eeb71f1eeab2a1a7da0b05ec57167d47e5c3f","after_hash":"077d0a3603074ad6b710b3b804c9800ae9c7a05b8a5bb66d514af161e78875e3","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"10879d5be967a132fe42cf02c04d0e0c923ec4da99b73fafaf02058c66705f64"} +{"hash_algorithm":"sha256","ts":"2026-10-14T15:04:27.090Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T15:04:27.090Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-14T15:04:27.090Z","author":"codex-agent","text":"Confirmed resource workaround: complete identity-isolated npm and Bun release:check each passed 587/587 tests and all 11 checks with NODE_OPTIONS=--max-old-space-size=12288. Coverage remains exact 100/100/100/100 across 12 authored modules, without source exemptions or threshold changes. The earlier default-heap npm run passed all 587 tests before c8 aggregation exhausted the heap. Default-heap CI comparison and bounded aggregation remain follow-up work; the security change does not alter coverage implementation or CI memory settings."}]}],"before_hash":"077d0a3603074ad6b710b3b804c9800ae9c7a05b8a5bb66d514af161e78875e3","after_hash":"b864dfba4ffb8a0939ef768641ce1055d227ead20d466da3906c395dcaf30ab1","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"26f184be7cf9759994ce653ea8f2f3829cd3708a619a83c28813adad7b792303"} diff --git a/.agents/pm/history/pm-github-wrmt.jsonl b/.agents/pm/history/pm-github-wrmt.jsonl new file mode 100644 index 00000000..7f4b6d63 --- /dev/null +++ b/.agents/pm/history/pm-github-wrmt.jsonl @@ -0,0 +1,42 @@ +{"hash_algorithm":"sha256","ts":"2026-10-14T11:43:07.998Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","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-wrmt"},{"op":"add","path":"/metadata/title","value":"Regenerate caller changelogs and dispatch required CI for gated sync PRs"},{"op":"add","path":"/metadata/description","value":"Make gated sync reviews independently mergeable with an overridable post-import command, a final privacy scan including CHANGELOG.md, and explicit caller CI workflow dispatch after PR creation or update. Fleet adoption remains tracked by pm-github-aiok."},{"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":[]},{"op":"add","path":"/metadata/created_at","value":"2026-10-14T11:43:07.998Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-10-14T11:43:07.998Z"},{"op":"add","path":"/metadata/author","value":"codex-agent"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"ab25ce757e758c58fe52025932aac7d84b0204e2f8e4d1819c918eb65991b28a","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":"d5fff41794e858de1d9de84c932dc9ce9ab9ed04aec93ca91f9e8a91bd515489"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:43:20.255Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:43:20.255Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"ab25ce757e758c58fe52025932aac7d84b0204e2f8e4d1819c918eb65991b28a","after_hash":"597ce88d1f90d6dff96a27d45750a7907a5ba7359243ca5530e6adc17b99df44","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"e858c7bb276082334cd80bbd993de3a1a3b72eb641629bfccde9ea72df6e69cc"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:43:23.237Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:43:23.237Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"597ce88d1f90d6dff96a27d45750a7907a5ba7359243ca5530e6adc17b99df44","after_hash":"f60b4e995b671b559e207cc52ea13ad8dfa24e5029f792633f4a412b4b2975fc","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"dfad8bdeb0c8aeb786654632114f76e439de5965c403d7251c51751abab98295"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:43:26.402Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:43:26.402Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-14T11:43:26.402Z","author":"codex-agent","text":"Measured fleet use: pm-gantt-chart#142 was blocked because bot PR workflows ended action_required and imported closed issues changed the generated changelog. Plan: run caller post-import command, stage CHANGELOG.md with tracker data, scan an explicit combined diff before health and publication, then dispatch configured caller workflows on the pushed branch. Test with real local git repositories and HTTP fixtures; record behavioural revert proofs. No duplicate feature found; fleet rollout is separate in pm-github-aiok."}]}],"before_hash":"f60b4e995b671b559e207cc52ea13ad8dfa24e5029f792633f4a412b4b2975fc","after_hash":"170e5a62a4b0419e75e3213392fe1613a1d11ec0e10df69d8bf46ad08d738084","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"13a89ff82ff5219f1aa2b658f624d997426fe18346777430880f1dfcd35e4d23"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:45:25.161Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:45:25.161Z"},{"op":"add","path":"/metadata/tags/0","value":"changelog-unreleased"}],"before_hash":"170e5a62a4b0419e75e3213392fe1613a1d11ec0e10df69d8bf46ad08d738084","after_hash":"4d5b8137186c7fa2d1247a20de68ec6c64038b0f63094019e78cf02c879975fc","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"7184a80aac2dc37353ed35a567f4a655be38191ecd22564069e166566a858d13"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:45:33.123Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:45:33.123Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/import-gate.test.ts","scope":"project","provenance":{"author":"codex-agent","created_at":"2026-10-14T11:45:33.007Z","source_kind":"local_mutation","source_ref":"fix/sync-pr-ci-and-changelog"}}]}],"before_hash":"4d5b8137186c7fa2d1247a20de68ec6c64038b0f63094019e78cf02c879975fc","after_hash":"372e33bfebb7a8cbafe618ebcad5e96121268c52c6df35ace5aef27d77221065","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"a326a10ae81b7360a095112fe87391c925a1abcb0d28baefcc8051057edb9beb"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:46:08.423Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:46:08.423Z"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/pm-github-sync.yml","scope":"project"}]}],"before_hash":"372e33bfebb7a8cbafe618ebcad5e96121268c52c6df35ace5aef27d77221065","after_hash":"d9aec2eef2ab71f8d3d3d27f650a6847f75dd09f47c046afb25cfd1fff04d0d3","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"32049cf2d82388f4cff5080b704c862780508f2b650b6826a7c7f23d554a67dd"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:46:14.626Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","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/1","value":{"path":"test/import-gate.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:46:14.626Z"}],"before_hash":"d9aec2eef2ab71f8d3d3d27f650a6847f75dd09f47c046afb25cfd1fff04d0d3","after_hash":"3888f328bdff32a3021b0169e27adc4465babb7618902aa4bda525f7fa42854c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"62d65e2377f224ad2cfb586f61825c3dcb3eb97010a751bdd8fae1dcb9090e2d"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:46:23.150Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:46:23.150Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"README.md","scope":"project"}]}],"before_hash":"3888f328bdff32a3021b0169e27adc4465babb7618902aa4bda525f7fa42854c","after_hash":"ba6ba0a53f1bc7ac75cfd10f3dc65bba394e3076cf5a5488c2c96c40c8acc561","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"2c0f2d5f7df53516d0c4bda7663db1773162f5e0dd67928c29c60251b64d3314"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:46:30.902Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","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/path","value":"docs/sync-workflow-caller.yml"},{"op":"add","path":"/metadata/docs/1","value":{"path":"README.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:46:30.902Z"}],"before_hash":"ba6ba0a53f1bc7ac75cfd10f3dc65bba394e3076cf5a5488c2c96c40c8acc561","after_hash":"84afe33308ffe966c9c1f634e4fbbde1ff942ed9ab7d4e06b83efbdf8b80cb04","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"ba34ced909214dcfaf5949fb6c100feab92d71b831f3773dc2b664fce65fb7c0"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:48:29.601Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_remove","patch":[{"op":"remove","path":"/metadata/tests"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:48:29.601Z"}],"before_hash":"84afe33308ffe966c9c1f634e4fbbde1ff942ed9ab7d4e06b83efbdf8b80cb04","after_hash":"8a7ef279948a4ea528208bf9f56b225cad563ded6b6c41807b40b1212e216c5c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"cbbce85b1d2a6cfc134c30c5ffd985cfe4553a33d6e9dfa64aeef9e65c7e6570"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:48:35.020Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:48:35.020Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --test-name-pattern=\"the executable reusable workflow pushes only a clean|post-import changelog findings|post-import command can|dispatch handles\" test/import-gate.test.ts","scope":"project","timeout_seconds":900,"provenance":{"author":"codex-agent","created_at":"2026-10-14T11:48:34.865Z","source_kind":"local_mutation","source_ref":"fix/sync-pr-ci-and-changelog"}}]}],"before_hash":"8a7ef279948a4ea528208bf9f56b225cad563ded6b6c41807b40b1212e216c5c","after_hash":"07f50b24f66a48ad89e387c7ac7c9106ceb7031945d8d0aa21bcaddcb6f170d6","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"3dd2b30b4f23d3e3edeb3ba2796afddd7353985f36bfded71680d8e3c604c19e"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:48:40.689Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-14T11:48:40.689Z","author":"codex-agent","text":"Implemented post-import-run and ci-workflows with documented defaults, actions: write, combined tracker/changelog diff scanning, dispatch after successful PR publication, and approval-required run URL notices. Typecheck, test compilation, lint, and generated changelog check pass. Initial linked whole-file run exceeded the runner default 120-second budget; link the focused workflow regressions with an explicit 900-second command budget. Required release gates retain unchanged thresholds and internal import deadlines."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:48:40.689Z"}],"before_hash":"07f50b24f66a48ad89e387c7ac7c9106ceb7031945d8d0aa21bcaddcb6f170d6","after_hash":"80fb131b0a341e610c9451e0930ac64c8a3084e415d7bf47d8ba1d5865c0d31f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"2982737d0eef9b4cb9c5d39e5e497fae47356d7946058401d4c911eccdeb05ec"} +{"hash_algorithm":"sha256","ts":"2026-10-14T11:53:36.296Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-14T11:53:36.296Z","author":"codex-agent","text":"Focused workflow regressions: 4/4 pass, zero failures/cancellations/skips/todo. Behavioural revert proofs (workflow shell only, unchanged tests): removing the post-import execution fails the published changelog assertion (0/1 pass); excluding CHANGELOG.md from the explicit final diff fails because the secret-bearing local sync succeeds (0/1 pass); removing gh workflow run fails for missing dispatch receipts (0/1 pass). All changes restored. Fixtures use real git repositories, bare remotes, the packed candidate with the pinned host CLI, and local HTTP responses; no live GitHub test writes."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T11:53:36.296Z"}],"before_hash":"80fb131b0a341e610c9451e0930ac64c8a3084e415d7bf47d8ba1d5865c0d31f","after_hash":"9d1199ecb23e4684e7de3bd20e4f3d69390766574f826a92d5a40cb7b185d3aa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"5d37f9400a7d036bbf04265c5904b1a6971944ca3b03a20784377ae4106e625d"} +{"hash_algorithm":"sha256","ts":"2026-10-14T12:04:50.071Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-14T12:04:50.070Z","author":"codex-agent","text":"The complete standalone workflow/import-gate suite passed 34/34 with zero failures, cancellations, skips, or todo, including the native Bun repeat-import deadlines. The final linked focused run (pm test pm-github-wrmt --run --progress) passed 4/4 with an explicit 900-second command budget and no failures/cancellations/skips/todo. Strict health with merge-driver verification passed; one pre-existing advisory covers four stale in-progress items. Full isolated npm and Bun release gates remain required before delivery."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T12:04:50.071Z"}],"before_hash":"9d1199ecb23e4684e7de3bd20e4f3d69390766574f826a92d5a40cb7b185d3aa","after_hash":"a541231335fb3212e56d69bbb714a949fcc53c6d3e19f16baed62035fd9fc817","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"20b0765d798934829113b9397f2ce53870052079f32b2c935497b380387cc5e4"} +{"hash_algorithm":"sha256","ts":"2026-10-14T12:21:35.364Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-10-14T12:21:35.364Z","author":"codex-agent","text":"Initial isolated npm gate: 583/584 tests passed; the sole failure was the canonical publish-attestation auditor rejecting opaque Bash execution of a caller command. Keep that safety gate unchanged. Final input is post-import-script, default changelog:full, invoked through explicit npm run --if-present with a validated script name; callers override the npm script name or disable with an empty string. Updated fixtures use caller package scripts, including failing and secret-generating scripts. Typecheck, lint and attestation verification pass; repeat focused tests, revert proofs and both isolated release gates on this final design."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T12:21:35.364Z"}],"before_hash":"a541231335fb3212e56d69bbb714a949fcc53c6d3e19f16baed62035fd9fc817","after_hash":"2ee7aab04d3d59834cb209b97e639227ad13bf4b12f6aee89c86f88949d63404","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"623088b9fc5d695c350b72a670d202bd59efd745bbce074c337d4d50f025a8f9"} +{"hash_algorithm":"sha256","ts":"2026-10-14T12:21:39.478Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_remove","patch":[{"op":"remove","path":"/metadata/tests"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T12:21:39.478Z"}],"before_hash":"2ee7aab04d3d59834cb209b97e639227ad13bf4b12f6aee89c86f88949d63404","after_hash":"f8bf749854dae41d506bbd388373ddf09bc77c07a5f9ce2a2ff661c990436bf2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"428f0eb6e8b57bf2c699f85646ca1ecbe680fca025704b6d4092b079c3cef9a2"} +{"hash_algorithm":"sha256","ts":"2026-10-14T12:21:44.134Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T12:21:44.134Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --test-name-pattern=\"the executable reusable workflow pushes only a clean|post-import changelog findings|post-import script can|dispatch handles|workflow input shell diagnoses\" test/import-gate.test.ts","scope":"project","timeout_seconds":900,"provenance":{"author":"codex-agent","created_at":"2026-10-14T12:21:43.966Z","source_kind":"local_mutation","source_ref":"fix/sync-pr-ci-and-changelog"}}]}],"before_hash":"f8bf749854dae41d506bbd388373ddf09bc77c07a5f9ce2a2ff661c990436bf2","after_hash":"14bf7fd1e56b8e149557ad940374a1748ad94ab849457073c2098a9eaa218917","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"cf2fc334572419fab2811c58d1fa16cc0bbeea2e94d6e489c64887744bbaafe5"} +{"hash_algorithm":"sha256","ts":"2026-10-14T12:25:13.303Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T12:25:13.303Z"},{"op":"replace","path":"/metadata/description","value":"Make gated sync reviews independently mergeable with an overridable caller npm script, a final privacy scan including CHANGELOG.md, and explicit caller CI workflow dispatch after PR creation or update. Fleet adoption remains tracked by pm-github-aiok."}],"before_hash":"14bf7fd1e56b8e149557ad940374a1748ad94ab849457073c2098a9eaa218917","after_hash":"73a98e822904f596affdcc15376a7700eb8df52f8b48b5c8a64517462da44f96","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"84ee45af50b41680b15f47c1e767116cc35be1135c63a368318b65a9508448b4"} +{"hash_algorithm":"sha256","ts":"2026-10-14T12:27:24.827Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-10-14T12:27:24.824Z","author":"codex-agent","text":"Final named-script design: linked pm test run passed 5/5, with zero failures/cancellations/skips/todo. Four behavioural reverts with unchanged tests each failed (0/1, exit 1): remove npm script execution -> stale published changelog; exclude CHANGELOG.md from final diff -> synthetic secret reaches local push; remove gh workflow run -> missing dispatch receipts; remove script-name validation -> raw-command input succeeds. Restored all changes. Canonical attestation check passes without exceptions or threshold changes. Repeating both full release gates under isolated HOME, unset PM_PATH/PM_AUTHOR, and disabled global/system git config."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T12:27:24.827Z"}],"before_hash":"73a98e822904f596affdcc15376a7700eb8df52f8b48b5c8a64517462da44f96","after_hash":"5f53755947b9c7505ae10ea658519d98fe42abc50dfcf2e5bb2531074326fe43","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"00286bc5c7dd6f189d7a49c9c99adb34945a7d1a06d28dba02c0bc995efbbd98"} +{"hash_algorithm":"sha256","ts":"2026-10-14T12:47:18.176Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-10-14T12:47:18.176Z","author":"codex-agent","text":"Final isolated npm release:check passed (exit 0): 584/584 tests, zero failures/cancellations/skips/todo; full coverage gate passed at unchanged four 100-percent thresholds; source duplication zero; privacy, docstrings, typecheck, build, lint, production audit, pack, changelog check and canonical publish attestation all passed. Next gate is isolated bun run release:check on unchanged implementation."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T12:47:18.176Z"}],"before_hash":"5f53755947b9c7505ae10ea658519d98fe42abc50dfcf2e5bb2531074326fe43","after_hash":"bdecd6743521d55a2054874d26cb5fc4b532366ed9e5163a95368d9f12116192","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"745026f46033ee1d91bc3a89260b6582634468cc8e5fea0155c71fc7c82173cb"} +{"hash_algorithm":"sha256","ts":"2026-10-14T13:10:16.298Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/7","value":{"created_at":"2026-10-14T13:10:16.298Z","author":"codex-agent","text":"Final isolated bun run release:check passed (exit 0): 584/584 tests, zero failures/cancellations/skips/todo. Both npm and Bun measured 12 authored modules with 9584/9584 lines and statements, 234/234 functions, and 2557/2557 branches: exact 100/100/100/100, no skipped source. Both release gates passed typecheck, build, docstrings, lint, zero duplication, privacy, production audit (zero vulnerabilities), pack, changelog check and canonical attestation at unchanged thresholds. Final linked workflow regressions passed 5/5; all four behavioural reverts failed as expected. Delivery is the fix/sync-pr-ci-and-changelog review branch; callers must repin the workflow and package release, grant actions: write and expose matching required checks via workflow_dispatch. Release the claim without closing this Feature; the orchestrator verifies and closes it."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T13:10:16.298Z"}],"before_hash":"bdecd6743521d55a2054874d26cb5fc4b532366ed9e5163a95368d9f12116192","after_hash":"ae9a0ff442b6de477b15439c1d7b0d240c8a4b88db4464e22a9fa68a98e80c00","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"dd3eee1cb648d45cc7ee06fc24e5632d2bb84b280979aca84ea44d09c99349db"} +{"hash_algorithm":"sha256","ts":"2026-10-14T13:10:17.220Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/docs/1/path","value":"docs/sync-workflow-caller.yml"},{"op":"replace","path":"/metadata/docs/0/path","value":"CHANGELOG.md"},{"op":"add","path":"/metadata/docs/2","value":{"path":"README.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T13:10:17.220Z"}],"before_hash":"ae9a0ff442b6de477b15439c1d7b0d240c8a4b88db4464e22a9fa68a98e80c00","after_hash":"fb28e2d30bc3c459d016cbdd5609c068a64260777ceb69443a91f9facd306af4","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"cc635175b62e278d5011483cf4ae2fe13f0ae5a5b287d9d1b4e0e7da7ceb91dd"} +{"hash_algorithm":"sha256","ts":"2026-10-14T13:10:18.217Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"445ce290ac84082aec925c7f","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T13:10:18.217Z"}],"before_hash":"fb28e2d30bc3c459d016cbdd5609c068a64260777ceb69443a91f9facd306af4","after_hash":"aa445808ead330e5e2f06f2cfcb325e0aee76cb34ab8488a3fc9184b4d24aafa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"db130e8630b83cbf4fb1ffc38bc5692bae187053ac336c8275b45efdba2c056e"} +{"hash_algorithm":"sha256","ts":"2026-10-14T13:47:14.810Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T13:47:14.810Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"aa445808ead330e5e2f06f2cfcb325e0aee76cb34ab8488a3fc9184b4d24aafa","after_hash":"f657a36e83e38d925cce4691af3ed4f8b4dcf0ce224caa50c3c811cc89ce4bc0","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"04a4af0c3468aecda7ec58b8e8bf8d29345435af5be8c4ebaad9b3aa668498ba"} +{"hash_algorithm":"sha256","ts":"2026-10-14T13:47:16.164Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/8","value":{"created_at":"2026-10-14T13:47:16.164Z","author":"codex-agent","text":"CodeRabbit round 1: confirmed fail-open publication when caller post-import scripts change paths outside the final privacy scan. Plan: explicit tracker and CHANGELOG.md scope, complete NUL-delimited status checks after post-import and immediately before commit, exact commit diff rescanning, restricted commit pathspec, and real Git adversarial tests with a behavioural revert proof."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T13:47:16.164Z"}],"before_hash":"f657a36e83e38d925cce4691af3ed4f8b4dcf0ce224caa50c3c811cc89ce4bc0","after_hash":"c6b325c5e631a59e14e83c2a2f6cc7decd1fe59dae2ed613a75fd04145c95b2c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"4cc90b0afb50d2dc99d28d41aa7f4ba297591c750e6a10e300566364ee15ebb6"} +{"hash_algorithm":"sha256","ts":"2026-10-14T13:58:52.217Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/9","value":{"created_at":"2026-10-14T13:58:52.217Z","author":"codex-agent","text":"Implemented SDK-resolved scope, complete NUL-delimited status enumeration including rename sources, exact file-list staging and privacy rescanning, and commit --only with literal NUL pathspecs. The allowlist and setup-file restoration policy are documented in README.md. Added six caller-scope cases, five late-change cases, and an independent commit-pathspec bypass control using real Git repositories and local bare remotes. Caller fixture dependency and runtime ignores now match normal package checkouts."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T13:58:52.217Z"}],"before_hash":"c6b325c5e631a59e14e83c2a2f6cc7decd1fe59dae2ed613a75fd04145c95b2c","after_hash":"9fc50c3e10919bad66d7abc2c75e0980ccededfae91bdb340903baf29805443f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"54feeee127a6563577d6dea650dbd1fbe622b30fb895be20b25b57c766cf10de"} +{"hash_algorithm":"sha256","ts":"2026-10-14T13:58:56.969Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"node --test --test-name-pattern=\"caller post-import changes|publication rechecks|restricted commit|pushes only a clean\" test/import-gate.test.ts","scope":"project","timeout_seconds":1800,"provenance":{"author":"codex-agent","created_at":"2026-10-14T13:58:56.773Z","source_kind":"local_mutation","source_ref":"fix/sync-pr-ci-and-changelog"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T13:58:56.969Z"}],"before_hash":"9fc50c3e10919bad66d7abc2c75e0980ccededfae91bdb340903baf29805443f","after_hash":"bcf72ca082e98a6adce4855e2fb069b901bd7d13a83b6a6f4e1c25a79a291060","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"2381482b87ed19f2e97e7fa60f0c6850bc7347e027d7b9d0f52c102ad8e382d6"} +{"hash_algorithm":"sha256","ts":"2026-10-14T14:04:37.784Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/10","value":{"created_at":"2026-10-14T14:04:37.784Z","author":"codex-agent","text":"Positive workflow controls passed 3/3: clean tracker plus changelog publication and repeat update; install-only settings never committed; restricted commit excludes unrelated staged content when scope checks are bypassed. Scope-only behavioural revert in an isolated checkout failed the unchanged regression (0/1, exit 1): the staged-extra caller returned success and pushed a sync branch. The explicit commit pathspec independently prevented the extra file from being included. Running a second revert to reproduce the original unrestricted-commit leak; full isolated release gates and linked regressions remain in progress."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T14:04:37.784Z"}],"before_hash":"bcf72ca082e98a6adce4855e2fb069b901bd7d13a83b6a6f4e1c25a79a291060","after_hash":"8ffdae021392eb13c3fb36e04b268afa033731a5b139a366a3db0e940d6663de","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"52ee554692d131eb573e7c2ee85bcf201772b2f8f4c78565531f74acb26f38b6"} +{"hash_algorithm":"sha256","ts":"2026-10-14T14:07:36.027Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/11","value":{"created_at":"2026-10-14T14:07:36.027Z","author":"codex-agent","text":"Second behavioural revert confirmed the original leak: keeping the unchanged bypass-control test and restoring the unrestricted git commit failed (0/1, exit 1) because git cat-file found extra.txt on the bare remote sync branch (status 0 instead of expected 128). Scope-only revert also pushed a sync branch and failed its unchanged no-publication regression. Both safeguards and tests remain restored in the package worktree; only an isolated proof checkout was mutated."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T14:07:36.027Z"}],"before_hash":"8ffdae021392eb13c3fb36e04b268afa033731a5b139a366a3db0e940d6663de","after_hash":"2807577735364e106d7577a579f3e3a552b79b9a499fbe857d121a6874f5fca2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"36ab78f453027f0f1687411d0a5174fcce8e3a7faf8bcd1341d5f08679577646"} +{"hash_algorithm":"sha256","ts":"2026-10-14T14:19:52.798Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/12","value":{"created_at":"2026-10-14T14:19:52.798Z","author":"codex-agent","text":"Final linked regression run passed 4/4 with zero failures, cancellations, skips or todo: six caller out-of-scope cases each failed closed with no remote change, five late-change cases each blocked publication, clean tracker plus changelog publication and repeat update passed, and restricted commit excluded unrelated staged data when checks were bypassed. Exact command: node --test --test-name-pattern=\"caller post-import changes|publication rechecks|restricted commit|pushes only a clean\" test/import-gate.test.ts. Ran via pm test pm-github-wrmt --run --only-last --progress with the linked 1800-second budget. Automatic test-result recording is disabled by repository policy; this comment preserves the verified result without changing that policy."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T14:19:52.798Z"}],"before_hash":"2807577735364e106d7577a579f3e3a552b79b9a499fbe857d121a6874f5fca2","after_hash":"6e4e136678273cf3d0000e18c6d703c4f1c5524955fd315a87586931696ba9e2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"3462d295b9668a317b01d29e39ca5c96e7f3f735087c946c821949e74372c43c"} +{"hash_algorithm":"sha256","ts":"2026-10-14T14:30:05.375Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/13","value":{"created_at":"2026-10-14T14:30:05.372Z","author":"codex-agent","text":"Initial isolated npm release:check completed all 587/587 tests with zero failures/cancellations/skips/todo, then failed during c8 coverage aggregation at Node's default 4 GiB heap. No release gate is claimed passed from that attempt. Re-run both complete release commands with NODE_OPTIONS=--max-old-space-size=12288, preserving identity isolation, source inventory and all four 100-percent coverage thresholds. Linked regressions and both behavioural revert proofs already passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T14:30:05.375Z"}],"before_hash":"6e4e136678273cf3d0000e18c6d703c4f1c5524955fd315a87586931696ba9e2","after_hash":"304ab8af3d063f627a3d96622f1d5d7fc43bd1c57f301d4413af098688835c2f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"ce2f74890b64c5be0dfb3c7b94dc408beb6d5a50a9b5db85b616d17b2ff4d661"} +{"hash_algorithm":"sha256","ts":"2026-10-14T14:33:41.625Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/14","value":{"created_at":"2026-10-14T14:33:41.625Z","author":"codex-agent","text":"Default-heap coverage aggregation follow-up is pm-github-vhi6 (open, no implementation changes in this security fix). The initial full test suite passed 587/587 before c8 ran out of heap; complete npm and Bun release gates are being rerun with a larger process heap and unchanged identity isolation, source scope and thresholds."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T14:33:41.625Z"}],"before_hash":"304ab8af3d063f627a3d96622f1d5d7fc43bd1c57f301d4413af098688835c2f","after_hash":"83c0853ece14dca95cddd4c32931737bf9426e72b01d1667797122fc72d582ac","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"5329a3768f0aac68acd831eb80a9660294baf16edf1521d3a60200a7f273de87"} +{"hash_algorithm":"sha256","ts":"2026-10-14T15:04:26.288Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/15","value":{"created_at":"2026-10-14T15:04:26.288Z","author":"codex-agent","text":"Final acceptance: complete npm run release:check and bun run release:check each passed (exit 0), 587/587 tests with zero failures/cancellations/skips/todo. Both used env -u PM_PATH -u PM_AUTHOR, fresh HOME, GIT_CONFIG_GLOBAL=/dev/null, GIT_CONFIG_NOSYSTEM=1, plus NODE_OPTIONS=--max-old-space-size=12288 after the documented default-heap aggregation failure. All 11 release checks passed at unchanged thresholds: typecheck, build, docstrings, lint, duplication, privacy, coverage, production audit, pack, changelog and attestation. Exact coverage: 12 authored modules, 9584/9584 lines and statements, 234/234 functions, 2557/2557 branches; no skipped source. Both stored linked commands passed via pm test pm-github-wrmt --run --progress with the pinned CLI on PATH: 5/5 and 4/4 test executions, zero failures/cancellations/skips/todo. Both isolated behavioural reverts failed as expected (0/1 each): removing scope checks pushed a sync branch; restoring unrestricted commit published extra.txt. Existing history is preserved; default-heap/CI resource acceptance remains open in pm-github-vhi6. No thread replies, bot commands, release or merge."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T15:04:26.288Z"}],"before_hash":"83c0853ece14dca95cddd4c32931737bf9426e72b01d1667797122fc72d582ac","after_hash":"7f58a5bd54238905917b925f8223b82baad0886747323d4548a1634507fa107a","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"8c669cb469a136e6f017ecb32644c115ccb4e4b82f15f07c21bf1faab6ff88f0"} +{"hash_algorithm":"sha256","ts":"2026-10-14T15:04:28.059Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"85cc36f7b561a65ec3db0d35","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T15:04:28.059Z"}],"before_hash":"7f58a5bd54238905917b925f8223b82baad0886747323d4548a1634507fa107a","after_hash":"11179e1c098afa1058216d2e14ab3a43d3470e9fb2656c0b5bde1c3427f70cbe","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"c662c36e110d9638b8d3e87ea19465b5596a218b1f06b6f7c810326ac7cf541f"} +{"hash_algorithm":"sha256","ts":"2026-10-14T18:25:43.589Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"69df8378557c28a1f174bcc3","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T18:25:43.589Z"},{"op":"add","path":"/metadata/assignee","value":"codex-agent"},{"op":"add","path":"/metadata/claim_principal","value":"codex-agent"}],"before_hash":"11179e1c098afa1058216d2e14ab3a43d3470e9fb2656c0b5bde1c3427f70cbe","after_hash":"dacf3719bfa2c0e231b381c966ba8dad3698ff2ac25caecfd3efaca328ce626d","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"98429cd08a999000a6b5d4e0c165245912e20548283de425b6a133bf4828a35f"} +{"hash_algorithm":"sha256","ts":"2026-10-14T18:25:45.155Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"69df8378557c28a1f174bcc3","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/16","value":{"created_at":"2026-10-14T18:25:45.155Z","author":"codex-agent","text":"Round 3: a no-op gated import must discard regenerated CHANGELOG.md drift, emit a notice that the caller changelog:check owns it, close an owned open sync PR, and exit successfully. Add real-git pipeline regressions for stale changelog no-op and tracker-plus-changelog publication, then prove the old behavior fails and run isolated npm and Bun release gates."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T18:25:45.155Z"}],"before_hash":"dacf3719bfa2c0e231b381c966ba8dad3698ff2ac25caecfd3efaca328ce626d","after_hash":"8e7c43d1ba362423d48c02cd981ff842899dd8b944a56e8b46ba6329931dfeda","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"a8f02eeafdf716592f811f1fe96374c1d142753d117b309306d5213cfd8c36c7"} +{"hash_algorithm":"sha256","ts":"2026-10-14T18:27:01.506Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"69df8378557c28a1f174bcc3","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":"node --test --test-name-pattern=\"no-op import discards|pushes only a clean import|no-change run|install-only settings\" test/import-gate.test.ts","scope":"project","timeout_seconds":600,"provenance":{"author":"codex-agent","created_at":"2026-10-14T18:27:01.453Z","source_kind":"local_mutation","source_ref":"fix/sync-pr-ci-and-changelog"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T18:27:01.506Z"}],"before_hash":"8e7c43d1ba362423d48c02cd981ff842899dd8b944a56e8b46ba6329931dfeda","after_hash":"7cd84b2e536faae3dbc3e752dbedaae721ee27fe19e5e69a98087f816faba69f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"5397563221add952315ad85ed4d097aa2f9a7234ba35b44864adbdd2009a9935"} +{"hash_algorithm":"sha256","ts":"2026-10-14T18:29:30.272Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"69df8378557c28a1f174bcc3","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/17","value":{"created_at":"2026-10-14T18:29:30.272Z","author":"codex-agent","text":"Implemented tracker-only no-op detection before publication. Regenerated changelog drift is restored from HEAD (or removed when absent from HEAD), a notice delegates drift to caller changelog:check, and the existing owned-PR closure exits successfully. Focused direct and pinned pm linked runs each passed 6/6 tests, zero failures/cancellations/skips/todo, including stale and newly generated changelogs, clean checkout, no push/dispatch, own/fork PR handling, and tracker-plus-changelog publication. Behavioural revert: restored only the workflow to its previous implementation, retaining the new tests; the stale-changelog regression failed 0/1, exit 1, with the original no-item-link error. Fixed workflow restored. Run complete npm and Bun release gates with identity isolation and unchanged thresholds."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T18:29:30.272Z"}],"before_hash":"7cd84b2e536faae3dbc3e752dbedaae721ee27fe19e5e69a98087f816faba69f","after_hash":"7ecef4e2045deff772cbcff21f93e096adbe04404b5a758522d5c8a353d157ad","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"acee36b213ef0d95bcfd112c7025a381b8bdcf338317005f50ae847ce31e77e5"} +{"hash_algorithm":"sha256","ts":"2026-10-14T18:53:20.471Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"69df8378557c28a1f174bcc3","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/18","value":{"created_at":"2026-10-14T18:53:20.471Z","author":"codex-agent","text":"Final round-3 acceptance: env -u PM_PATH -u PM_AUTHOR HOME=$(mktemp -d) GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 npm run release:check and the same isolated prefix with bun run release:check each passed exit 0: all 11 checks, 590/590 tests, zero failures/cancellations/skips/todo. Exact coverage across 12 authored modules: 9592/9592 lines and statements, 234/234 functions, 2557/2557 branches, no skipped source. Production audit: zero vulnerabilities; source duplication: zero. No added NODE_OPTIONS override. Focused direct test and pm test pm-github-wrmt --run --only-last --progress each passed 6/6; the linked command is stored on this item. Reverting only the workflow reproduced the stale-changelog failure (0/1, exit 1, original no-item-link error); fix restored. Changelog drift is restored or removed on no-op with a notice; owned stale PR closure is preserved; tracker-plus-changelog publication remains passing. README documents ownership by caller changelog:check. No out-of-scope defect or PM CLI bug found. Leave item in progress and release claim for orchestrator verification; no review replies, bot commands, release or merge."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T18:53:20.471Z"}],"before_hash":"7ecef4e2045deff772cbcff21f93e096adbe04404b5a758522d5c8a353d157ad","after_hash":"1e06da153c8379631624b8d8938489265ad48ff210bccbc2f43396e6abbcc24c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"cfafa9cf076ce2c9ba45d3b3d482dc2a3706b72361c0a9cb4e7705c36cdd7e48"} +{"hash_algorithm":"sha256","ts":"2026-10-14T18:53:21.201Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"69df8378557c28a1f174bcc3","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T18:53:21.201Z"}],"before_hash":"1e06da153c8379631624b8d8938489265ad48ff210bccbc2f43396e6abbcc24c","after_hash":"77b502db66dd8d4b0cabde243f55230bf0aac6be7cc6df767eb6e33731e767a0","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"9753d49ebe150fda4f86c8fce5f21a2f28307f50f5add57a14107e4247e8d2b0"} +{"hash_algorithm":"sha256","ts":"2026-10-14T18:53:48.691Z","author":"codex-agent","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"69df8378557c28a1f174bcc3","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/19","value":{"created_at":"2026-10-14T18:53:48.691Z","author":"codex-agent","text":"Final post-write changelog regeneration and changelog:check passed; output was unchanged. Strict pm health with required merge drivers passed exit 0, with the existing stale-in-progress advisory only. History changes are append-only. Claim is released and item remains in_progress for orchestrator verification. Commit the explicit workflow, regression test, README and paired tracker item/history paths as SteveBot, then audit the committed Git objects and push the existing PR branch."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-14T18:53:48.691Z"}],"before_hash":"77b502db66dd8d4b0cabde243f55230bf0aac6be7cc6df767eb6e33731e767a0","after_hash":"33c3cb22ecb4ffa3f0f6ab11ae1051f14589fcde1f1a6f4589dd650a4ce3f4a2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"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":"d27cc1eddf91addbaa1282d780f6b500c538c107dfd623ae7630789b62c35519"} diff --git a/.agents/pm/tasks/pm-github-vhi6.toon b/.agents/pm/tasks/pm-github-vhi6.toon new file mode 100644 index 00000000..67a32d35 --- /dev/null +++ b/.agents/pm/tasks/pm-github-vhi6.toon @@ -0,0 +1,16 @@ +id: pm-github-vhi6 +title: Bound coverage aggregation memory and compare default-heap CI behavior +description: "During the PR #122 privacy-scope regression work, an identity-isolated npm release:check completed 587/587 tests with zero failures, skips or cancellations, then c8 report aggregation exhausted Node's default 4 GiB heap. Native coverage receipts occupied approximately 2.9 GiB. The security fix is being validated with NODE_OPTIONS=--max-old-space-size=12288 and unchanged source inventory and four 100-percent thresholds. Compare default-heap CI behavior, investigate bounded report aggregation or documented resource requirements, and retain complete authored-source and real CLI coverage. Earlier local heap-exhaustion observations also appear in pm-github-fmrf; this task tracks current default-heap acceptance separately from the scope fix in pm-github-wrmt." +type: Task +status: open +priority: 2 +tags[3]: coverage,follow-up,gates +created_at: "2026-10-14T14:32:34.672Z" +updated_at: "2026-10-14T15:04:27.090Z" +author: codex-agent +comments[1]{created_at,author,text}: + "2026-10-14T15:04:27.090Z",codex-agent,"Confirmed resource workaround: complete identity-isolated npm and Bun release:check each passed 587/587 tests and all 11 checks with NODE_OPTIONS=--max-old-space-size=12288. Coverage remains exact 100/100/100/100 across 12 authored modules, without source exemptions or threshold changes. The earlier default-heap npm run passed all 587 tests before c8 aggregation exhausted the heap. Default-heap CI comparison and bounded aggregation remain follow-up work; the security change does not alter coverage implementation or CI memory settings." +files[2]{path,scope}: + scripts/coverage-gate.ts,project + test/import-gate.test.ts,project +body: "" diff --git a/.github/workflows/pm-github-sync.yml b/.github/workflows/pm-github-sync.yml index 7e1dbaa6..ce2283d1 100644 --- a/.github/workflows/pm-github-sync.yml +++ b/.github/workflows/pm-github-sync.yml @@ -7,7 +7,8 @@ # Pipeline order is the safety property: validate → dry-run plan → gated import # (plan completeness + provenance idempotency verified BEFORE the write, then # the fail-closed privacy gate scans the proposed tracker change AFTER it) → -# strict pm health → and only then commit + push + PR. `--gate` exits non-zero +# caller post-import script → full status scope check + exact commit diff gate → strict pm health +# → commit + push + PR → caller CI dispatch. `--gate` exits non-zero # on any credential, personal-data, or host-path finding — or on any unreadable # scan input — so this job can never reach the push step with a leak in the # change. Actions are pinned by SHA. @@ -31,6 +32,16 @@ on: type: string required: false default: "automation/pm-github-sync" + post-import-script: + description: "Caller npm script after import, before the final gate; missing skips, empty disables it" + type: string + required: false + default: "changelog:full" + ci-workflows: + description: "Whitespace-separated caller workflow filenames with workflow_dispatch; empty disables dispatch" + type: string + required: false + default: "ci.yml" node-version: description: "Node.js version for the pm CLI" type: string @@ -51,11 +62,14 @@ jobs: contents: write pull-requests: write issues: read + actions: write runs-on: ubuntu-latest env: REPOSITORY: ${{ inputs.repository }} SYNC_BRANCH: ${{ inputs.sync-branch }} PM_GITHUB_VERSION: ${{ inputs.pm-github-version }} + POST_IMPORT_SCRIPT: ${{ inputs.post-import-script }} + CI_WORKFLOWS: ${{ inputs.ci-workflows }} CALLING_REPOSITORY: ${{ github.repository }} steps: - name: Validate sync inputs @@ -66,6 +80,7 @@ jobs: [[ "${PM_GITHUB_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]] || { echo "::error::pm-github-version must be an exact numeric version: N.N.N or N.N.N-N."; exit 1; } [[ "${SYNC_BRANCH}" == automation/* ]] || { echo "::error::sync-branch must start with automation/."; exit 1; } git check-ref-format "refs/heads/${SYNC_BRANCH}" + [[ -z "${POST_IMPORT_SCRIPT:-}" || "${POST_IMPORT_SCRIPT:-}" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.:-]*$ ]] || { echo "::error::post-import-script must be an npm script name, not a shell command."; exit 1; } - name: Checkout main uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -102,12 +117,80 @@ jobs: npm ci --ignore-scripts printf '%s/node_modules/.bin\n' "${GITHUB_WORKSPACE}" >> "${GITHUB_PATH}" + # The explicit publication allowlist is the SDK-resolved tracker (except + # extensions, locks, checkpoints and extension setup files) and CHANGELOG.md. Nothing else may + # change, even if unstaged or untracked. Keep all boundaries on this list. + - name: Resolve sync commit scope + shell: bash + run: | + set -euo pipefail + PM_SYNC_TRACKER="$(node --input-type=module -e 'import { resolvePmRoot } from "@unbrained/pm-cli/sdk"; import { relative } from "node:path"; const p = relative(process.cwd(), resolvePmRoot(process.cwd())); if (!p || p === ".." || p.startsWith("../") || /[\r\n]/.test(p)) throw new Error("Sync tracker must be inside the repository"); process.stdout.write(p);')" + export PM_SYNC_TRACKER + printf 'PM_SYNC_TRACKER=%s\n' "${PM_SYNC_TRACKER}" >> "${GITHUB_ENV}" + cat > "${RUNNER_TEMP}/pm-github-sync-scope.sh" <<'SCOPE' + sync_scope=(":(top,literal)${PM_SYNC_TRACKER}" ':(top,literal)CHANGELOG.md' + ":(top,exclude,literal)${PM_SYNC_TRACKER}/extensions" + ":(top,exclude,literal)${PM_SYNC_TRACKER}/locks" + ":(top,exclude,literal)${PM_SYNC_TRACKER}/checkpoints" + ":(top,exclude,literal)${PM_SYNC_TRACKER}/settings.json" + ":(top,exclude,literal)${PM_SYNC_TRACKER}/history/_workspace.jsonl") + restore_sync_setup() { + for setup_path in "${PM_SYNC_TRACKER}/settings.json" "${PM_SYNC_TRACKER}/history/_workspace.jsonl"; do + if git cat-file -e "HEAD:${setup_path}" 2>/dev/null; then + git --literal-pathspecs restore --source=HEAD --staged --worktree -- "${setup_path}" + else + git --literal-pathspecs rm -q --cached --ignore-unmatch -- "${setup_path}" + rm -f -- "${setup_path}" + fi + done + } + check_sync_scope() { + local entry path failed=0 + git status --porcelain=v1 -z --untracked-files=all > "${RUNNER_TEMP}/pm-github-sync-status" + while IFS= read -r -d '' entry; do + path="${entry:3}" + check_sync_path "${path}" || failed=1 + # Porcelain -z renames/copies contain a second, original path. + if [[ "${entry:0:2}" == *R* || "${entry:0:2}" == *C* ]]; then + IFS= read -r -d '' path || { echo '::error::Incomplete Git status record.'; return 1; } + check_sync_path "${path}" || failed=1 + fi + done < "${RUNNER_TEMP}/pm-github-sync-status" + return "${failed}" + } + check_sync_path() { + case "$1" in + "${PM_SYNC_TRACKER}"/extensions|"${PM_SYNC_TRACKER}"/extensions/*|"${PM_SYNC_TRACKER}"/locks|"${PM_SYNC_TRACKER}"/locks/*|"${PM_SYNC_TRACKER}"/checkpoints|"${PM_SYNC_TRACKER}"/checkpoints/*) ;; + "${PM_SYNC_TRACKER}"/*|CHANGELOG.md) return 0 ;; + esac + printf '::error::Changed path outside sync commit scope: %q\n' "$1" + return 1 + } + gate_sync_change() { + check_sync_scope + git ls-files -z --modified --deleted --others --exclude-standard -- "${sync_scope[0]}" "${sync_scope[@]:2}" > "${RUNNER_TEMP}/pm-github-sync-stage-paths" + if [[ -s "${RUNNER_TEMP}/pm-github-sync-stage-paths" ]]; then + git --literal-pathspecs add --pathspec-from-file="${RUNNER_TEMP}/pm-github-sync-stage-paths" --pathspec-file-nul + fi + if [[ -e CHANGELOG.md ]] || git ls-files --error-unmatch -- CHANGELOG.md >/dev/null 2>&1; then + git --literal-pathspecs add -- CHANGELOG.md + fi + git diff --cached --name-only -z --no-renames -- "${sync_scope[@]}" > "${RUNNER_TEMP}/pm-github-sync-commit-paths" + mapfile -d '' -t sync_paths < "${RUNNER_TEMP}/pm-github-sync-commit-paths" + : > "${RUNNER_TEMP}/pm-github-sync.patch" + if [[ ${#sync_paths[@]} -gt 0 ]]; then + git --literal-pathspecs diff --cached --no-color --no-ext-diff --no-textconv --no-renames --binary --unified=2147483647 -- "${sync_paths[@]}" > "${RUNNER_TEMP}/pm-github-sync.patch" + fi + ./node_modules/.bin/pm github gate --diff "${RUNNER_TEMP}/pm-github-sync.patch" --json + } + SCOPE + - name: Install pinned pm-github extension shell: bash run: | set -euo pipefail ./node_modules/.bin/pm package install "npm:pm-github@${PM_GITHUB_VERSION}" --project - node -e 'if (require("./.agents/pm/extensions/pm-github/package.json").version !== process.argv[1]) process.exit(1)' "${PM_GITHUB_VERSION}" + node -e 'if (require("./" + process.argv[2] + "/extensions/pm-github/package.json").version !== process.argv[1]) process.exit(1)' "${PM_GITHUB_VERSION}" "${PM_SYNC_TRACKER}" - name: Configure PM merge drivers run: ./node_modules/.bin/pm merge install @@ -156,16 +239,33 @@ jobs: run: | set -euo pipefail paths_file="${RUNNER_TEMP}/pm-github-tracker-paths" - git ls-files -z --modified --deleted --others --exclude-standard -- .agents/pm > "${paths_file}" - node -e 'const fs = require("node:fs"); const file = process.argv[1]; const paths = fs.readFileSync(file, "utf8").split("\0").filter(p => p && !["extensions", "locks", "checkpoints"].includes(p.slice(".agents/pm/".length).split("/")[0])); fs.writeFileSync(file, paths.map(p => p + "\0").join(""));' "${paths_file}" + source "${RUNNER_TEMP}/pm-github-sync-scope.sh" + git ls-files -z --modified --deleted --others --exclude-standard -- "${sync_scope[0]}" "${sync_scope[@]:2}" > "${paths_file}" if [[ -s "${paths_file}" ]]; then git --literal-pathspecs add --pathspec-from-file="${paths_file}" --pathspec-file-nul fi ./node_modules/.bin/pm github import "${REPOSITORY}" --state all --atomic --gate --with-comments - git diff --exit-code -- .agents/pm ':(exclude,glob).agents/pm/extensions/**' ':(exclude,glob).agents/pm/locks/**' ':(exclude,glob).agents/pm/checkpoints/**' - [[ -z "$(git ls-files --others --exclude-standard -- .agents/pm ':(exclude,glob).agents/pm/extensions/**' ':(exclude,glob).agents/pm/locks/**' ':(exclude,glob).agents/pm/checkpoints/**')" ]] + git diff --exit-code -- "${sync_scope[0]}" "${sync_scope[@]:2}" + [[ -z "$(git ls-files --others --exclude-standard -- "${sync_scope[0]}" "${sync_scope[@]:2}")" ]] ./node_modules/.bin/pm github gate --json + - name: Run caller post-import script + shell: bash + run: | + set -euo pipefail + if [[ -n "${POST_IMPORT_SCRIPT}" ]]; then + npm run --if-present "${POST_IMPORT_SCRIPT}" + fi + + # Check every changed path immediately after the caller script. Extension + # setup files stay outside the patch and are restored before publication. + - name: Gate tracker and regenerated changelog + shell: bash + run: | + set -euo pipefail + source "${RUNNER_TEMP}/pm-github-sync-scope.sh" + gate_sync_change + - name: Verify strict pm health run: ./node_modules/.bin/pm health --strict-exit @@ -175,7 +275,8 @@ jobs: shell: bash run: | set -euo pipefail - ./node_modules/.bin/pm github gate --json + source "${RUNNER_TEMP}/pm-github-sync-scope.sh" + gate_sync_change # gh pr list --head matches branch names, and a fork can open a PR # from a branch with the same name, so filter the results to PRs whose # head repository is this repository (never trust .[0] alone). @@ -192,19 +293,19 @@ jobs: gh pr close "${number}" -R "${REPOSITORY}" --comment "The tracker already matches GitHub, so this sync no longer proposes a change; closing this review PR." fi } - # Installing the pinned extension can add it to an explicit - # extensions.enabled list, and pm records that settings change in the - # workspace history. Both are setup state, not synced data, so neither - # reaches the commit (the extension is not needed after the gate). - for setup_path in .agents/pm/settings.json .agents/pm/history/_workspace.jsonl; do - if git cat-file -e "HEAD:${setup_path}" 2>/dev/null; then - git restore --source=HEAD --staged --worktree -- "${setup_path}" - else - git rm -q --cached --ignore-unmatch -- "${setup_path}" - rm -f -- "${setup_path}" + if git diff --cached --quiet -- "${sync_scope[0]}" "${sync_scope[@]:2}"; then + # Changelog drift alone belongs to the caller's changelog check; + # publish regenerated output only alongside tracker changes. + if ! git diff --cached --quiet -- CHANGELOG.md; then + if git cat-file -e HEAD:CHANGELOG.md 2>/dev/null; then + git --literal-pathspecs restore --source=HEAD --staged --worktree -- CHANGELOG.md + else + git --literal-pathspecs rm -q --cached --ignore-unmatch -- CHANGELOG.md + rm -f -- CHANGELOG.md + fi + echo "::notice::The caller's changelog differs from a fresh regeneration; the repository's own changelog:check owns that drift. Discarding CHANGELOG.md because no tracker item changed." fi - done - if git diff --cached --quiet; then + restore_sync_setup echo "GitHub issues already match the pm tracker; no PR is needed." # A stale own review PR would still look actionable although this # run has no change to propose, so close it before returning. @@ -216,7 +317,7 @@ jobs: : > "${links_file}" while IFS= read -r changed; do case "${changed}" in - .agents/pm/*/*.toon) + "${PM_SYNC_TRACKER}"/*/*.toon) item_id="${changed##*/}" item_id="${item_id%.toon}" # The main link is the permanent one after merge; the branch link @@ -224,12 +325,18 @@ jobs: printf -- '- [%s](https://github.com/%s/blob/main/%s) ([proposed](https://github.com/%s/blob/%s/%s))\n' "${item_id}" "${REPOSITORY}" "${changed}" "${REPOSITORY}" "${SYNC_BRANCH}" "${changed}" >> "${links_file}" ;; esac - done < <(git diff --cached --name-only -- .agents/pm) + done < <(git diff --cached --name-only -- "${sync_scope[0]}" "${sync_scope[@]:2}") if [[ ! -s "${links_file}" ]]; then echo "::error::The sync changed tracker state but no item link could be generated." exit 1 fi - git commit -m "chore(pm): sync GitHub issues (gated)" + # Re-enumerate all status paths, re-stage and re-scan immediately before + # committing. --only prevents any unrelated staged path from leaking + # even if the scope check is bypassed. NUL paths preserve odd filenames. + gate_sync_change + restore_sync_setup + check_sync_scope + git --literal-pathspecs commit --only --pathspec-from-file="${RUNNER_TEMP}/pm-github-sync-commit-paths" --pathspec-file-nul -m "chore(pm): sync GitHub issues (gated)" gh auth setup-git git push --force-with-lease="refs/heads/${SYNC_BRANCH}:${SYNC_LEASE}" origin "HEAD:refs/heads/${SYNC_BRANCH}" body_file="${RUNNER_TEMP}/pm-github-sync-pr.md" @@ -245,3 +352,29 @@ jobs: else gh pr edit "${pr_number}" -R "${REPOSITORY}" --body-file "${body_file}" fi + export SYNC_PUBLISHED=true + printf 'SYNC_PUBLISHED=true\n' >> "${GITHUB_ENV}" + + - name: Dispatch caller CI and report parked runs + if: env.SYNC_PUBLISHED == 'true' + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + [[ "${SYNC_PUBLISHED:-false}" == true ]] || exit 0 + while IFS= read -r workflow_file; do + [[ -n "${workflow_file}" ]] || continue + [[ "${workflow_file}" =~ ^[a-zA-Z0-9_.-]+\.ya?ml$ ]] || { echo "::error::ci-workflows must contain workflow filenames."; exit 1; } + if [[ ! -f ".github/workflows/${workflow_file}" ]]; then + echo "::notice::Skipping missing caller workflow: ${workflow_file}" + continue + fi + gh workflow run "${workflow_file}" -R "${REPOSITORY}" --ref "${SYNC_BRANCH}" + done < <(printf '%s\n' "${CI_WORKFLOWS}" | tr ' \t\r' '\n') + head_sha="$(git rev-parse HEAD)" + parked_runs="$(gh run list -R "${REPOSITORY}" --commit "${head_sha}" --status action_required --limit 100 --json url --jq '.[].url')" + while IFS= read -r run_url; do + [[ -n "${run_url}" ]] || continue + echo "::notice::A pull-request workflow requires maintainer approval: ${run_url}. Review this parked run if needed; caller CI dispatch runs are separate." + done <<< "${parked_runs}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 353d7497..5b609300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Added + +- Regenerate caller changelogs and dispatch required CI for gated sync PRs ([pm-github-wrmt](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-wrmt.toon)) + ## 2026.10.14 - 2026-10-14 ### Added diff --git a/README.md b/README.md index 5cce8c61..05805461 100644 --- a/README.md +++ b/README.md @@ -349,7 +349,23 @@ The `gate` receipt reports the verdict, scanned file count, findings, and allowl ## Automated sync workflow -Fleet repos sync their issues into pm through ONE audited implementation: the reusable `workflow_call` workflow at [`.github/workflows/pm-github-sync.yml`](.github/workflows/pm-github-sync.yml) in this repository. It runs the gated pipeline in fail-closed order — checkout → install the **pinned** extension → `pm github validate` → dry-run plan → **gated import** → verify the repeat import is a no-op → `pm health --strict-exit` → and only then commit, push the sync branch, and open/update a review PR whose body links every changed item as `https://github.com//blob/main/.agents/pm//.toon`. Because `--gate` exits non-zero on any finding, the job can never reach the push step with a leak in the change. Actions are pinned by commit SHA; permissions are least-privilege (`contents: write`, `pull-requests: write`, `issues: read`). +Fleet repos sync their issues into pm through ONE audited implementation: the reusable `workflow_call` workflow at [`.github/workflows/pm-github-sync.yml`](.github/workflows/pm-github-sync.yml). Its safety order is validate → dry-run plan → **gated import** → verify the repeat import is a no-op → caller post-import script → **full changed-path scope check and privacy gate** → strict health → repeat scope check and scan immediately before commit/push/review PR → caller CI dispatch. The default `post-import-script` is `changelog:full`, invoked with `npm run --if-present`: it runs the caller's own generator when present, so imported closed issues and their changelog entries land in the same commit. Actions are pinned by commit SHA. The job requests `contents: write`, `pull-requests: write`, `issues: read`, and `actions: write` for dispatch. + +When the tracker has no changes, regenerated `CHANGELOG.md` drift is discarded and the run emits a notice that the caller's own `changelog:check` owns that drift. The sync closes any owned open review PR and exits successfully without pushing or dispatching CI. Changelog changes are published only together with tracker changes. + +The explicit allowed scope for changed paths is: + +- Files beneath the SDK-resolved repository-local pm tracker, excluding its `extensions/`, `locks/`, and `checkpoints/` runtime directories. The extension setup files `settings.json` and `history/_workspace.jsonl` are permitted during setup, then restored before publication and excluded from both the scan and commit. +- The repository-root `CHANGELOG.md`. + +After the caller script and again immediately before committing, `git status --porcelain=v1 -z --untracked-files=all` enumerates staged, unstaged, and untracked changes, including both sides of renames. Every path outside this list produces an error and prevents publication. Allowed changes are staged and the privacy gate scans the exact NUL-delimited path list passed to `git commit --only --pathspec-from-file` with literal pathspecs. The final scan is regenerated from current content; any finding, unreadable input, or script failure prevents publication. The restricted commit also excludes unrelated staged content if a scope check is bypassed. + +Optional inputs: + +| Input | Default | Behavior | +| --- | --- | --- | +| `post-import-script` | `changelog:full` | Caller npm script name invoked with `npm run --if-present` after import and before the final scan. Override for a different package script, or set `""` to disable. Missing scripts are skipped. Raw shell commands are not accepted. Generated `CHANGELOG.md` is staged with the tracker. Changing any file outside the documented scope fails the run, whether staged, unstaged, or untracked. | +| `ci-workflows` | `ci.yml` | Whitespace-separated filenames in the caller's `.github/workflows`, for example `ci.yml codeql.yml`. Missing files emit a notice and are skipped; set `""` to disable dispatch. Existing workflows must enable `workflow_dispatch`; dispatch errors fail the sync job. | A fleet repo owns the schedule and calls it with the pinned versions (see [`docs/sync-workflow-caller.yml`](docs/sync-workflow-caller.yml)): @@ -364,6 +380,7 @@ permissions: contents: write pull-requests: write issues: read + actions: write jobs: gated-sync: @@ -372,7 +389,9 @@ jobs: pm-github-version: "RELEASE_VERSION" # required: exact published version ``` -Replace the two release placeholders after the gated extension is published; the current published version does not contain this feature. The calling repo needs `@unbrained/pm-cli` as a devDependency (the workflow's `npm ci` provides the CLI) and its pm tracker at `.agents/pm` committed on `main`. Under **Settings → Actions → General → Workflow permissions** it must also allow read and write token permissions and enable **Allow GitHub Actions to create and approve pull requests** (an organization can disable this), or the run pushes the sync branch and then fails to open the review PR. A PR that `GITHUB_TOKEN` opens or edits does not trigger `pull_request` workflows, so the sync PR gets no CI checks until a maintainer re-runs them (for example by closing and reopening the PR); the reusable workflow always uses the job's `GITHUB_TOKEN`. +Replace the two release placeholders with the release commit and package version containing this change; existing callers must repin both. The calling repo needs `@unbrained/pm-cli` as a devDependency (the workflow's `npm ci` provides the CLI) and its pm tracker at `.agents/pm` committed on `main`. Under **Settings → Actions → General → Workflow permissions** it must allow read and write token permissions and enable **Allow GitHub Actions to create and approve pull requests** (an organization can disable this), or the run pushes the sync branch and then fails to open the review PR. The caller must grant `actions: write`; a reusable workflow cannot elevate its caller's permissions. + +Bot-created review PRs may have missing required checks or `pull_request` runs parked in `action_required`. After a successful push and PR creation/update, the reusable workflow uses the job's `GITHUB_TOKEN` to run `gh workflow run --ref "$SYNC_BRANCH"` for each configured caller CI workflow. Explicit `workflow_dispatch` creates runs on the sync head, so callers must enable that trigger and ensure those runs produce their required check names (and do not restrict the required jobs to the `pull_request` event). It also lists `action_required` runs for the exact pushed SHA and prints their URLs as notices without failing because approval is needed. Missing workflows, disabled dispatch, or approval notices do not establish that required CI passed; inspect the dispatched checks. A no-change sync closes its stale own review PR and dispatches nothing. See GitHub's [GITHUB_TOKEN event rules](https://docs.github.com/en/actions/concepts/security/github_token) and [dispatch trigger and permission requirements](https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event). ### Installed CLI timeout acceptance diff --git a/docs/sync-workflow-caller.yml b/docs/sync-workflow-caller.yml index cfa8cd59..8d0087af 100644 --- a/docs/sync-workflow-caller.yml +++ b/docs/sync-workflow-caller.yml @@ -1,4 +1,7 @@ -# Replace release placeholders after the gated extension is published. +# Repin both placeholders to the release containing changelog regeneration and CI dispatch. +# Each configured CI workflow must enable workflow_dispatch and produce its required +# check names on that event. Missing files are skipped with a notice. +# Parked action_required PR runs are reported with URLs; dispatch runs separately. # Repository setup: Settings -> Actions -> General -> Workflow permissions must allow # read and write permissions and enable "Allow GitHub Actions to create and approve # pull requests" (off by default for new personal repositories; an organization can @@ -12,8 +15,12 @@ permissions: contents: write pull-requests: write issues: read + actions: write # Required to dispatch the caller CI after opening/updating the PR. jobs: gated-sync: uses: unbraind/pm-github/.github/workflows/pm-github-sync.yml@RELEASE_COMMIT_SHA with: pm-github-version: "RELEASE_VERSION" + # Defaults shown explicitly; use "" to disable either optional action. + post-import-script: "changelog:full" + ci-workflows: "ci.yml" # Whitespace-separated filenames, e.g. "ci.yml codeql.yml". diff --git a/scripts/coverage-gate.ts b/scripts/coverage-gate.ts index 5543a186..0186c8b6 100644 --- a/scripts/coverage-gate.ts +++ b/scripts/coverage-gate.ts @@ -63,6 +63,9 @@ export function collectCoverageSources(root: string, target: string): string[] { }); } +/** Heap ceiling for the c8 merge process; GitHub-hosted Linux runners have 16 GiB. */ +export const COVERAGE_HEAP_FLAG = "--max-old-space-size=8192"; + /** * Run the strict all-source gate and return its exit status. * @@ -86,7 +89,12 @@ export function runCoverageGate(root: string, runner: CoverageRunner = spawnSync const coverageEnv: NodeJS.ProcessEnv = { ...process.env, TZ: "UTC" }; delete coverageEnv.NODE_TEST_CONTEXT; const node = nodeToolingExecutable(process.versions); - const result = runner(node, [c8, "--100", "--all", "--exclude-after-remap", "--extension=.ts", "--extension=.js", + // c8 merges the raw V8 coverage of every spawned process, including each + // real pm CLI child the sync-pipeline tests start. That merge exceeded Node's + // default ~4 GiB heap on CI ("Reached heap limit"), after every test had + // passed. The larger heap is a resource limit only: thresholds, includes + // and the test set are unchanged. + const result = runner(node, [COVERAGE_HEAP_FLAG, c8, "--100", "--all", "--exclude-after-remap", "--extension=.ts", "--extension=.js", `--reports-dir=${reports}`, `--temp-directory=${join(reports, "v8")}`, "--reporter=text", "--reporter=lcov", "--reporter=json-summary", "--reporter=json", ...sources.map(file => `--include=${file}`), node, "--test", ...config.tests], diff --git a/test/coverage-gate.test.ts b/test/coverage-gate.test.ts index 450dcbdb..0649da6a 100644 --- a/test/coverage-gate.test.ts +++ b/test/coverage-gate.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; -import { collectCoverageSources, runCoverageGate, runCoverageGateIfMain, type CoverageRunner } from "../scripts/coverage-gate.ts"; +import { collectCoverageSources, COVERAGE_HEAP_FLAG, runCoverageGate, runCoverageGateIfMain, type CoverageRunner } from "../scripts/coverage-gate.ts"; import { nodeToolingExecutable } from "../scripts/main-invocation.ts"; /** Create an independently runnable project with one branching source and its test. */ @@ -135,3 +135,17 @@ test("coverage inventory accepts dot-dot names inside the repository and rejects assert.throws(() => collectCoverageSources(root, ".."), /escapes the repository/); } finally { rmSync(root, { recursive: true, force: true }); } }); + +test("the c8 merge process gets the enlarged heap before the c8 entry point", () => { + const seen: string[][] = []; + const spy: CoverageRunner = (_command, args) => { seen.push([...args]); return { status: 1 }; }; + const root = coverageFixture(); + try { + assert.equal(runCoverageGate(root, spy), 1); + } finally { + rmSync(root, { recursive: true, force: true }); + } + assert.equal(seen.length, 1); + assert.equal(seen[0]![0], COVERAGE_HEAP_FLAG, "the heap flag must precede c8 so the merge process receives it"); + assert.match(seen[0]![1]!, /c8\.js$/); +}); diff --git a/test/import-gate.test.ts b/test/import-gate.test.ts index 94f7fb6c..e807192a 100644 --- a/test/import-gate.test.ts +++ b/test/import-gate.test.ts @@ -4,7 +4,8 @@ // the adversarial cases prove a malicious issue body, comment, email, or host // path fails the gated import step so nothing is ever committed or pushed to // the remote. The positive case proves the whole sequence: plan → gated import -// → strict health → commit/push → a second gated run that is a no-op for item +// → caller changelog → final gate → strict health → commit/push → CI dispatch +// → a second gated run that is a no-op for item // identity via the provenance tag. // // Malicious fixtures are syntactically valid but FAKE and assembled from parts @@ -591,9 +592,11 @@ test("the reusable sync workflow is a workflow_call with explicit pinned inputs" assert.match(workflow, /^on:\n workflow_call:/m); assert.match(workflow, /pm-github-version:\n\s+description:[^\n]+\n\s+type: string\n\s+required: true/); assert.match(workflow, /repository:\n\s+description:/); + assert.match(workflow, /post-import-script:[\s\S]*?default: "changelog:full"/); + assert.match(workflow, /ci-workflows:[\s\S]*?default: "ci.yml"/); assert.doesNotMatch(workflow, /^on:\n schedule:/m, "the schedule stays with the caller"); - // Job permissions cover issue reads, branch pushes and review PR writes. - const permissions = /^ permissions:\n contents: write\n pull-requests: write\n issues: read\n/m; + // Job permissions cover issue reads, branch pushes, review PR writes and dispatch. + const permissions = /^ permissions:\n contents: write\n pull-requests: write\n issues: read\n actions: write\n/m; assert.match(workflow, permissions); assert.ok( (workflow.match(/^ permissions:/gm) ?? []).length === 1, @@ -622,7 +625,11 @@ test("the gated import runs after validate/plan and before health, commit, and p const install = stepOffset("Install pinned pm-github extension"); assert.ok(install < validate, "the pinned extension is installed before it is used"); assert.ok(validate < plan && plan < gated, "validate and dry-run plan precede the gated import"); - assert.ok(gated < health && health < commit, "the gate precedes health, commit, and push"); + const postImport = stepOffset("Run caller post-import script"); + const finalGate = stepOffset("Gate tracker and regenerated changelog"); + const dispatch = stepOffset("Dispatch caller CI and report parked runs"); + assert.ok(gated < postImport && postImport < finalGate && finalGate < health && health < commit && commit < dispatch, + "post-import output is gated before health/publication and CI follows the PR"); assert.match(workflow, /import "\$\{REPOSITORY\}" --state all --atomic --gate/); assert.match(workflow, /pm health --strict-exit/); // No staged catch-all that could commit unscanned content. @@ -636,7 +643,7 @@ test("the PR body links every changed item as a permanent main-tree pm link", () /https:\/\/github\.com\/%s\/blob\/main\/%s/, "the link template targets the main-tree item path", ); - assert.match(workflow, /\.agents\/pm\/\*\/\*\.toon/); + assert.match(workflow, /"\$\{PM_SYNC_TRACKER\}"\/\*\/\*\.toon/); assert.match(workflow, /gh pr create/); assert.match(workflow, /gh pr edit/); // The push is explicit and force-resets the automation branch from main. @@ -651,6 +658,7 @@ test("the caller example pins the reusable workflow and the extension version", ); assert.match(callerExample, /pm-github-version: "RELEASE_VERSION"/); assert.match(callerExample, /^permissions:\n contents: write\n pull-requests: write\n/m); + assert.match(callerExample, /^ actions: write/m); const lines = callerExample.split("\n").filter((line) => line.trim() !== "" && !line.startsWith("#")); assert.ok(lines.length <= 40, "the caller stays a small, reviewable file"); }); @@ -696,9 +704,18 @@ function packWorkflowCandidate(packageRoot: string, destination: string, env: No /** Install the candidate and create a local gh PR receipt boundary, never a network writer. */ async function prepareWorkflowFixture(root: string, base: string): Promise { + if (!fs.existsSync(path.join(root, "package.json"))) fs.writeFileSync(path.join(root, "package.json"), "{}\n"); + // Match caller dependency/runtime ignores before installing the extension; + // fixture setup itself must not masquerade as a post-import change. + fs.appendFileSync(path.join(root, ".gitignore"), "\nnode_modules/\n.agents/pm/extensions/\n.agents/pm/locks/\n.agents/pm/checkpoints/\n"); + assert.equal(spawnSync("git", ["add", "--", "package.json", ".gitignore"], { cwd: root }).status, 0); + assert.equal(spawnSync("git", ["-c", "user.name=Sync Fixture", "-c", "user.email=fixture@users.noreply.github.com", + "commit", "-qm", "caller dependency configuration"], { cwd: root }).status, 0); const bin = path.join(root, "node_modules", ".bin"); fs.mkdirSync(bin, { recursive: true }); fs.symlinkSync(path.resolve(import.meta.dirname, "../node_modules/.bin/pm"), path.join(bin, "pm")); + fs.mkdirSync(path.join(root, "node_modules/@unbrained"), { recursive: true }); + fs.symlinkSync(path.join(repoRoot, "node_modules/@unbrained/pm-cli"), path.join(root, "node_modules/@unbrained/pm-cli")); const tools = path.join(base, "tools"); fs.mkdirSync(tools, { recursive: true }); fs.writeFileSync(path.join(tools, "gh"), `#!/usr/bin/env bash @@ -732,12 +749,23 @@ case "$1 $2" in shift done ;; + "workflow run") + [[ -s "\${REVIEW_RECEIPT}" ]] || exit 2 + git ls-remote --exit-code origin "refs/heads/\${SYNC_BRANCH}" >/dev/null || exit 2 + printf '%s\\n' "$*" >> "\${REVIEW_ACTIONS}.dispatch" + exit "\${DISPATCH_STATUS:-0}" + ;; + "run list") + printf '%s\\n' "$*" >> "\${REVIEW_ACTIONS}.runs" + printf '%s\\n' "\${PARKED_RUN_URL:-}" + ;; *) exit 1 ;; esac `, { mode: 0o755 }); const env = { ...process.env, PATH: tools + path.delimiter + bin + path.delimiter + process.env.PATH, GH_TOKEN: "synthetic-test-token", GITHUB_TOKEN: "synthetic-test-token", PM_AUTHOR: "codex-sol", PM_GLOBAL_PATH: fixtureGlobalRoot, PM_PATH: path.join(root, ".agents", "pm"), REPOSITORY: "acme/widgets", CALLING_REPOSITORY: "acme/widgets", + POST_IMPORT_SCRIPT: "changelog:full", CI_WORKFLOWS: "ci.yml", PM_GITHUB_VERSION: "2026.10.4", SYNC_BRANCH, SYNC_LEASE: "", RUNNER_TEMP: base, GITHUB_ENV: path.join(base, "job-env"), REVIEW_RECEIPT: path.join(base, "review.md"), REVIEW_ACTIONS: path.join(base, "review-actions") }; const packageRoot = path.resolve(import.meta.dirname, ".."); @@ -752,8 +780,9 @@ esac return env; } -const WORKFLOW_IMPORT_SEQUENCE = ["Validate GitHub access", "Revalidate GitHub blockers (report only)", "Preview GitHub to pm plan", "Gated GitHub to pm import", - "Verify repeat import is a no-op", "Verify strict pm health", "Commit gated sync changes and open review PR"] as const; +const WORKFLOW_IMPORT_SEQUENCE = ["Resolve sync commit scope", "Validate GitHub access", "Revalidate GitHub blockers (report only)", "Preview GitHub to pm plan", "Gated GitHub to pm import", + "Verify repeat import is a no-op", "Run caller post-import script", "Gate tracker and regenerated changelog", + "Verify strict pm health", "Commit gated sync changes and open review PR", "Dispatch caller CI and report parked runs"] as const; test("workflow packing isolates prepare from the caller tracker in clones and linked worktrees", async () => { const { root, base } = initSyncRepo(); @@ -841,9 +870,23 @@ test("the executable reusable workflow never publishes each adversarial fixture" test("the executable reusable workflow pushes only a clean import and creates or updates linked reviews", async () => { const { root, base, bare, git } = initSyncRepo(); try { + fs.mkdirSync(path.join(root, ".github/workflows"), { recursive: true }); + for (const file of ["ci.yml", "codeql.yaml"]) fs.writeFileSync(path.join(root, ".github/workflows", file), "on: workflow_dispatch\n"); + fs.writeFileSync(path.join(root, "generate-changelog.cjs"), `const fs = require("node:fs"); +const items = fs.readdirSync(".agents/pm/issues").filter(file => file.endsWith(".toon")); +if (items.length !== 1) process.exit(1); +fs.writeFileSync("CHANGELOG.md", "# Changelog\\n- " + items[0] + "\\n"); +`); + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ scripts: { "changelog:full": "node generate-changelog.cjs" } })); + fs.writeFileSync(path.join(root, "CHANGELOG.md"), "# Changelog\n"); + assert.equal(git(["add", "--", ".github/workflows", "generate-changelog.cjs", "package.json", "CHANGELOG.md"]).status, 0); + assert.equal(git(["commit", "-qm", "caller changelog and dispatchable CI"]).status, 0); + assert.equal(git(["push", "-q", "origin", "main"]).status, 0); const env = await prepareWorkflowFixture(root, base); + env.CI_WORKFLOWS = "ci.yml missing.yml\ncodeql.yaml"; + env.PARKED_RUN_URL = "https://example.invalid/actions/runs/123"; assert.equal(git(["switch", "-c", SYNC_BRANCH]).status, 0); - const handle = githubHandler([issue(1, "Synthetic issue", "Reviewed public body.")]); + const handle = githubHandler([{ ...issue(1, "Synthetic issue", "Reviewed public body."), state: "closed" }]); await withMockGithub((req, res, url, server) => { if (req.url === "/repos/acme/widgets") jsonResponse(res, 200, { private: false }); else handle(req, res, url, server); @@ -857,11 +900,24 @@ test("the executable reusable workflow pushes only a clean import and creates or // While the PR is open the main link may not resolve yet, so each item // also links its proposed version on the sync branch. assert.ok(review.includes(`([proposed](https://github.com/acme/widgets/blob/${SYNC_BRANCH}/.agents/pm/issues/`), review); + assert.match(fs.readFileSync(env.GITHUB_ENV!, "utf8"), /^SYNC_PUBLISHED=true$/m, "subsequent Actions steps see publication success"); const lease = git(["rev-parse", "HEAD"]).stdout.trim(); + const publishedChangelog = spawnSync("git", ["--git-dir", bare, "show", `${SYNC_BRANCH}:CHANGELOG.md`], { encoding: "utf8" }); + assert.equal(publishedChangelog.status, 0); + assert.match(publishedChangelog.stdout, /- .*\.toon/); + const publishedPaths = git(["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"]).stdout.trim().split("\n"); + assert.ok(publishedPaths.includes("CHANGELOG.md"), "the sync commit includes regenerated changelog output"); + assert.ok(publishedPaths.some(file => /^\.agents\/pm\/issues\/.*\.toon$/.test(file)), "the same sync commit includes tracker items"); + assert.deepEqual(fs.readFileSync(env.REVIEW_ACTIONS! + ".dispatch", "utf8").trim().split("\n"), + ["ci.yml", "codeql.yaml"].map(file => `workflow run ${file} -R acme/widgets --ref ${SYNC_BRANCH}`)); + assert.match(result.stdout, /::notice::Skipping missing caller workflow: missing.yml/); + assert.ok(result.stdout.includes(`::notice::A pull-request workflow requires maintainer approval: ${env.PARKED_RUN_URL}`)); + assert.ok(fs.readFileSync(env.REVIEW_ACTIONS! + ".runs", "utf8").includes(`--commit ${lease} --status action_required`)); // Each Actions job starts from a fresh checkout, without old local journals. const nextRoot = path.join(base, "next-work"); assert.equal(spawnSync("git", ["clone", "--branch", "main", bare, nextRoot], { encoding: "utf8" }).status, 0); const nextEnv = await prepareWorkflowFixture(nextRoot, base); + nextEnv.CI_WORKFLOWS = "ci.yml codeql.yaml"; assert.equal(spawnSync("git", ["-C", nextRoot, "switch", "-c", SYNC_BRANCH]).status, 0); assert.equal(spawnSync("git", ["-C", nextRoot, "config", "user.name", "Fixture"]).status, 0); assert.equal(spawnSync("git", ["-C", nextRoot, "config", "user.email", "fixture@users.noreply.github.com"]).status, 0); @@ -869,6 +925,8 @@ test("the executable reusable workflow pushes only a clean import and creates or PM_GITHUB_API_BASE: process.env.PM_GITHUB_API_BASE, SYNC_LEASE: lease, OWN_OPEN_PR: "1" }); assert.equal(update.code, 0, update.stderr + "\n" + update.stdout); assert.deepEqual(fs.readFileSync(env.REVIEW_ACTIONS!, "utf8").trim().split("\n"), ["create", "edit"]); + assert.equal(fs.readFileSync(env.REVIEW_ACTIONS! + ".dispatch", "utf8").trim().split("\n").length, 4, + "both PR creation and update dispatch caller CI"); }); } finally { fs.rmSync(base, { recursive: true, force: true }); } }); @@ -902,11 +960,13 @@ test("the workflow never edits a fork PR sharing the sync branch name", async () * GitHub, so the run proposes no change ("no PR is needed"). * * @param prListOverrides - Environment shaping which open PRs `gh pr list` lists. + * @param changelogState - Optional caller changelog drift to regenerate after a no-op import. * @returns The exit code, output, recorded review-PR actions and remote refs. */ -async function replayNoChangeSequence(prListOverrides: NodeJS.ProcessEnv): Promise<{ +async function replayNoChangeSequence(prListOverrides: NodeJS.ProcessEnv, changelogState?: "stale" | "absent"): Promise<{ code: number | null; stdout: string; stderr: string; reviewActions: string[] | undefined; refs: string[]; + status: string; changelog: string | undefined; published: boolean; }> { const { root, base, bare, git } = initSyncRepo(); try { @@ -917,6 +977,13 @@ async function replayNoChangeSequence(prListOverrides: NodeJS.ProcessEnv): Promi settings.extensions.enabled = ["pm-other"]; fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n"); assert.equal(git(["commit", "-qam", "restrict enabled extensions"]).status, 0); + if (changelogState) { + fs.writeFileSync(path.join(root, "generate-changelog.cjs"), 'require("node:fs").writeFileSync("CHANGELOG.md", "# Fresh changelog\\n");\n'); + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ scripts: { "changelog:full": "node generate-changelog.cjs" } })); + if (changelogState === "stale") fs.writeFileSync(path.join(root, "CHANGELOG.md"), "# Stale changelog\n"); + assert.equal(git(["add", "--", "generate-changelog.cjs", "package.json", ...(changelogState === "stale" ? ["CHANGELOG.md"] : [])]).status, 0); + assert.equal(git(["commit", "-qm", "caller changelog regeneration fixture"]).status, 0); + } assert.equal(git(["push", "-q", bare, "main"]).status, 0); const env = await prepareWorkflowFixture(root, base); assert.notEqual(git(["diff", "--quiet", "--", ".agents/pm/settings.json"]).status, 0, "precondition: install edits settings.json"); @@ -932,7 +999,9 @@ async function replayNoChangeSequence(prListOverrides: NodeJS.ProcessEnv): Promi const actionsPath = env.REVIEW_ACTIONS!; return { code: result!.code, stdout: result!.stdout, stderr: result!.stderr, reviewActions: fs.existsSync(actionsPath) ? fs.readFileSync(actionsPath, "utf8").trim().split("\n") : undefined, - refs: remoteRefs(bare) }; + refs: remoteRefs(bare), status: git(["status", "--porcelain"]).stdout.trim(), + changelog: fs.existsSync(path.join(root, "CHANGELOG.md")) ? fs.readFileSync(path.join(root, "CHANGELOG.md"), "utf8") : undefined, + published: fs.readFileSync(env.GITHUB_ENV!, "utf8").includes("SYNC_PUBLISHED=true") }; } finally { fs.rmSync(base, { recursive: true, force: true }); } } @@ -960,6 +1029,30 @@ test("a no-change run with only a fork PR sharing the branch name closes nothing assert.deepEqual(run.refs, ["refs/heads/main"]); }); +test("a no-op import discards stale changelog drift and leaves a clean checkout without publication", async () => { + for (const ownPr of [undefined, "1"]) { + const run = await replayNoChangeSequence({ OWN_OPEN_PR: ownPr }, "stale"); + assert.equal(run.code, 0, run.stderr + "\n" + run.stdout); + assert.match(run.stdout, /::notice::.*changelog differs from a fresh regeneration.*changelog:check owns that/); + assert.equal(run.changelog, "# Stale changelog\n", "restore the committed caller changelog"); + assert.equal(run.status, "", "both index and working tree are clean"); + assert.deepEqual(run.reviewActions, ownPr ? ["close"] : undefined); + assert.deepEqual(run.refs, ["refs/heads/main"], "the no-op run does not push"); + assert.equal(run.published, false, "the no-op run does not dispatch caller CI"); + } +}); + +test("a no-op import discards a newly generated changelog absent from HEAD", async () => { + const run = await replayNoChangeSequence({}, "absent"); + assert.equal(run.code, 0, run.stderr + "\n" + run.stdout); + assert.match(run.stdout, /::notice::.*changelog:check owns that/); + assert.equal(run.changelog, undefined); + assert.equal(run.status, ""); + assert.equal(run.reviewActions, undefined); + assert.deepEqual(run.refs, ["refs/heads/main"]); + assert.equal(run.published, false); +}); + test("packed installed CLI repeats a completed gated import under native Bun within 45 seconds", { timeout: 180_000 }, async () => { const { root, base, bare } = initSyncRepo(); try { @@ -1033,6 +1126,8 @@ test("workflow input shell diagnoses a foreign repository and branch outside aut for (const [input, value, diagnostic] of [ ["REPOSITORY", "acme/other", "::error::repository must equal the calling repository (acme/widgets)."], ["SYNC_BRANCH", "feature/sync", "::error::sync-branch must start with automation/."], + ["POST_IMPORT_SCRIPT", "npm run changelog:full", "::error::post-import-script must be an npm script name, not a shell command."], + ["POST_IMPORT_SCRIPT", "--version", "::error::post-import-script must be an npm script name, not a shell command."], ] as const) { const result = await executeWorkflow(workflowShell(["Validate sync inputs"]), repoRoot, { ...process.env, REPOSITORY: "acme/widgets", CALLING_REPOSITORY: "acme/widgets", SYNC_BRANCH, PM_GITHUB_VERSION: "2026.10.6", @@ -1046,7 +1141,7 @@ test("workflow input shell diagnoses a foreign repository and branch outside aut test("workflow credentials stay out of install steps and checkout persistence", () => { const steps = workflow.split(/\n - name: /).slice(1); const tokenSteps = steps.filter(step => step.includes("GH_TOKEN:")).map(step => step.split("\n")[0]); - assert.deepEqual(tokenSteps, ["Validate GitHub access", "Revalidate GitHub blockers (report only)", "Preview GitHub to pm plan", "Gated GitHub to pm import", "Verify repeat import is a no-op", "Commit gated sync changes and open review PR"]); + assert.deepEqual(tokenSteps, ["Validate GitHub access", "Revalidate GitHub blockers (report only)", "Preview GitHub to pm plan", "Gated GitHub to pm import", "Verify repeat import is a no-op", "Commit gated sync changes and open review PR", "Dispatch caller CI and report parked runs"]); assert.doesNotMatch(workflow.slice(0, stepOffset("Validate sync inputs")), /GH_TOKEN:/); assert.match(steps.find(step => step.startsWith("Checkout main"))!, /persist-credentials: false/); assert.match(workflowShell(["Install dependencies"]), /npm ci --ignore-scripts/); @@ -1076,3 +1171,175 @@ test("scheduled blocker report warns on clearance, preserves items, and fails on } } finally { fs.rmSync(base, { recursive: true, force: true }); } }); + + +test("post-import changelog findings and script failures block publication", async () => { + for (const script of ["generate-secret", "fail-script"]) { + const { root, base, bare, git } = initSyncRepo(); + try { + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ scripts: { + "generate-secret": "node generate-secret.cjs", "fail-script": "node -e 'process.exit(19)'", + } })); + fs.writeFileSync(path.join(root, "generate-secret.cjs"), + 'require("node:fs").writeFileSync("CHANGELOG.md", process.env.SYNTHETIC_SECRET + "\\n");\n'); + assert.equal(git(["add", "--", "package.json", "generate-secret.cjs"]).status, 0); + assert.equal(git(["commit", "-qm", "caller scripts"]).status, 0); + const env = await prepareWorkflowFixture(root, base); + assert.equal(git(["switch", "-c", SYNC_BRANCH]).status, 0); + const handle = githubHandler([issue(1, "Public issue", "Reviewed public body.")]); + await withMockGithub((req, res, url, server) => { + if (req.url === "/repos/acme/widgets") jsonResponse(res, 200, { private: false }); + else handle(req, res, url, server); + }, async () => { + const result = await executeWorkflow(workflowShell(WORKFLOW_IMPORT_SEQUENCE), root, { ...env, + PM_GITHUB_API_BASE: process.env.PM_GITHUB_API_BASE, POST_IMPORT_SCRIPT: script, SYNTHETIC_SECRET: GH_TOKEN }); + assert.notEqual(result.code, 0); + if (script === "generate-secret") { + assert.match(result.stderr, /github-token-classic/); + assert.ok(fs.readFileSync(path.join(root, "CHANGELOG.md"), "utf8").includes(GH_TOKEN)); + assert.ok(!result.stdout.includes(GH_TOKEN) && !result.stderr.includes(GH_TOKEN)); + } else assert.equal(result.code, 19); + assert.deepEqual(remoteRefs(bare), ["refs/heads/main"]); + assert.ok(!fs.existsSync(env.REVIEW_RECEIPT!)); + assert.ok(!fs.existsSync(env.REVIEW_ACTIONS! + ".dispatch")); + }); + } finally { fs.rmSync(base, { recursive: true, force: true }); } + } +}); + +test("caller post-import changes outside the commit scope fail closed for every Git status class", async () => { + for (const mode of ["staged", "unstaged", "untracked", "credential", "rename", "multiple"] as const) { + const { root, base, bare, git } = initSyncRepo(); + try { + const offending = mode === "multiple" ? ["extra.txt", "second.txt"] : ["extra.txt"]; + fs.writeFileSync(path.join(root, "extra.txt"), "baseline\n"); + fs.writeFileSync(path.join(root, "post-import.ts"), `import { writeFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; +const mode = process.env.SCOPE_TEST_MODE; +if (mode !== "rename") writeFileSync("extra.txt", mode === "credential" ? process.env.SYNTHETIC_SECRET! : "changed\\n"); +if (mode === "staged" || mode === "credential") execFileSync("git", ["add", "--", "extra.txt"]); +if (mode === "rename") execFileSync("git", ["mv", "extra.txt", ".agents/pm/extra.txt"]); +if (mode === "multiple") writeFileSync("second.txt", "changed\\n"); +`); + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ scripts: { "post-import-run": "node post-import.ts" } })); + const baselinePaths = ["package.json", "post-import.ts", ...(mode === "untracked" ? [] : ["extra.txt"])]; + assert.equal(git(["add", "--", ...baselinePaths]).status, 0); + assert.equal(git(["commit", "-qm", "caller fixture"]).status, 0); + assert.equal(git(["push", "-q", "origin", "main"]).status, 0); + if (mode === "untracked") fs.unlinkSync(path.join(root, "extra.txt")); + const before = spawnSync("git", ["--git-dir", bare, "rev-parse", "main"], { encoding: "utf8" }).stdout; + const env = await prepareWorkflowFixture(root, base); + assert.equal(git(["switch", "-c", SYNC_BRANCH]).status, 0); + const handle = githubHandler([issue(1, "Public issue", "Reviewed public body.")]); + await withMockGithub((req, res, url, server) => { + if (req.url === "/repos/acme/widgets") jsonResponse(res, 200, { private: false }); + else handle(req, res, url, server); + }, async () => { + const result = await executeWorkflow(workflowShell(WORKFLOW_IMPORT_SEQUENCE), root, { ...env, + PM_GITHUB_API_BASE: process.env.PM_GITHUB_API_BASE, POST_IMPORT_SCRIPT: "post-import-run", + SCOPE_TEST_MODE: mode, SYNTHETIC_SECRET: GH_TOKEN }); + assert.notEqual(result.code, 0, `must reject ${mode}: ${result.stdout}\n${result.stderr}`); + assert.match(result.stdout, /Changed path outside sync commit scope/); + for (const file of offending) assert.ok(result.stdout.includes(file), result.stdout); + assert.ok(!result.stdout.includes(GH_TOKEN) && !result.stderr.includes(GH_TOKEN)); + assert.deepEqual(remoteRefs(bare), ["refs/heads/main"]); + assert.equal(spawnSync("git", ["--git-dir", bare, "rev-parse", "main"], { encoding: "utf8" }).stdout, before); + assert.ok(!fs.existsSync(env.REVIEW_RECEIPT!)); + assert.ok(!fs.existsSync(env.REVIEW_ACTIONS! + ".dispatch")); + }); + } finally { fs.rmSync(base, { recursive: true, force: true }); } + } +}); + +test("publication rechecks late changes and rescans the exact working content it commits", async () => { + for (const mode of ["staged", "unstaged", "untracked", "credential", "tracker-secret"] as const) { + const { root, base, bare, git } = initSyncRepo(); + try { + const env = await prepareWorkflowFixture(root, base); + assert.equal(git(["switch", "-c", SYNC_BRANCH]).status, 0); + const handle = githubHandler([issue(1, "Public issue", "Reviewed public body.")]); + await withMockGithub((req, res, url, server) => { + if (req.url === "/repos/acme/widgets") jsonResponse(res, 200, { private: false }); + else handle(req, res, url, server); + }, async () => { + const filename = mode === "tracker-secret" ? "CHANGELOG.md" : mode === "unstaged" ? "package.json" : "late.txt"; + const mutate = `node -e 'require("node:fs").writeFileSync(process.env.LATE_PATH, process.env.LATE_CONTENT)'\n` + + (mode === "staged" || mode === "credential" ? 'git add -- "$LATE_PATH"\n' : ""); + // Insert immediately BEFORE the final status check and scan, after link + // generation, so both the early gate and the commit-step entry pass. + const lateShell = workflowShell(WORKFLOW_IMPORT_SEQUENCE).replace( + 'gate_sync_change\nrestore_sync_setup\ncheck_sync_scope\ngit --literal-pathspecs commit --only', + mutate + 'gate_sync_change\nrestore_sync_setup\ncheck_sync_scope\ngit --literal-pathspecs commit --only'); + assert.ok(lateShell.includes(mutate), "late injection must execute before the commit gate"); + const result = await executeWorkflow(lateShell, root, { ...env, + PM_GITHUB_API_BASE: process.env.PM_GITHUB_API_BASE, LATE_PATH: filename, + LATE_CONTENT: mode === "credential" || mode === "tracker-secret" ? GH_TOKEN : "changed\n" }); + assert.notEqual(result.code, 0, `${mode}: ${result.stdout}\n${result.stderr}`); + if (mode === "tracker-secret") assert.match(result.stderr, /github-token-classic/); + else assert.ok(result.stdout.includes(filename), result.stdout); + assert.deepEqual(remoteRefs(bare), ["refs/heads/main"]); + }); + } finally { fs.rmSync(base, { recursive: true, force: true }); } + } +}); + +test("restricted commit excludes unrelated staged content even when scope checks are bypassed", async () => { + const { root, base, bare, git } = initSyncRepo(); + try { + const env = await prepareWorkflowFixture(root, base); + assert.equal(git(["switch", "-c", SYNC_BRANCH]).status, 0); + const handle = githubHandler([issue(1, "Public issue", "Reviewed public body.")]); + await withMockGithub((req, res, url, server) => { + if (req.url === "/repos/acme/widgets") jsonResponse(res, 200, { private: false }); + else handle(req, res, url, server); + }, async () => { + const shell = workflowShell(WORKFLOW_IMPORT_SEQUENCE).replace(/^\s*check_sync_scope$/gm, "") + .replace('source "${RUNNER_TEMP}/pm-github-sync-scope.sh"\ngate_sync_change', + 'node -e \'require("node:fs").writeFileSync("extra.txt", "unscanned")\'\ngit add -- extra.txt\nsource "${RUNNER_TEMP}/pm-github-sync-scope.sh"\ngate_sync_change'); + const result = await executeWorkflow(shell, root, { ...env, PM_GITHUB_API_BASE: process.env.PM_GITHUB_API_BASE }); + assert.equal(result.code, 0, result.stderr + "\n" + result.stdout); + assert.ok(remoteRefs(bare).includes("refs/heads/" + SYNC_BRANCH), "scope-only revert allows publication"); + assert.equal(spawnSync("git", ["--git-dir", bare, "cat-file", "-e", `${SYNC_BRANCH}:extra.txt`]).status, 128, + "explicit commit pathspec still prevents publication of the unrelated file"); + assert.equal(git(["diff", "--cached", "--name-only", "--", "extra.txt"]).stdout.trim(), "extra.txt"); + }); + } finally { fs.rmSync(base, { recursive: true, force: true }); } +}); + +test("post-import script can be overridden or disabled and defaults tolerate an absent script", async () => { + const { root, base } = initSyncRepo(); + try { + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ scripts: { custom: "node custom.cjs" } })); + fs.writeFileSync(path.join(root, "custom.cjs"), 'require("node:fs").writeFileSync("CHANGELOG.md", "custom");\n'); + const shell = workflowShell(["Run caller post-import script"]); + for (const script of ["changelog:full", "", "custom"]) { + const result = await executeWorkflow(shell, root, { ...process.env, POST_IMPORT_SCRIPT: script }); + assert.equal(result.code, 0, result.stderr); + assert.equal(fs.existsSync(path.join(root, "CHANGELOG.md")), script === "custom"); + } + assert.equal(fs.readFileSync(path.join(root, "CHANGELOG.md"), "utf8"), "custom"); + } finally { fs.rmSync(base, { recursive: true, force: true }); } +}); + +test("dispatch handles disabled, invalid, failed, and unpublished caller workflows", async () => { + const { root, base, git } = initSyncRepo(); + try { + const env = await prepareWorkflowFixture(root, base); + fs.mkdirSync(path.join(root, ".github/workflows"), { recursive: true }); + fs.writeFileSync(path.join(root, ".github/workflows/ci.yml"), "on: workflow_dispatch\n"); + assert.equal(git(["switch", "-c", SYNC_BRANCH]).status, 0); + assert.equal(git(["push", "-q", "origin", SYNC_BRANCH]).status, 0); + fs.writeFileSync(env.REVIEW_RECEIPT!, "Existing review"); + const shell = workflowShell(["Dispatch caller CI and report parked runs"]); + for (const [overrides, expected] of [ + [{ SYNC_PUBLISHED: "false", CI_WORKFLOWS: "ci.yml" }, 0], + [{ SYNC_PUBLISHED: "true", CI_WORKFLOWS: "" }, 0], + [{ SYNC_PUBLISHED: "true", CI_WORKFLOWS: "../ci.yml" }, 1], + [{ SYNC_PUBLISHED: "true", CI_WORKFLOWS: "ci.yml", DISPATCH_STATUS: "23" }, 23], + ] as const) { + const result = await executeWorkflow(shell, root, { ...env, ...overrides }); + assert.equal(result.code, expected, result.stderr); + if (expected !== 23) assert.ok(!fs.existsSync(env.REVIEW_ACTIONS! + ".dispatch")); + } + } finally { fs.rmSync(base, { recursive: true, force: true }); } +});