From 7368b9905f7698d33719c5271ebeab5578b6e3bd Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Mon, 19 Oct 2026 23:28:10 +0300 Subject: [PATCH 1/2] ci: qualify the pinned rustup image metadata --- .../ci1-cli-image-pin-20261019.md | 26 ++++++++++++++++ internal/docs/ci1-qualified-pool.json | 31 +++++++++++++------ internal/docs/ci1-runners.md | 19 +++++++++--- scripts/ci1_image_test.py | 2 +- scripts/verify-ci1-image.py | 2 +- 5 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 internal/agent-sessions/ci1-cli-image-pin-20261019.md diff --git a/internal/agent-sessions/ci1-cli-image-pin-20261019.md b/internal/agent-sessions/ci1-cli-image-pin-20261019.md new file mode 100644 index 0000000..b1c2550 --- /dev/null +++ b/internal/agent-sessions/ci1-cli-image-pin-20261019.md @@ -0,0 +1,26 @@ +# CLI shared-image pin follow-up + +Collaborator: purpshell. Agent: Codex/gpt-6.1-sol, T3 thread +8ad9e447-6fd5-4fc5-8e96-189326af2c28. Manual visible-message and selected-outcome +summary only; no automatic transcript coverage claim. Excludes raw logs, +credentials, key attachments and hidden reasoning. Registration is inherited +from the coordinated monorepo record (PR 511), not newly requested here. + +The owner authorized the CI cutover, pausing new paid CI, and an exact-head +merge after clean review followed by a Region system alert only after dev CI +works. Public repositories remain GitHub-hosted; staging and PR 744 stay held. + +Dependency: monorepo PR 781 source 30d4c516b827098614cb64469f66028e7727f6b7. +The shared image pins rustup-init to its versioned 1.29.1 archive without +changing the installer bytes. Every other image input was checked unchanged. +The original image age was retained. Real reboot testing verified that both +worker profiles disable automatic last-state restoration before isolation. + +Candidate 4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d passed fresh-clone tools, browser, network and admission-refusal +checks. The old CLI pin rejected it and the updated pin accepted it. This +follow-up changes one expected tool-definition hash and its fixture, plus the +internal image/controller record. It adds no dual-hash acceptance, runtime, +package dependency, publication or product deployment change. + +Seven local admission tests and actionlint pass. GitHub source CI and the first +main push remain required. No automated review was requested. diff --git a/internal/docs/ci1-qualified-pool.json b/internal/docs/ci1-qualified-pool.json index 6fe9bcf..50dca4e 100644 --- a/internal/docs/ci1-qualified-pool.json +++ b/internal/docs/ci1-qualified-pool.json @@ -1,37 +1,50 @@ { "schema": 1, - "observed_at": "2026-10-19T19:00:23.666736+00:00", + "observed_at": "2026-10-19T20:26:49.954247+00:00", "controller": "ci-1", "scalesets": [ { "id": 1, "name": "ci1-general", - "image": "505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0", - "enabled": true + "image": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d", + "enabled": false, + "max_runners": 1, + "min_idle_runners": null }, { "id": 2, "name": "ci1-integration", - "image": "505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0", - "enabled": true + "image": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d", + "enabled": false, + "max_runners": 1, + "min_idle_runners": null } ], "incus_project": "ci1", "alias": { - "name": "505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0", - "target": "505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0" + "name": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d", + "target": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d" }, "authority": "GARM controller scale-set image pin and provider fingerprint-alias enforcement; guest manifest is a separate content and age check", - "mutation": "none; read-only verification", + "mutation": "Explicit coordinated pin after fresh-clone/network qualification; pools disabled at readback", "requalification": "Any new fingerprint needs explicit pool changes and the coordinated image/lifecycle qualification", "provider_wrapper": { "path": "/opt/garm/providers.d/ci1-incus", "sha256": "a091d8c0f8a08be521d754d046622b6fcc66f127c9bea52d60082f2a3d424813", - "matches_source": "https://github.com/polymorfa/polymorfa/blob/d35de858d73fe9c6d9e6110637a18a3dfe57fb56/infra/ci1/provider-admission.sh", + "matches_source": "https://github.com/polymorfa/polymorfa/blob/30d4c516b827098614cb64469f66028e7727f6b7/infra/ci1/provider-admission.sh", "checks": [ "local immutable fingerprint required", "fingerprint-named alias must target that exact fingerprint", "remote names rejected" ] + }, + "image_qualification": { + "source": "30d4c516b827098614cb64469f66028e7727f6b7", + "parent": "505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0", + "built_at_preserved": "2026-10-19T14:37:51.169664+00:00", + "tools_lock_sha256": "88d3c9a0e804935c1c7b60ac60d249512309405e150d654de8d896ce68f74f47", + "cli_old_pin": "rejected", + "cli_new_pin": "accepted", + "github": "pending on this PR" } } diff --git a/internal/docs/ci1-runners.md b/internal/docs/ci1-runners.md index c82f7b2..df61014 100644 --- a/internal/docs/ci1-runners.md +++ b/internal/docs/ci1-runners.md @@ -18,7 +18,7 @@ always uses ci-1. No source-test fallback exists. The new file identity lets this PR qualify while old paid workflow 338004193 stays disabled. Do not enable the old identity before inspecting its source; -the default branch still selects Blacksmith until the cutover merges. +the old source was deleted by PR 84 and was never re-enabled. Before installation, the job validates the shared image's base fingerprint, machine/tool input hashes, age of at most seven days, architecture and CPU shape. @@ -28,7 +28,7 @@ The image's pnpm cache belongs to the monorepo; no CLI npm-cache hit is inferred A changed image definition requires new qualification and explicit pin updates. The qualified image fingerprint is -`505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0`. +`4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d`. The controller's scale set 2 (`ci1-integration`) pins this exact final fingerprint; scale set 1 does too. A live readback on 2026-10-19 confirmed both pins and the fingerprint-named local Incus alias targeting that same fingerprint. The installed @@ -39,7 +39,15 @@ aliases. Image identity is bound by this controller configuration; the guest manifest checks content and age. A new image needs explicit operator pool changes and full qualification under the coordinated plan. -Its definition source is `6481b9e199d96fb3488f34d6335ac41169f4fb5d` in the monorepo. +The metadata derivation source is `30d4c516b827098614cb64469f66028e7727f6b7` in +the monorepo. Its parent is `505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0`, originally built from `6481b9e`. +Only rustup download metadata changed. The versioned 1.29.1 archive and installed +initializer have identical SHA-256; no tool upgrade occurred. The derivative +retains the original `built_at` of 2026-10-19T14:37:51.169664+00:00. Fresh-clone +browser/tool and network probes passed, input-refusal tests restored their +changes, and the old CLI gate refused the candidate while this pin accepted it. +Both pools were disabled at the controller readback; actual GitHub qualification +is still required. The underlying Ubuntu base is `2139edd9da4026da8a66c0557af7eee4789101104eb27860bacd57086219cebb`. Public source dependencies only are installed; no customer credential, release @@ -56,8 +64,9 @@ remain unchanged. The unchanged local `npm run verify` passed all 110 test files and 1,051 tests, types, lint, formatting, build, package dry-run and production audit (zero production vulnerabilities). Three release-evidence tests also passed. -Local workflow lint and diff checks pass. GitHub source qualification and the -first main-branch run remain required. Host lifecycle proof is separate from CLI +Local workflow lint and diff checks pass. PR 84 and its first main push passed +on the parent image. This candidate needs its own GitHub source qualification +and first main-branch run. Host lifecycle proof is separate from CLI source acceptance. The publication workflow is unchanged. It runs on its schedule or manual diff --git a/scripts/ci1_image_test.py b/scripts/ci1_image_test.py index 8204c79..fa54fa7 100644 --- a/scripts/ci1_image_test.py +++ b/scripts/ci1_image_test.py @@ -23,7 +23,7 @@ def setUp(self): "base_fingerprint": "2139edd9da4026da8a66c0557af7eee4789101104eb27860bacd57086219cebb", "inputs": { "infra/ci1/machine.json": "0900619bac06c6a81f5d7535107985bf8d9cf11c6dd2079273d8ab81d9ebac3a", - "infra/ci1/tools.lock.json": "4c2a7cac77ebd8221054b2e78dfdd81e15327fb92d3e80d8986a73fb5e326d15", + "infra/ci1/tools.lock.json": "88d3c9a0e804935c1c7b60ac60d249512309405e150d654de8d896ce68f74f47", }, } diff --git a/scripts/verify-ci1-image.py b/scripts/verify-ci1-image.py index 6891d8f..2ea3c20 100644 --- a/scripts/verify-ci1-image.py +++ b/scripts/verify-ci1-image.py @@ -9,7 +9,7 @@ BASE = "2139edd9da4026da8a66c0557af7eee4789101104eb27860bacd57086219cebb" INPUTS = { "infra/ci1/machine.json": "0900619bac06c6a81f5d7535107985bf8d9cf11c6dd2079273d8ab81d9ebac3a", - "infra/ci1/tools.lock.json": "4c2a7cac77ebd8221054b2e78dfdd81e15327fb92d3e80d8986a73fb5e326d15", + "infra/ci1/tools.lock.json": "88d3c9a0e804935c1c7b60ac60d249512309405e150d654de8d896ce68f74f47", } From 4041af144e2429865434e61bf35a2ae6dd61112b Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Mon, 19 Oct 2026 23:34:48 +0300 Subject: [PATCH 2/2] ci: use GitHub-hosted runners for the public CLI --- .github/actionlint.yaml | 3 - .github/workflows/ci-hosted.yml | 32 +++++++ .github/workflows/ci1.yml | 49 ---------- internal/agent-sessions/ci1-cli-20261019.md | 11 +++ .../ci1-cli-image-pin-20261019.md | 26 ----- internal/docs/ci-hosted.md | 24 +++++ internal/docs/ci1-qualified-pool.json | 50 ---------- internal/docs/ci1-runners.md | 95 ------------------- scripts/ci1_image_test.py | 89 ----------------- scripts/verify-ci1-image.py | 36 ------- 10 files changed, 67 insertions(+), 348 deletions(-) delete mode 100644 .github/actionlint.yaml create mode 100644 .github/workflows/ci-hosted.yml delete mode 100644 .github/workflows/ci1.yml delete mode 100644 internal/agent-sessions/ci1-cli-image-pin-20261019.md create mode 100644 internal/docs/ci-hosted.md delete mode 100644 internal/docs/ci1-qualified-pool.json delete mode 100644 internal/docs/ci1-runners.md delete mode 100644 scripts/ci1_image_test.py delete mode 100644 scripts/verify-ci1-image.py diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml deleted file mode 100644 index 2c0bbea..0000000 --- a/.github/actionlint.yaml +++ /dev/null @@ -1,3 +0,0 @@ -self-hosted-runner: - labels: - - ci1-integration diff --git a/.github/workflows/ci-hosted.yml b/.github/workflows/ci-hosted.yml new file mode 100644 index 0000000..d640123 --- /dev/null +++ b/.github/workflows/ci-hosted.yml @@ -0,0 +1,32 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + verify: + name: Verify Node ${{ matrix.node-version }} + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + node-version: + - 22 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: ${{ matrix.node-version }} + cache: npm + - run: npm ci + - run: npm run verify + - run: node --test scripts/release-evidence.test.mjs diff --git a/.github/workflows/ci1.yml b/.github/workflows/ci1.yml deleted file mode 100644 index c36e6c6..0000000 --- a/.github/workflows/ci1.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: - - main - -permissions: - contents: read - -jobs: - verify: - name: Verify Node ${{ matrix.node-version }} - runs-on: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'ubuntu-latest' || 'ci1-integration' }} - timeout-minutes: 20 - strategy: - fail-fast: false - matrix: - node-version: - - 22 - steps: - - name: Require trusted source - env: - EVENT_NAME: ${{ github.event_name }} - HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} - REPOSITORY: ${{ github.repository }} - run: | - set -euo pipefail - if [[ "$EVENT_NAME" == pull_request && "$HEAD_REPOSITORY" != "$REPOSITORY" ]]; then - echo 'Fork PRs require a separately qualified runner policy' >&2 - exit 1 - fi - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Require qualified ci-1 image - run: | - python3 scripts/verify-ci1-image.py - sha256sum --check --strict /etc/ci1-image/manifests.sha256 - - name: Test image admission refusals - run: python3 -m unittest discover -s scripts -p ci1_image_test.py -v - - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version: ${{ matrix.node-version }} - cache: npm - - run: npm ci - - run: npm run verify - - run: node --test scripts/release-evidence.test.mjs diff --git a/internal/agent-sessions/ci1-cli-20261019.md b/internal/agent-sessions/ci1-cli-20261019.md index 7416451..aba063e 100644 --- a/internal/agent-sessions/ci1-cli-20261019.md +++ b/internal/agent-sessions/ci1-cli-20261019.md @@ -49,3 +49,14 @@ fingerprint-named Incus alias pointing to itself. The provider rejects remote names and retargeted aliases. The new operator receipt documents that authority; the guest's manifest is a content/age check rather than image attestation. No pool, image, host limit, product secret, deployment or runtime changed. + +## Public repository policy + +On 2026-10-19 the owner confirmed that CLI and SDK repositories are public and +made polymorfa/cli public. Live API readback confirmed CLI and SDK visibility. +The CLI CI destination is therefore standard GitHub-hosted Ubuntu for both +pull requests and main pushes. The ci-1 image pin, admission helper and +controller receipt were removed from the active source. The full Node 22 +verification and release-evidence commands are preserved, with a read-only +job token and no persisted checkout credentials. This section supersedes the +private-repository CI routing described above; prior results remain historical. diff --git a/internal/agent-sessions/ci1-cli-image-pin-20261019.md b/internal/agent-sessions/ci1-cli-image-pin-20261019.md deleted file mode 100644 index b1c2550..0000000 --- a/internal/agent-sessions/ci1-cli-image-pin-20261019.md +++ /dev/null @@ -1,26 +0,0 @@ -# CLI shared-image pin follow-up - -Collaborator: purpshell. Agent: Codex/gpt-6.1-sol, T3 thread -8ad9e447-6fd5-4fc5-8e96-189326af2c28. Manual visible-message and selected-outcome -summary only; no automatic transcript coverage claim. Excludes raw logs, -credentials, key attachments and hidden reasoning. Registration is inherited -from the coordinated monorepo record (PR 511), not newly requested here. - -The owner authorized the CI cutover, pausing new paid CI, and an exact-head -merge after clean review followed by a Region system alert only after dev CI -works. Public repositories remain GitHub-hosted; staging and PR 744 stay held. - -Dependency: monorepo PR 781 source 30d4c516b827098614cb64469f66028e7727f6b7. -The shared image pins rustup-init to its versioned 1.29.1 archive without -changing the installer bytes. Every other image input was checked unchanged. -The original image age was retained. Real reboot testing verified that both -worker profiles disable automatic last-state restoration before isolation. - -Candidate 4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d passed fresh-clone tools, browser, network and admission-refusal -checks. The old CLI pin rejected it and the updated pin accepted it. This -follow-up changes one expected tool-definition hash and its fixture, plus the -internal image/controller record. It adds no dual-hash acceptance, runtime, -package dependency, publication or product deployment change. - -Seven local admission tests and actionlint pass. GitHub source CI and the first -main push remain required. No automated review was requested. diff --git a/internal/docs/ci-hosted.md b/internal/docs/ci-hosted.md new file mode 100644 index 0000000..f11ea29 --- /dev/null +++ b/internal/docs/ci-hosted.md @@ -0,0 +1,24 @@ +# Public CLI CI + +The owner made polymorfa/cli public on 2026-10-19. CLI and SDK repositories use +standard GitHub-hosted runners. They are excluded from ci-1, whose runner group +has public-repository access disabled. + +`.github/workflows/ci-hosted.yml` preserves the CI display name, Verify Node 22 +check and all existing source commands: `npm ci`, `npm run verify`, and the three +release-evidence tests. It runs on pull requests, including forks, and main +pushes using `ubuntu-latest`, read-only repository contents, no persisted +checkout credentials, npm caching and a 20-minute deadline. The workflow uses +`pull_request`, not a privileged target event, and declares no secrets. + +No ci-1 manifest, image pin, private tool cache or self-hosted runner is required. +New workflow identity allows source qualification without enabling an old +Blacksmith workflow. Existing source checks, production audit, build and package +dry-run are preserved. Actual GitHub qualification and the first main push +remain required before calling this cutover verified. + +CLI commands, SDK dependency, API contracts, package and lockfile, public product +docs/changelog, product flags and Admin controls are unaffected: this changes +only CI routing. The publication workflow is unchanged, schedule/manual only, +and retains matching SDK/API acceptance. No package publication or product +deployment is invoked. Staging and regional rename #744 remain held. diff --git a/internal/docs/ci1-qualified-pool.json b/internal/docs/ci1-qualified-pool.json deleted file mode 100644 index 50dca4e..0000000 --- a/internal/docs/ci1-qualified-pool.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "schema": 1, - "observed_at": "2026-10-19T20:26:49.954247+00:00", - "controller": "ci-1", - "scalesets": [ - { - "id": 1, - "name": "ci1-general", - "image": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d", - "enabled": false, - "max_runners": 1, - "min_idle_runners": null - }, - { - "id": 2, - "name": "ci1-integration", - "image": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d", - "enabled": false, - "max_runners": 1, - "min_idle_runners": null - } - ], - "incus_project": "ci1", - "alias": { - "name": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d", - "target": "4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d" - }, - "authority": "GARM controller scale-set image pin and provider fingerprint-alias enforcement; guest manifest is a separate content and age check", - "mutation": "Explicit coordinated pin after fresh-clone/network qualification; pools disabled at readback", - "requalification": "Any new fingerprint needs explicit pool changes and the coordinated image/lifecycle qualification", - "provider_wrapper": { - "path": "/opt/garm/providers.d/ci1-incus", - "sha256": "a091d8c0f8a08be521d754d046622b6fcc66f127c9bea52d60082f2a3d424813", - "matches_source": "https://github.com/polymorfa/polymorfa/blob/30d4c516b827098614cb64469f66028e7727f6b7/infra/ci1/provider-admission.sh", - "checks": [ - "local immutable fingerprint required", - "fingerprint-named alias must target that exact fingerprint", - "remote names rejected" - ] - }, - "image_qualification": { - "source": "30d4c516b827098614cb64469f66028e7727f6b7", - "parent": "505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0", - "built_at_preserved": "2026-10-19T14:37:51.169664+00:00", - "tools_lock_sha256": "88d3c9a0e804935c1c7b60ac60d249512309405e150d654de8d896ce68f74f47", - "cli_old_pin": "rejected", - "cli_new_pin": "accepted", - "github": "pending on this PR" - } -} diff --git a/internal/docs/ci1-runners.md b/internal/docs/ci1-runners.md deleted file mode 100644 index df61014..0000000 --- a/internal/docs/ci1-runners.md +++ /dev/null @@ -1,95 +0,0 @@ -# CLI checks on ci-1 - -The owner approved the GARM + Incus cutover and paused new paid CI runs. This -change qualifies the private CLI repository on disposable Linux workers. The -[coordinated plan](https://github.com/polymorfa/polymorfa/blob/d35de858d73fe9c6d9e6110637a18a3dfe57fb56/internal/docs/superpowers/plans/2026-10-19-ci1-runner-images-and-cutover.md) -owns the host, image, lifecycle proof and environment holds. - -## Workflow and image contract - -`ci1.yml` keeps the CI display name, Verify Node 22 check and all three existing -commands: `npm ci`, `npm run verify` and the release-evidence tests. It uses -`ci1-integration` with eight CPUs and 32 GiB RAM, a 20-minute job deadline, read-only -repository permission and no checkout credential persistence. The already qualified integration pool lets this CLI check run alongside the -monorepo general queue, within the unchanged host budget. Fork PRs run only a -failing guard on a GitHub-hosted runner, before checkout; the preserved Verify -Node 22 check fails instead of reporting a successful skip. Trusted source work -always uses ci-1. No source-test fallback exists. - -The new file identity lets this PR qualify while old paid workflow 338004193 -stays disabled. Do not enable the old identity before inspecting its source; -the old source was deleted by PR 84 and was never re-enabled. - -Before installation, the job validates the shared image's base fingerprint, -machine/tool input hashes, age of at most seven days, architecture and CPU shape. -It then verifies the image's checksum manifest with strict parsing; any malformed -line fails admission. Node setup selects cached Node 22. Existing npm cache behavior and the lockfile-based installation remain. -The image's pnpm cache belongs to the monorepo; no CLI npm-cache hit is inferred. -A changed image definition requires new qualification and explicit pin updates. - -The qualified image fingerprint is -`4c554d1f7cead7449c80848fd1f2405c996143b888a2a337e4ad42727de04e0d`. -The controller's scale set 2 (`ci1-integration`) pins this exact final fingerprint; -scale set 1 does too. A live readback on 2026-10-19 confirmed both pins and the -fingerprint-named local Incus alias targeting that same fingerprint. The installed -provider wrapper SHA-256 also matches its committed source. The -[readback receipt](ci1-qualified-pool.json) records the binding. The controller -provider refuses remote or non-fingerprint image names and rejects retargeted -aliases. Image identity is bound by this controller configuration; the guest -manifest checks content and age. A new image needs explicit operator pool -changes and full qualification under the coordinated plan. - -The metadata derivation source is `30d4c516b827098614cb64469f66028e7727f6b7` in -the monorepo. Its parent is `505d3336ea46318175907ac2192df5002406731bd2990a344ba2ee98c1c89ab0`, originally built from `6481b9e`. -Only rustup download metadata changed. The versioned 1.29.1 archive and installed -initializer have identical SHA-256; no tool upgrade occurred. The derivative -retains the original `built_at` of 2026-10-19T14:37:51.169664+00:00. Fresh-clone -browser/tool and network probes passed, input-refusal tests restored their -changes, and the old CLI gate refused the candidate while this pin accepted it. -Both pools were disabled at the controller readback; actual GitHub qualification -is still required. -The underlying Ubuntu base is -`2139edd9da4026da8a66c0557af7eee4789101104eb27860bacd57086219cebb`. -Public source dependencies only are installed; no customer credential, release -signing key or production secret is added to CI or the image. - -## Qualification and rollout - -Source dependency: CLI main `c21af71f13e68230769d1a2ca17985d143bf6c6e`. -Seven local admission tests cover the accepted image, wrong architecture/shape, -expiry/future clock, wrong base, changed/missing definition or tool hashes, -fork guard outcomes before checkout and malformed checksum rows. The two new -regressions failed before the fix and pass afterward; all source test commands -remain unchanged. -The unchanged local `npm run verify` passed all 110 test files and 1,051 tests, -types, lint, formatting, build, package dry-run and production audit (zero -production vulnerabilities). Three release-evidence tests also passed. -Local workflow lint and diff checks pass. PR 84 and its first main push passed -on the parent image. This candidate needs its own GitHub source qualification -and first main-branch run. Host lifecycle proof is separate from CLI -source acceptance. - -The publication workflow is unchanged. It runs on its schedule or manual -invocation, requires matching SDK/API acceptance and never runs merely because -this CI PR merges. This change does not authorize a package publication or -staging deployment. Staging and regional rename holds remain in force. - -On a bad image, drain/rebuild the worker pool under the monorepo runbook and -qualify it again. Do not silently re-enable paid CI. Reverting the source change -does not re-enable workflow 338004193; paid resumption needs an owner cost decision. - -## Impact and session provenance - -CI and operator documentation are updated. CLI commands, credential handling, -SDK dependency, API contracts, public docs/changelog, product release flags, -Admin controls, billing, Usage, logs, analytics and customer interfaces are -unaffected because no runtime, dependency or product configuration changes. -No customer feature or release flag is introduced. Linear is unavailable in this -session; no tracker synchronization is claimed. - -Collaborator purpshell; Codex/gpt-6.1-sol; T3 thread -`8ad9e447-6fd5-4fc5-8e96-189326af2c28`. The existing registration is -[monorepo PR 511](https://github.com/polymorfa/polymorfa/pull/511). -The [sanitized manual checkpoint](../agent-sessions/ci1-cli-20261019.md) records -visible instructions and tool outcomes, with capture gaps. It provides -provenance, not GitHub acceptance or release authority. diff --git a/scripts/ci1_image_test.py b/scripts/ci1_image_test.py deleted file mode 100644 index fa54fa7..0000000 --- a/scripts/ci1_image_test.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Fail-closed admission for the shared, pinned ci-1 image.""" -import copy -import datetime -import importlib.util -import pathlib -import hashlib -import os -import subprocess -import tempfile -import textwrap -import unittest - -spec = importlib.util.spec_from_file_location("ci1_image", pathlib.Path(__file__).with_name("verify-ci1-image.py")) -image = importlib.util.module_from_spec(spec) -spec.loader.exec_module(image) - - -class AdmissionTest(unittest.TestCase): - def setUp(self): - self.now = datetime.datetime(2026, 10, 19, 18, 0, tzinfo=datetime.timezone.utc) - self.manifest = { - "built_at": "2026-10-19T14:41:57+00:00", - "base_fingerprint": "2139edd9da4026da8a66c0557af7eee4789101104eb27860bacd57086219cebb", - "inputs": { - "infra/ci1/machine.json": "0900619bac06c6a81f5d7535107985bf8d9cf11c6dd2079273d8ab81d9ebac3a", - "infra/ci1/tools.lock.json": "88d3c9a0e804935c1c7b60ac60d249512309405e150d654de8d896ce68f74f47", - }, - } - - def test_qualified_image(self): - image.validate_image(self.manifest, self.now, "x86_64", 8) - - def test_wrong_shape_or_architecture(self): - for arch, cpus in [("aarch64", 8), ("x86_64", 4)]: - with self.subTest(arch=arch, cpus=cpus), self.assertRaises(ValueError): - image.validate_image(self.manifest, self.now, arch, cpus) - - def test_expired_image_or_future_clock(self): - for built in ["2026-10-11T18:00:00+00:00", "2026-10-19T18:02:00+00:00", "2026-10-19T14:41:57"]: - with self.subTest(built=built), self.assertRaises(ValueError): - image.validate_image({**self.manifest, "built_at": built}, self.now, "x86_64", 8) - - def test_wrong_base(self): - with self.assertRaises(ValueError): - image.validate_image({**self.manifest, "base_fingerprint": "0" * 64}, self.now, "x86_64", 8) - - def test_changed_or_missing_definition_and_tools(self): - for name in self.manifest["inputs"]: - for value in [None, "0" * 64]: - bad = copy.deepcopy(self.manifest) - if value is None: - del bad["inputs"][name] - else: - bad["inputs"][name] = value - with self.subTest(name=name, value=value), self.assertRaises(ValueError): - image.validate_image(bad, self.now, "x86_64", 8) - - -class WorkflowAdmissionTest(unittest.TestCase): - def setUp(self): - self.workflow = pathlib.Path(__file__).parents[1].joinpath(".github/workflows/ci1.yml").read_text() - self.scratch = pathlib.Path.home() / ".polymorfa-agent-work" / "cli-ci1-admission-tests" - self.scratch.mkdir(parents=True, exist_ok=True) - - def test_malformed_checksum_row_fails(self): - with tempfile.TemporaryDirectory(dir=self.scratch) as directory: - root = pathlib.Path(directory) - data = root / "protected-file" - data.write_bytes(b"qualified bytes") - record = root / "manifest.sha256" - record.write_text(hashlib.sha256(data.read_bytes()).hexdigest() + " " + str(data) + "\n") - command = next(line.strip().split() for line in self.workflow.splitlines() if line.strip().startswith("sha256sum --check")) - command[-1] = str(record) - result = subprocess.run(command, capture_output=True) - self.assertEqual(result.returncode, 0, result.stderr.decode()) - record.write_text(record.read_text() + "malformed entry\n") - result = subprocess.run(command, capture_output=True) - self.assertNotEqual(result.returncode, 0, result.stdout.decode()) - - def test_fork_guard_fails_before_checkout(self): - marker = " - name: Require trusted source\n" - self.assertIn(marker, self.workflow) - script = self.workflow.split(marker, 1)[1].split(" run: |\n", 1)[1].split("\n - ", 1)[0] - script = textwrap.dedent(script) - for event, head, expected in [("pull_request", "polymorfa/cli", 0), ("push", "", 0), ("pull_request", "external/cli", 1), ("pull_request", "", 1)]: - with self.subTest(event=event, head=head): - result = subprocess.run(["bash", "-c", script], env={**os.environ, "EVENT_NAME": event, "HEAD_REPOSITORY": head, "REPOSITORY": "polymorfa/cli"}, capture_output=True) - self.assertEqual(result.returncode, expected) - self.assertLess(self.workflow.index(marker), self.workflow.index(" - uses: actions/checkout@")) diff --git a/scripts/verify-ci1-image.py b/scripts/verify-ci1-image.py deleted file mode 100644 index 2ea3c20..0000000 --- a/scripts/verify-ci1-image.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -"""Validate the shared ci-1 image before installing CLI dependencies.""" -import datetime -import json -import os -import pathlib -import platform - -BASE = "2139edd9da4026da8a66c0557af7eee4789101104eb27860bacd57086219cebb" -INPUTS = { - "infra/ci1/machine.json": "0900619bac06c6a81f5d7535107985bf8d9cf11c6dd2079273d8ab81d9ebac3a", - "infra/ci1/tools.lock.json": "88d3c9a0e804935c1c7b60ac60d249512309405e150d654de8d896ce68f74f47", -} - - -def validate_image(data, now, architecture, cpus): - if architecture != "x86_64" or cpus != 8: - raise ValueError("ci-1 requires the qualified 8-CPU x86_64 integration VM") - built = datetime.datetime.fromisoformat(data["built_at"]) - if built.tzinfo is None: - raise ValueError("ci-1 image timestamp requires a timezone") - age = now - built - if age.total_seconds() < -60 or age > datetime.timedelta(days=7): - raise ValueError("ci-1 image age or clock is invalid; rebuild before running CI") - if data["base_fingerprint"] != BASE: - raise ValueError("ci-1 base image does not match the qualified definition") - for name, expected in INPUTS.items(): - if data["inputs"].get(name) != expected: - raise ValueError("ci-1 image definition or tools changed; qualify before running CI") - - -if __name__ == "__main__": - manifest = pathlib.Path("/etc/ci1-image/input-manifest.json") - data = json.loads(manifest.read_text()) - validate_image(data, datetime.datetime.now(datetime.timezone.utc), platform.machine(), len(os.sched_getaffinity(0))) - print("ci-1 image age, base, tool definition and integration VM shape qualified")