diff --git a/.agents/pm/chores/pm-github-drwr.toon b/.agents/pm/chores/pm-github-drwr.toon new file mode 100644 index 0000000..91b6915 --- /dev/null +++ b/.agents/pm/chores/pm-github-drwr.toon @@ -0,0 +1,40 @@ +id: pm-github-drwr +title: Certify pm CLI 2026.9.10 and pick up the canonical auditor fixes the lockfile was holding back +description: "CI runs the repo-pinned binaries, so the pins decide what the gates exercise. Advance @unbrained/pm-cli from 2026.9.7 to 2026.9.10, the published npm latest, and pm-ops from 2026.9.7 to 2026.9.9. The pm-ops range already read as a caret, so this repository read as tracking the canonical publish-attestation auditor; it was not, because npm ci installs the lockfile and the lockfile pinned 2026.9.7. Measured with the 38-case bypass corpus, this repository admitted seven fail-open constructions at the locked version and is clean at 2026.9.9, with the launcher unchanged." +type: Chore +status: closed +priority: 2 +tags: [] +created_at: "2026-09-10T21:53:08.100Z" +updated_at: "2026-09-10T22:28:34.162Z" +closed_at: "2026-09-10T22:28:34.149Z" +completed_at: "2026-09-10T22:28:34.149Z" +claim_principal: claude-orchestrator +author: claude-orchestrator +files[3]{path,scope}: + package-lock.json,project + package.json,project + test/verify-release-publish-attestation.test.ts,project +tests[2]: + - command: npm test + scope: project + timeout_seconds: 2400 + provenance: + author: claude-orchestrator + created_at: "2026-09-10T21:53:10.554Z" + source_kind: local_mutation + source_ref: chore/certify-pm-cli-2026-9-10 + assert_stdout_contains[1]: pass 299 + note: Full suite under CLI 2026.9.10 and pm-ops 2026.9.9; the count assertion fails if the suite shrinks + - command: node --test test/verify-release-publish-attestation.test.ts + scope: project + timeout_seconds: 900 + provenance: + author: claude-orchestrator + created_at: "2026-09-10T22:28:33.156Z" + source_kind: local_mutation + source_ref: chore/certify-pm-cli-2026-9-10 + assert_stdout_contains[1]: the installed auditor refuses every known fail-open provenance handoff + note: "Consumer-level fail-open regression guard; verified RED against pm-ops 2026.9.7 and green at 2026.9.9, with the positive control green in both" +close_reason: "Pins advanced: @unbrained/pm-cli 2026.9.7 to 2026.9.10, pm-ops 2026.9.7 to 2026.9.9. Greptile and CodeRabbit both observed that the bump closed seven fail-open cases without leaving anything in this repository that would notice if they returned, and that the clean-corpus claim had no structured test behind it. Both are correct. Added two tests to test/verify-release-publish-attestation.test.ts: the seven constructions are run through auditPublishAttestation from the installed pm-ops and each must be refused, plus a positive control asserting a readable --provenance binding still passes. The guard asserts behaviour rather than a version number, so it also fails against a regressed release numbered above any floor. Revert-check: with pm-ops downgraded to 2026.9.7 the guard goes RED naming the nonliteral-overwrite construction while the control stays green; restoring 2026.9.9 returns the suite to 301 of 301. The 38-case corpus itself stays in the private companion because it is fleet infrastructure, so the linked test names the local guard, which is the part that is runnable here." +body: "" diff --git a/.agents/pm/history/pm-github-drwr.jsonl b/.agents/pm/history/pm-github-drwr.jsonl new file mode 100644 index 0000000..8a13df9 --- /dev/null +++ b/.agents/pm/history/pm-github-drwr.jsonl @@ -0,0 +1,9 @@ +{"ts":"2026-09-10T21:53:08.100Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-drwr"},{"op":"add","path":"/metadata/title","value":"Certify pm CLI 2026.9.10 and pick up the canonical auditor fixes the lockfile was holding back"},{"op":"add","path":"/metadata/description","value":"CI runs the repo-pinned binaries, so the pins decide what the gates exercise. Advance @unbrained/pm-cli from 2026.9.7 to 2026.9.10, the published npm latest, and pm-ops from 2026.9.7 to 2026.9.9. The pm-ops range already read as a caret, so this repository read as tracking the canonical publish-attestation auditor; it was not, because npm ci installs the lockfile and the lockfile pinned 2026.9.7. Measured with the 38-case bypass corpus, this repository admitted seven fail-open constructions at the locked version and is clean at 2026.9.9, with the launcher unchanged."},{"op":"add","path":"/metadata/type","value":"Chore"},{"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-09-10T21:53:08.100Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-09-10T21:53:08.100Z"},{"op":"add","path":"/metadata/author","value":"claude-orchestrator"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"9c351753f800eb019871e3af0fc6b5944c3c1c495b47ac49a67468bec437f248","item_hash_version":3,"message":"","event_class":"substantive","record_hash_version":1,"record_hash":"0c58cc9fd47b8e8154149e561fa938938abecfa24fdb16114941d4176d0cda21"} +{"ts":"2026-09-10T21:53:09.033Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T21:53:09.033Z"},{"op":"add","path":"/metadata/assignee","value":"claude-orchestrator"},{"op":"add","path":"/metadata/claim_principal","value":"claude-orchestrator"}],"before_hash":"9c351753f800eb019871e3af0fc6b5944c3c1c495b47ac49a67468bec437f248","after_hash":"9212d4ff317b70a692a38609f23b620b30cbd4474bdd27b21e70a3e479250906","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"607336b02abca37dd8ba126bb148fb2dd1a8b0a5eca7811554d4b46144584c8f"} +{"ts":"2026-09-10T21:53:09.793Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T21:53:09.793Z"},{"op":"add","path":"/metadata/files","value":[{"path":"package-lock.json","scope":"project"},{"path":"package.json","scope":"project"}]}],"before_hash":"9212d4ff317b70a692a38609f23b620b30cbd4474bdd27b21e70a3e479250906","after_hash":"8ab3ef4439cc71fca90eda801d175b89eab6e4a4623a62b9bfc4d7ad8f6b6e83","item_hash_version":3,"event_class":"maintenance","record_hash_version":1,"record_hash":"d822964c8afc707a0ccbd43bad73d73ec866eda274149ef98b280ce3667305b6"} +{"ts":"2026-09-10T21:53:10.580Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T21:53:10.580Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"npm test","scope":"project","timeout_seconds":2400,"provenance":{"author":"claude-orchestrator","created_at":"2026-09-10T21:53:10.554Z","source_kind":"local_mutation","source_ref":"chore/certify-pm-cli-2026-9-10"},"assert_stdout_contains":["pass 299"],"note":"Full suite under CLI 2026.9.10 and pm-ops 2026.9.9; the count assertion fails if the suite shrinks"}]}],"before_hash":"8ab3ef4439cc71fca90eda801d175b89eab6e4a4623a62b9bfc4d7ad8f6b6e83","after_hash":"c40a6b4fd81e080f35241436f737cce8a44928793389ba02906c03514d1f299f","item_hash_version":3,"event_class":"maintenance","record_hash_version":1,"record_hash":"3ebeab1d32078cd40397c1488ae6edd710c0bfbfe5f0b9d05ef70e8669336d03"} +{"ts":"2026-09-10T21:53:11.365Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T21:53:11.365Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-09-10T21:53:11.331Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-09-10T21:53:11.331Z"},{"op":"add","path":"/metadata/close_reason","value":"Pins advanced: @unbrained/pm-cli 2026.9.7 to 2026.9.10, pm-ops 2026.9.7 to 2026.9.9. npm test passes 299 of 299 with zero failures. The publish-attestation bypass corpus reports clean for this repository after the bump, against seven admitted cases before it, with scripts/verify-release-publish-attestation.ts unchanged."}],"before_hash":"c40a6b4fd81e080f35241436f737cce8a44928793389ba02906c03514d1f299f","after_hash":"c7c543a62bd4517b640d7e16b8c71009bbb26d550347fc43afb585ca629de529","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"e623811374e0d4bf274f50834f2254356f70372f60253d5cab8362ddd2f65f10"} +{"ts":"2026-09-10T22:28:32.686Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"reopen","patch":[{"op":"remove","path":"/metadata/close_reason"},{"op":"remove","path":"/metadata/completed_at"},{"op":"remove","path":"/metadata/closed_at"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T22:28:32.686Z"},{"op":"replace","path":"/metadata/status","value":"open"}],"before_hash":"c7c543a62bd4517b640d7e16b8c71009bbb26d550347fc43afb585ca629de529","after_hash":"e8062a1c86821b82191043c9ebe7c7dcc617d261e8033bd8a0e8b365dd35a77d","item_hash_version":3,"context":{"recurrence":{"reason":"Bot review found the bump shipped without a regression guard; reopening to add one before this lands.","from_status":"closed","to_status":"open","previous_terminal":{"close_reason":"Pins advanced: @unbrained/pm-cli 2026.9.7 to 2026.9.10, pm-ops 2026.9.7 to 2026.9.9. npm test passes 299 of 299 with zero failures. The publish-attestation bypass corpus reports clean for this repository after the bump, against seven admitted cases before it, with scripts/verify-release-publish-attestation.ts unchanged."}}},"event_class":"substantive","record_hash_version":1,"record_hash":"484c389e111faf794a6d2106477b9afccaffd550b89cf05387e49e325533bb8c"} +{"ts":"2026-09-10T22:28:33.181Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"node --test test/verify-release-publish-attestation.test.ts","scope":"project","timeout_seconds":900,"provenance":{"author":"claude-orchestrator","created_at":"2026-09-10T22:28:33.156Z","source_kind":"local_mutation","source_ref":"chore/certify-pm-cli-2026-9-10"},"assert_stdout_contains":["the installed auditor refuses every known fail-open provenance handoff"],"note":"Consumer-level fail-open regression guard; verified RED against pm-ops 2026.9.7 and green at 2026.9.9, with the positive control green in both"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T22:28:33.181Z"}],"before_hash":"e8062a1c86821b82191043c9ebe7c7dcc617d261e8033bd8a0e8b365dd35a77d","after_hash":"54a76130899fc5605585d0e4b6b020d45c246adf35de3f0b548344eff29f20d2","item_hash_version":3,"event_class":"maintenance","record_hash_version":1,"record_hash":"c5ab84b46cc243af269c50cb028e79c4d3f3a46ace7f148c8d9a175ed6d7e745"} +{"ts":"2026-09-10T22:28:33.664Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/2","value":{"path":"test/verify-release-publish-attestation.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T22:28:33.664Z"}],"before_hash":"54a76130899fc5605585d0e4b6b020d45c246adf35de3f0b548344eff29f20d2","after_hash":"06e1de515b4e4831dda1778bc7077b2e57a598751ff3623707b525b490f33856","item_hash_version":3,"event_class":"maintenance","record_hash_version":1,"record_hash":"ba4dce548095967e2cf11cbf6d18e02895bfe85c61a0ebce6d4f8467ce5e1c1d"} +{"ts":"2026-09-10T22:28:34.162Z","author":"claude-orchestrator","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5","agent_model_source":"probe","agent_instance":"3a51a1fe5204a56374236921","agent_provenance":{"model":{"value":"claude-opus-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.268","source":"probe"}},"op":"close","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-10T22:28:34.162Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-09-10T22:28:34.149Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-09-10T22:28:34.149Z"},{"op":"add","path":"/metadata/close_reason","value":"Pins advanced: @unbrained/pm-cli 2026.9.7 to 2026.9.10, pm-ops 2026.9.7 to 2026.9.9. Greptile and CodeRabbit both observed that the bump closed seven fail-open cases without leaving anything in this repository that would notice if they returned, and that the clean-corpus claim had no structured test behind it. Both are correct. Added two tests to test/verify-release-publish-attestation.test.ts: the seven constructions are run through auditPublishAttestation from the installed pm-ops and each must be refused, plus a positive control asserting a readable --provenance binding still passes. The guard asserts behaviour rather than a version number, so it also fails against a regressed release numbered above any floor. Revert-check: with pm-ops downgraded to 2026.9.7 the guard goes RED naming the nonliteral-overwrite construction while the control stays green; restoring 2026.9.9 returns the suite to 301 of 301. The 38-case corpus itself stays in the private companion because it is fleet infrastructure, so the linked test names the local guard, which is the part that is runnable here."}],"before_hash":"06e1de515b4e4831dda1778bc7077b2e57a598751ff3623707b525b490f33856","after_hash":"f0fb1b082c1b9d300924aa3a403f683b5ad599d37440f18dae7e0bfcad6002e9","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"5ff89de3912576fe6c4f91e23968d7c5fc1c4753ecc9f022675e0eeaae50b0af"} diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a584a..ffd1109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Other + +- Certify pm CLI 2026.9.10 and pick up the canonical auditor fixes the lockfile was holding back ([pm-github-drwr](https://github.com/unbraind/pm-github/blob/main/.agents/pm/chores/pm-github-drwr.toon)) + ## 2026.9.8 - 2026-09-08 ### Security diff --git a/package-lock.json b/package-lock.json index 38b8d1b..87fbeaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,9 @@ "license": "MIT", "devDependencies": { "@types/node": "^26.1.1", - "@unbrained/pm-cli": "2026.9.7", + "@unbrained/pm-cli": "2026.9.10", "pm-changelog": "2026.9.6", - "pm-ops": "^2026.9.7", + "pm-ops": "^2026.9.9", "typescript": "^7.0.2" }, "engines": { @@ -669,9 +669,9 @@ } }, "node_modules/@unbrained/pm-cli": { - "version": "2026.9.7", - "resolved": "https://registry.npmjs.org/@unbrained/pm-cli/-/pm-cli-2026.9.7.tgz", - "integrity": "sha512-EbyrHyYVHqk2qyhRNPlu892npbSR7Sgd2C65rIacWKVdDuIdDLv6GqkTq9O23u4zoGwhgvK1mAYCvlpE8skSvw==", + "version": "2026.9.10", + "resolved": "https://registry.npmjs.org/@unbrained/pm-cli/-/pm-cli-2026.9.10.tgz", + "integrity": "sha512-MZQor/cbmacHsJ9mg5Roj6VHa8kz9aD9poi9zMyU/k3Y8k5fAgSGl/oupIoW4n5IWfNO4MLmIECaCcI5JWSgxg==", "dev": true, "license": "MIT", "dependencies": { @@ -998,9 +998,9 @@ } }, "node_modules/pm-ops": { - "version": "2026.9.7", - "resolved": "https://registry.npmjs.org/pm-ops/-/pm-ops-2026.9.7.tgz", - "integrity": "sha512-fU+j8c/r4zmo4WMJBrifeZQxEUwtQPoQe1jLCgh2/MXQog0eVJEChRFgCv4zwNWEcSVFBz7vr5AMUrv0jov7SA==", + "version": "2026.9.9", + "resolved": "https://registry.npmjs.org/pm-ops/-/pm-ops-2026.9.9.tgz", + "integrity": "sha512-OY6Ces3qRDCO5wGOsJgYo4Tqu5ykSMwwDfvBKUeJEJCp9QbDPzZxw5uHqERTidVLzHeYQTgzpJFKj694VuzcuQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 699e109..d1f2dae 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,9 @@ }, "devDependencies": { "@types/node": "^26.1.1", - "@unbrained/pm-cli": "2026.9.7", + "@unbrained/pm-cli": "2026.9.10", "pm-changelog": "2026.9.6", - "pm-ops": "^2026.9.7", + "pm-ops": "^2026.9.9", "typescript": "^7.0.2" }, "keywords": [ diff --git a/test/verify-release-publish-attestation.test.ts b/test/verify-release-publish-attestation.test.ts index 7582fc2..9068921 100644 --- a/test/verify-release-publish-attestation.test.ts +++ b/test/verify-release-publish-attestation.test.ts @@ -74,6 +74,55 @@ test("the resolved gate still refuses an unattested publish", () => { assert.deepEqual(attested.recognition, { kind: "recognized", count: 1 }); }); +/** + * Shell constructions that hand `npm publish` an unreadable `--provenance` + * binding, each of which a fail-open auditor accepts as attested. + * + * These seven were admitted by the canonical auditor at pm-ops 2026.9.7 — the + * version this repository's lockfile installed until 2026.9.9 — and are refused + * from 2026.9.9 on. They are reproduced here, at consumer level, because a + * dependency bump is not a guard: nothing in this repository failed while it was + * installing an auditor that accepted all seven, and nothing would fail again if + * a later lockfile change put one back. The property asserted is behavioural — + * *this* construction is refused by whichever auditor is installed — not a + * version comparison, which would still pass against a regressed release + * numbered above any floor. + */ +const FAIL_OPEN_CONSTRUCTIONS: ReadonlyArray<{ id: string; script: string }> = [ + { id: "nonliteral-overwrite", script: "FLAG=--provenance\nFLAG=$OTHER\nnpm publish $FLAG --access public\n" }, + { id: "nonliteral-overwrite-cmdsub", script: "FLAG=--provenance\nFLAG=$(cat /tmp/x)\nnpm publish $FLAG --access public\n" }, + { id: "quoted-metachar-value", script: "FLAG=\"--provenance;\"\nnpm publish $FLAG --access public\n" }, + { id: "single-quoted-metachar-value", script: "FLAG='--provenance;'\nnpm publish $FLAG --access public\n" }, + { id: "multiword-unreadable-tail", script: "FLAG=--provenance\nNOOP=x FLAG=$(true)\nnpm publish $FLAG --access public\n" }, + { id: "quoted-paren-in-substitution", script: "FLAG=--provenance\nFLAG=$(printf ') ' )\nnpm publish $FLAG --access public\n" }, + { id: "unterminated-substitution", script: "FLAG=--provenance\nFLAG=$(unterminated\nnpm publish $FLAG --access public\n" }, +]; + +test("the installed auditor refuses every known fail-open provenance handoff", () => { + for (const { id, script } of FAIL_OPEN_CONSTRUCTIONS) { + const audited = auditPublishAttestation([ + { file: `.github/case-${id}.sh`, text: `#!/usr/bin/env bash\n${script}` }, + ]); + assert.ok( + audited.failures.length > 0, + `the installed pm-ops auditor accepted the ${id} construction as attested; ` + + "an unattested publish would reach the registry", + ); + } +}); + +test("the fail-open guard still admits a genuinely attested publish", () => { + // Without this control the guard above passes against an auditor that refuses + // everything, which would be just as broken and far easier to ship. + const audited = auditPublishAttestation([ + { + file: ".github/case-control.sh", + text: "#!/usr/bin/env bash\nFLAG=--provenance\nnpm publish $FLAG --access public\n", + }, + ]); + assert.deepEqual(audited.failures, [], "a readable --provenance binding must still pass"); +}); + test("this repository's own workflows pass the gate", () => { // The gate pointed at this checkout, which is what CI runs. Reported through // captured streams rather than the process ones so a failure is readable.