From 8575abb9d77c65d6b45fc845d3989f861d6fbea0 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Mon, 28 Sep 2026 15:30:16 +0200 Subject: [PATCH 1/2] Prepare Net ordinary V2 activation handoff --- docs/roadmaps/v2-ordinary-adoption.md | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/roadmaps/v2-ordinary-adoption.md b/docs/roadmaps/v2-ordinary-adoption.md index b1e36cb..056df7a 100644 --- a/docs/roadmaps/v2-ordinary-adoption.md +++ b/docs/roadmaps/v2-ordinary-adoption.md @@ -53,3 +53,35 @@ Do not enable the preflight or add a credential job until one reviewed source ch The later activation must change policy status, installed state, package evidence, observer guard, and the bounded credential job together. This disabled source cannot settle work and imports no V1 admission or receiver state. + +## Activation handoff (prepared on 2026-09-28) + +The next source change is one reviewed activation PR based on protected `main`. Before editing it, +read the public `FS.GG.Coordination.Cli` 0.1.5 release asset from the served feed, verify its SHA-256, +and confirm that the published package implements `net-v1`. Record that exact digest and source commit +in `packagePin`; a local build or an intended release version is insufficient. + +Refresh Net's repository ID, protected-main head, required check names and App IDs, workflow IDs and +paths, and the `ordinary-v2` environment branch policy and three dedicated secret *names* from the +native API. Re-read the shared Authority binding against its current source. On 2026-09-28 the Net +readback matched repository ID `1305845505`, four required GitHub Actions App `15368` contexts, +workflow IDs `316245439`, `316890379`, `316890380`, environment ID `22920188172`, its sole `main` +branch policy ID `61287584`, and exactly the three names recorded above. These observations are a +baseline, not permission to use stale values at activation. + +In that PR, enable the secret-free preflight, pass its receipt digest and activation result to a +bounded `ordinary-v2` credential job, and use the installed public CLI archive only after checking +its served SHA-256. The credential job must recheck the same-run receipt and current protected +policy, workflow, and anchor before reading the dedicated secrets and executing one settlement +attempt. Set policy status to `installed`, `credentialJob.installed` to true, and pin the verified +package in the same source change. Update the source tests so an enabled workflow is checked for +the receipt fence, local-only package install, exact secret inventory, and absence of request or +manual trigger paths. + +The clean source gate for this repository is the two Python receiver test files followed by +`dotnet restore FS.GG.Net.slnx --locked-mode`, `dotnet build FS.GG.Net.slnx -c Debug --no-restore`, +and `dotnet test FS.GG.Net.slnx -c Debug --no-build --no-restore`. If the shared NuGet cache raises +`NU1403` for `FSharp.Core 10.1.401`, use a fresh task-specific `NUGET_PACKAGES` directory; the +isolated locked restore, build, and both test assemblies passed at this handoff. Preserve all four +native required checks, merge the exact green PR head, read back the merged Authority, and verify +the ordinary `AlreadyComplete` rerun behavior before recording installed operation. From 23e0191a81fbd358445075642a774829922f4933 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Mon, 28 Sep 2026 17:01:20 +0200 Subject: [PATCH 2/2] Activate Net ordinary V2 settlement with CLI 0.1.5 --- .../workflows/v2-ci-ordinary-settlement.yml | 88 ++++++++++++++++++- docs/roadmaps/v2-ordinary-adoption.md | 56 ++++++------ policy/v2-ci-ordinary-settlement.json | 30 ++++--- tests/v2-ci-ordinary-observe/run.py | 35 +++++--- tests/v2-ci-ordinary-qualification/run.py | 19 ++-- 5 files changed, 159 insertions(+), 69 deletions(-) diff --git a/.github/workflows/v2-ci-ordinary-settlement.yml b/.github/workflows/v2-ci-ordinary-settlement.yml index 2b83903..6864875 100644 --- a/.github/workflows/v2-ci-ordinary-settlement.yml +++ b/.github/workflows/v2-ci-ordinary-settlement.yml @@ -1,4 +1,4 @@ -name: V2 ordinary post-merge settlement (source only) +name: V2 ordinary post-merge settlement on: push: @@ -17,11 +17,11 @@ concurrency: jobs: preflight: name: ordinary-v2-secret-free-preflight - # Net remains inert until an immutable net-v1 CLI release and - # dedicated custody are installed and read back in one later source change. - if: ${{ false }} runs-on: ubuntu-latest timeout-minutes: 5 + outputs: + receipt_sha256: ${{ steps.receipt.outputs.sha256 }} + activation: ${{ steps.receipt.outputs.activation }} steps: - name: Check out exact protected source without persisted credentials uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 @@ -39,6 +39,10 @@ jobs: run: | set -euo pipefail python3 tools/v2-ci-ordinary-observe.py produce "$RUNNER_TEMP/ordinary-v2-preflight.json" + digest="$(sha256sum "$RUNNER_TEMP/ordinary-v2-preflight.json" | cut -d ' ' -f 1)" + activation="$(python3 -c 'import json,sys; print(str(json.load(open(sys.argv[1]))["activation"]).lower())' "$RUNNER_TEMP/ordinary-v2-preflight.json")" + echo "sha256=$digest" >> "$GITHUB_OUTPUT" + echo "activation=$activation" >> "$GITHUB_OUTPUT" - name: Retain public exact-run receipt uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: @@ -46,3 +50,79 @@ jobs: path: ${{ runner.temp }}/ordinary-v2-preflight.json retention-days: 30 if-no-files-found: error + + settle: + name: ordinary-v2-credential-settlement + needs: [preflight] + # Only the exact secret-free predecessor receipt can admit this job. + if: needs.preflight.outputs.activation == 'true' + environment: ordinary-v2 + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + PACKAGE_VERSION: 0.1.5 + # Immutable v0.1.5 GitHub release archive, independently read back before activation. + PACKAGE_SHA256: 3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9 + permissions: + actions: read + contents: read + checks: read + pull-requests: read + steps: + - name: Check out exact protected Net source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + - name: Download only this run's predecessor receipt + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: ordinary-v2-preflight-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/ordinary-v2-receipt + - name: Recheck the receipt and current public authority + shell: bash + env: + GH_TOKEN: ${{ github.token }} + EXPECTED_WORKFLOW_SHA: ${{ github.workflow_sha }} + EXPECTED_RECEIPT_SHA256: ${{ needs.preflight.outputs.receipt_sha256 }} + FSGG_V2_SOURCE_PROFILE: net-v1 + run: | + set -euo pipefail + receipt="$RUNNER_TEMP/ordinary-v2-receipt/ordinary-v2-preflight.json" + test "$(sha256sum "$receipt" | cut -d ' ' -f 1)" = "$EXPECTED_RECEIPT_SHA256" + python3 tools/v2-ci-ordinary-observe.py verify "$receipt" + - name: Set up the pinned .NET SDK + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 + with: + global-json-file: global.json + - name: Install only the pinned public Coordination CLI archive + shell: bash + run: | + set -euo pipefail + package_dir="$RUNNER_TEMP/ordinary-v2-package" + mkdir "$package_dir" + package="$package_dir/FS.GG.Coordination.Cli.$PACKAGE_VERSION.nupkg" + curl --fail --location --silent --show-error \ + "https://github.com/FS-GG/FS.GG.Coordination/releases/download/v$PACKAGE_VERSION/FS.GG.Coordination.Cli.$PACKAGE_VERSION.nupkg" \ + --output "$package" + test "$(sha256sum "$package" | cut -d ' ' -f 1)" = "$PACKAGE_SHA256" + config="$RUNNER_TEMP/ordinary-v2-local-only.config" + cat > "$config" < + CFG + dotnet tool install FS.GG.Coordination.Cli --version "$PACKAGE_VERSION" \ + --tool-path "$RUNNER_TEMP/ordinary-v2-cli" --configfile "$config" --no-cache + - name: Execute exactly one installed Net settlement attempt + shell: bash + env: + GH_TOKEN: ${{ github.token }} + EXPECTED_WORKFLOW_SHA: ${{ github.workflow_sha }} + FSGG_V2_SOURCE_PROFILE: net-v1 + FSGG_V2_PREFLIGHT_RECEIPT: ${{ runner.temp }}/ordinary-v2-receipt/ordinary-v2-preflight.json + V2_ORDINARY_APP_ID: ${{ secrets.V2_ORDINARY_APP_ID }} + V2_ORDINARY_APP_PRIVATE_KEY: ${{ secrets.V2_ORDINARY_APP_PRIVATE_KEY }} + V2_ORDINARY_AUTHORIZER_PRIVATE_KEY: ${{ secrets.V2_ORDINARY_AUTHORIZER_PRIVATE_KEY }} + run: | + set -euo pipefail + "$RUNNER_TEMP/ordinary-v2-cli/fsgg-coordination" ordinary-settlement execute diff --git a/docs/roadmaps/v2-ordinary-adoption.md b/docs/roadmaps/v2-ordinary-adoption.md index 056df7a..7d346c3 100644 --- a/docs/roadmaps/v2-ordinary-adoption.md +++ b/docs/roadmaps/v2-ordinary-adoption.md @@ -1,7 +1,7 @@ # C3-NET-01 — Ordinary V2 receiver adoption -Status: source prepared and disabled. Dedicated custody is enrolled; CLI release, installation, and -activation remain pending. +Status: activation source prepared for the 0.1.5 CLI. Dedicated custody is enrolled. Installed +operation and its post-merge settlement proof remain pending until this change merges. FS.GG.Net is the fixed C3 source repository (`FS-GG/FS.GG.Net`, repository ID `1305845505`) under the code-owned `net-v1` profile. This change adds only repository-owned @@ -10,9 +10,10 @@ check, generated workspace content, or protected effect. ## Prepared source -- The receiver workflow is bound to protected-main pushes, but its only job has an unconditional - false guard. It uses read-only GitHub permissions, persists no checkout credential, and contains - no credential job, environment binding, secret reference, package download, or settlement command. +- The receiver workflow is bound to protected-main pushes. Its read-only, secret-free preflight + produces an exact-run receipt. The bounded credential job runs only when that receipt admits + activation, rechecks current Authority, verifies the pinned public package archive, and uses + only the dedicated `ordinary-v2` secrets. Both checkouts persist no credential. - The source pattern comes from Rendering receiver commit `8f4acd853566ea287abd15655c1aa5c4d3ceb403`; the observer retains its repaired Audio bytes, and the qualifier replaces only the code-owned source profile. Their SHA-256 digests are @@ -30,36 +31,38 @@ check, generated workspace content, or protected effect. - The shared policy ID remains `v2-ci-i1-ordinary-settlement-v1`; the shared Authority anchor retains App `5064713`, installation `164553252`, repository `FS-GG/FS.GG.Coordination.Authority` (`1351660651`), `contents:write`, metadata read, and the existing writer/integrity ruleset pins. -- Read-only API observation at `2026-09-28T12:22:05Z`, against Net main - `2fe9c00976b5d01c36fbd587c21135b650cf43b9`, found the `ordinary-v2` environment as ID +- Read-only API observation at `2026-09-28T15:00:36Z`, against Net main + `dfc04d994e955842a7e16597f7093ed14f1b5251`, found the `ordinary-v2` environment as ID `22920188172`, restricted to the single `main` branch policy ID `61287584`, with no reviewers. Protected custody bridge run `36419999006` succeeded and the environment now reads back the exact three dedicated ordinary-v2 secret names. -- No immutable published CLI release with `net-v1` support is selected. Version and package - SHA-256 remain null, and policy explicitly refuses activation until a served package digest is - independently verified. -- Net already pins .NET SDK `10.0.401` in the repository's tracked `global.json`. This - receiver leaves that pin unchanged and invokes no .NET setup while disabled. +- The immutable public `v0.1.5` release package pin is SHA-256 + `3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9`, from reviewed + Coordination source and exact tag `1268908d2d5a38d30a764c927f3e0591e53138aa`. The public + release asset matches the prepared archive byte for byte. Its readback records an identical + GitHub Packages archive and an identical nuget.org payload after NuGet signing; anonymous + public-only install and invocation passed before this activation source was pushed. +- Net already pins .NET SDK `10.0.401` in the repository's tracked `global.json`. The credential + job uses that pin after receipt and Authority verification. ## Installation boundary -Do not enable the preflight or add a credential job until one reviewed source change verifies all of: +This activation source was held locally until one reviewed change verified all of: 1. an immutable published Coordination CLI supports the exact `net-v1` source profile and its served package SHA-256 is pinned; 2. Net identity, exact current required-check population, producer mappings, and shared Authority binding are freshly read back. -The later activation must change policy status, installed state, package evidence, observer guard, -and the bounded credential job together. This disabled source cannot settle work and imports no V1 -admission or receiver state. +This candidate changes policy status, installed state, package evidence, observer guard, and the +bounded credential job together. It imports no V1 admission or receiver state. ## Activation handoff (prepared on 2026-09-28) -The next source change is one reviewed activation PR based on protected `main`. Before editing it, -read the public `FS.GG.Coordination.Cli` 0.1.5 release asset from the served feed, verify its SHA-256, -and confirm that the published package implements `net-v1`. Record that exact digest and source commit -in `packagePin`; a local build or an intended release version is insufficient. +This activation candidate becomes one reviewed PR based on protected `main` only after the +public `FS.GG.Coordination.Cli` 0.1.5 release asset has its SHA-256 verified and the published +package implements `net-v1`. The package pin must identify that exact digest and source commit; +a local build or an intended release version is insufficient. Refresh Net's repository ID, protected-main head, required check names and App IDs, workflow IDs and paths, and the `ordinary-v2` environment branch policy and three dedicated secret *names* from the @@ -69,14 +72,11 @@ workflow IDs `316245439`, `316890379`, `316890380`, environment ID `22920188172` branch policy ID `61287584`, and exactly the three names recorded above. These observations are a baseline, not permission to use stale values at activation. -In that PR, enable the secret-free preflight, pass its receipt digest and activation result to a -bounded `ordinary-v2` credential job, and use the installed public CLI archive only after checking -its served SHA-256. The credential job must recheck the same-run receipt and current protected -policy, workflow, and anchor before reading the dedicated secrets and executing one settlement -attempt. Set policy status to `installed`, `credentialJob.installed` to true, and pin the verified -package in the same source change. Update the source tests so an enabled workflow is checked for -the receipt fence, local-only package install, exact secret inventory, and absence of request or -manual trigger paths. +The candidate enables the secret-free preflight, passes its receipt digest and activation result +to the `ordinary-v2` credential job, checks the public CLI archive SHA-256 before local-only +installation, and rechecks the same-run receipt and current protected policy, workflow, and anchor +before a settlement attempt. The policy and source tests bind the installed state, package pin, +receipt fence, exact secret inventory, and absence of request or manual trigger paths together. The clean source gate for this repository is the two Python receiver test files followed by `dotnet restore FS.GG.Net.slnx --locked-mode`, `dotnet build FS.GG.Net.slnx -c Debug --no-restore`, diff --git a/policy/v2-ci-ordinary-settlement.json b/policy/v2-ci-ordinary-settlement.json index 11727cd..5ff59ff 100644 --- a/policy/v2-ci-ordinary-settlement.json +++ b/policy/v2-ci-ordinary-settlement.json @@ -1,7 +1,7 @@ { "schema": "fsgg.github.v2-ci-ordinary-settlement-policy/1", "policyId": "v2-ci-i1-ordinary-settlement-v1", - "status": "source-qualified-not-installed", + "status": "installed", "repository": "FS-GG/FS.GG.Net", "repositoryId": 1305845505, "selectedSource": { @@ -84,9 +84,9 @@ "allowedBranches": [ "main" ], - "installed": false, + "installed": true, "liveObservation": { - "observedAt": "2026-09-28T12:22:05Z", + "observedAt": "2026-09-28T15:00:36Z", "environmentPresent": true, "environmentId": 22920188172, "environmentNodeId": "EN_kwDOTdWfAc8AAAAFViZRDA", @@ -104,17 +104,19 @@ "V2_ORDINARY_APP_PRIVATE_KEY", "V2_ORDINARY_AUTHORIZER_PRIVATE_KEY" ], - "disposition": "dedicated-custody-enrolled-release-pending" + "disposition": "dedicated-custody-enrolled" } }, "packagePin": { - "status": "awaiting-published-net-profile-release", - "version": null, - "sha256": null, - "servedPackageVerified": false, + "status": "published-verified", + "version": "0.1.5", + "sha256": "3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9", + "servedPackageVerified": true, + "sourceCommit": "1268908d2d5a38d30a764c927f3e0591e53138aa", + "releaseTag": "v0.1.5", + "publisherRunId": 36437778484, "requiredCapability": "net-v1 selected source", - "requiredBeforeActivation": true, - "refusal": "no immutable published CLI release asset with net-v1 support or verified served SHA-256 is selected" + "requiredBeforeActivation": true }, "credentialInventory": [ { @@ -153,8 +155,8 @@ "readBackAt": "2026-09-28T12:22:05Z" }, "activationEvidence": { - "observedAt": "2026-09-28T11:55:40Z", - "sourceMainSha": "2fe9c00976b5d01c36fbd587c21135b650cf43b9", + "observedAt": "2026-09-28T15:00:36Z", + "sourceMainSha": "dfc04d994e955842a7e16597f7093ed14f1b5251", "sourceRepositoryId": 1305845505, "requiredCheckAppId": 15368, "requiredGateChecks": [ @@ -165,8 +167,8 @@ ] }, "activationPrerequisites": [ - "published immutable Coordination CLI with net-v1 support and verified package SHA-256", - "current source-check population and shared Authority binding are read back again" + "published immutable Coordination CLI with net-v1 support and verified package SHA-256: satisfied by 0.1.5 release and public feed readback", + "current source-check population and shared Authority binding are read back again before activation" ], "forbiddenCredentialReuse": [ "CALLABLE_ISOLATED_OPERATION_APP_PRIVATE_KEY", diff --git a/tests/v2-ci-ordinary-observe/run.py b/tests/v2-ci-ordinary-observe/run.py index ac517c9..323a80e 100644 --- a/tests/v2-ci-ordinary-observe/run.py +++ b/tests/v2-ci-ordinary-observe/run.py @@ -95,27 +95,33 @@ def api(path): with patch.object(MODULE, "api", side_effect=api): MODULE.current_authority("FS-GG/FS.GG.Net", policy) - def test_workflow_is_hard_disabled_and_has_no_credential_or_package_surface(self): + def test_workflow_prepares_bounded_settlement_with_exact_receipt_guard(self): workflow = (ROOT / ".github/workflows/v2-ci-ordinary-settlement.yml").read_text() self.assertIn(" push:\n branches: [main]", workflow) - self.assertIn(" if: ${{ false }}", workflow) + self.assertNotIn(" if: ${{ false }}", workflow) + self.assertIn("if: needs.preflight.outputs.activation == 'true'", workflow) + self.assertIn("environment: ordinary-v2", workflow) self.assertIn("FSGG_V2_SOURCE_PROFILE: net-v1", workflow) self.assertIn("persist-credentials: false", workflow) self.assertIn("python3 tools/v2-ci-ordinary-observe.py produce", workflow) - for forbidden in ( - "secrets.", "environment:", "ordinary-settlement execute", "PACKAGE_VERSION", - "PACKAGE_SHA256", "setup-dotnet", "global.json", "workflow_dispatch:", - "repository_dispatch:", "pull_request:", "pull_request_target:", - ): + self.assertIn("python3 tools/v2-ci-ordinary-observe.py verify", workflow) + self.assertIn("PACKAGE_VERSION: 0.1.5", workflow) + self.assertIn("PACKAGE_SHA256: 3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9", workflow) + self.assertIn("https://github.com/FS-GG/FS.GG.Coordination/releases/download/v$PACKAGE_VERSION/FS.GG.Coordination.Cli.$PACKAGE_VERSION.nupkg", workflow) + self.assertNotIn("api.nuget.org/v3-flatcontainer", workflow) + self.assertIn("ordinary-settlement execute", workflow) + for name in ("V2_ORDINARY_APP_ID", "V2_ORDINARY_APP_PRIVATE_KEY", "V2_ORDINARY_AUTHORIZER_PRIVATE_KEY"): + self.assertIn("${{ secrets." + name + " }}", workflow) + for forbidden in ("workflow_dispatch:", "repository_dispatch:", "pull_request:", "pull_request_target:", "V1_ADMISSION", "CALLABLE_ISOLATED_OPERATION"): self.assertNotIn(forbidden, workflow) - def test_policy_anchor_environment_and_unresolved_package_are_bounded(self): + def test_policy_anchor_environment_and_published_package_are_bounded(self): policy = json.loads((ROOT / "policy/v2-ci-ordinary-settlement.json").read_text()) anchor = json.loads((ROOT / "policy/v2-ci-ordinary-settlement-anchor.json").read_text()) self.assertEqual("v2-ci-i1-ordinary-settlement-v1", policy["policyId"]) self.assertEqual(policy["policyId"], anchor["policyId"]) - self.assertEqual("source-qualified-not-installed", policy["status"]) - self.assertFalse(policy["credentialJob"]["installed"]) + self.assertEqual("installed", policy["status"]) + self.assertTrue(policy["credentialJob"]["installed"]) observation = policy["credentialJob"]["liveObservation"] self.assertEqual(22920188172, observation["environmentId"]) self.assertEqual(61287584, observation["branchPolicyId"]) @@ -126,11 +132,12 @@ def test_policy_anchor_environment_and_unresolved_package_are_bounded(self): "V2_ORDINARY_APP_PRIVATE_KEY", "V2_ORDINARY_AUTHORIZER_PRIVATE_KEY", }, set(observation["secretNames"])) - self.assertEqual("awaiting-published-net-profile-release", + self.assertEqual("published-verified", policy["packagePin"]["status"]) - self.assertIsNone(policy["packagePin"]["version"]) - self.assertIsNone(policy["packagePin"]["sha256"]) - self.assertFalse(policy["packagePin"]["servedPackageVerified"]) + self.assertEqual("0.1.5", policy["packagePin"]["version"]) + self.assertEqual("3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9", + policy["packagePin"]["sha256"]) + self.assertTrue(policy["packagePin"]["servedPackageVerified"]) self.assertEqual(3, len(policy["credentialInventory"])) self.assertTrue(all(item["provisioned"] for item in policy["credentialInventory"])) self.assertEqual(5064713, anchor["writer"]["appId"]) diff --git a/tests/v2-ci-ordinary-qualification/run.py b/tests/v2-ci-ordinary-qualification/run.py index c0a6165..7fea65c 100644 --- a/tests/v2-ci-ordinary-qualification/run.py +++ b/tests/v2-ci-ordinary-qualification/run.py @@ -141,17 +141,18 @@ def test_failed_incomplete_stale_and_foreign_evidence_refuse(self): with self.assertRaisesRegex(MODULE.Refusal, "wrong repository"): self.qualify(associations=associations) - def test_policy_remains_disabled_with_explicit_release_and_custody_refusals(self): - self.assertEqual("source-qualified-not-installed", self.policy["status"]) - self.assertFalse(self.policy["credentialJob"]["installed"]) + def test_policy_installs_only_the_verified_net_profile_release(self): + self.assertEqual("installed", self.policy["status"]) + self.assertTrue(self.policy["credentialJob"]["installed"]) self.assertEqual(3, self.policy["credentialJob"]["liveObservation"]["secretCount"]) self.assertEqual(3, len(self.policy["credentialInventory"])) - self.assertEqual("awaiting-published-net-profile-release", - self.policy["packagePin"]["status"]) - self.assertIsNone(self.policy["packagePin"]["version"]) - self.assertIsNone(self.policy["packagePin"]["sha256"]) - self.assertIn("no immutable published CLI release", - self.policy["packagePin"]["refusal"]) + pin = self.policy["packagePin"] + self.assertEqual("published-verified", pin["status"]) + self.assertEqual("0.1.5", pin["version"]) + self.assertEqual("3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9", + pin["sha256"]) + self.assertEqual("1268908d2d5a38d30a764c927f3e0591e53138aa", pin["sourceCommit"]) + self.assertTrue(pin["servedPackageVerified"]) self.assertEqual(["OpenV2"], self.policy["unchangedGates"]) self.assertEqual({"v1Admission": False, "receiverStateImport": False}, self.policy["migration"])