From eaaef66187c21b58fc8728dd0772297f6aaae320 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Fri, 9 Oct 2026 22:02:06 +0200 Subject: [PATCH] chore: prepare main-backed ordinary settlement adoption --- .../workflows/v2-ci-ordinary-settlement.yml | 8 +- ...v2-ci-ordinary-settlement-main-anchor.json | 107 ++++++++++++++++++ policy/v2-ci-ordinary-settlement.json | 20 ++-- tests/v2-ci-ordinary-observe/run.py | 12 +- tests/v2-ci-ordinary-qualification/run.py | 6 +- tools/v2-ci-ordinary-observe.py | 17 ++- 6 files changed, 141 insertions(+), 29 deletions(-) create mode 100644 policy/v2-ci-ordinary-settlement-main-anchor.json diff --git a/.github/workflows/v2-ci-ordinary-settlement.yml b/.github/workflows/v2-ci-ordinary-settlement.yml index 6864875..3acdb2a 100644 --- a/.github/workflows/v2-ci-ordinary-settlement.yml +++ b/.github/workflows/v2-ci-ordinary-settlement.yml @@ -60,9 +60,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 env: - PACKAGE_VERSION: 0.1.5 + PACKAGE_VERSION: 0.3.0 # Immutable v0.1.5 GitHub release archive, independently read back before activation. - PACKAGE_SHA256: 3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9 + PACKAGE_SHA256: a8cd6d602e1203257e1241df0b5dfdb9d867334b46dc406d8cdaa8e6d2b3019c permissions: actions: read contents: read @@ -91,7 +91,7 @@ jobs: 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" + python3 tools/v2-ci-ordinary-observe.py verify-main "$receipt" - name: Set up the pinned .NET SDK uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: @@ -125,4 +125,4 @@ jobs: 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 + "$RUNNER_TEMP/ordinary-v2-cli/fsgg-coordination" ordinary-settlement execute-main diff --git a/policy/v2-ci-ordinary-settlement-main-anchor.json b/policy/v2-ci-ordinary-settlement-main-anchor.json new file mode 100644 index 0000000..ddcbf3e --- /dev/null +++ b/policy/v2-ci-ordinary-settlement-main-anchor.json @@ -0,0 +1,107 @@ +{ + "schema": "fsgg.github.v2-ci-ordinary-settlement-anchor/1", + "policyId": "v2-ci-i1-ordinary-settlement-v1", + "operationClass": "ordinary-post-merge-delivery-settlement", + "authorizer": { + "keyId": "ordinary-v2-production-6121c3f2ab38acf3", + "algorithm": "RSA-PSS-SHA256", + "publicKeySpkiSha256": "6121c3f2ab38acf38a37f782eb7775da78598b9cfabc0054917d3d59fe50e3ee" + }, + "writer": { + "appId": 5064713, + "installationId": 164553252, + "repository": "FS-GG/FS.GG.Coordination.Authority", + "repositoryId": 1351660651, + "permissions": { + "contents": "write", + "metadata": "read" + } + }, + "rulesets": { + "writer": { + "id": 24802693, + "name": "ordinary-v2-main-writer", + "enforcement": "active", + "updatedAt": "2026-10-09T18:56:41.235+02:00", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "refs/heads/main" + ] + } + }, + "rules": [ + { + "type": "creation" + }, + { + "type": "update" + } + ], + "bypassActors": [ + { + "actorId": 4882140, + "actorType": "Integration", + "bypassMode": "always" + }, + { + "actorId": 5064713, + "actorType": "Integration", + "bypassMode": "always" + } + ] + }, + "integrity": { + "id": 24802698, + "name": "ordinary-v2-main-integrity", + "enforcement": "active", + "updatedAt": "2026-10-09T18:56:42.726+02:00", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "refs/heads/main" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + } + ], + "bypassActors": [] + } + }, + "effectiveRules": [ + { + "type": "deletion", + "rulesetId": 24802698, + "rulesetSourceType": "Repository", + "rulesetSource": "FS-GG/FS.GG.Coordination.Authority" + }, + { + "type": "non_fast_forward", + "rulesetId": 24802698, + "rulesetSourceType": "Repository", + "rulesetSource": "FS-GG/FS.GG.Coordination.Authority" + }, + { + "type": "creation", + "rulesetId": 24802693, + "rulesetSourceType": "Repository", + "rulesetSource": "FS-GG/FS.GG.Coordination.Authority" + }, + { + "type": "update", + "rulesetId": 24802693, + "rulesetSourceType": "Repository", + "rulesetSource": "FS-GG/FS.GG.Coordination.Authority" + } + ], + "acceptedAt": "2026-10-09T19:31:28.661736+00:00", + "sourceCommit": "8eef1ab7f205132553632e03dc650e7ecdd03679" +} diff --git a/policy/v2-ci-ordinary-settlement.json b/policy/v2-ci-ordinary-settlement.json index 5ff59ff..d100d66 100644 --- a/policy/v2-ci-ordinary-settlement.json +++ b/policy/v2-ci-ordinary-settlement.json @@ -10,10 +10,10 @@ "repositoryId": 1305845505 }, "sourceImplementation": { - "repository": "FS-GG/FS.GG.Rendering", - "commit": "8f4acd853566ea287abd15655c1aa5c4d3ceb403", - "adaptation": "net-v1-code-owned-source-profile", - "observerSha256": "6e63f6f724fb267e77ef02f41b4c58a833e0dc5b08c003f07fd16c89e4f7fd55", + "repository": "FS-GG/FS.GG.Coordination", + "commit": "8eef1ab7f205132553632e03dc650e7ecdd03679", + "adaptation": "net-v1-code-owned-source-profile; main-storage extension with local profile preserved", + "observerSha256": "7f5500d810ab9169d967f8c19fbcb01d706ebf82fdeea6e4a6927b0b19eef29a", "qualificationSha256": "ff38a1b5748aaec8ff23561ae7c0c3eb67fb5232b9a4a43071dff22182dbac32" }, "operationClass": "ordinary-post-merge-delivery-settlement", @@ -109,14 +109,14 @@ }, "packagePin": { "status": "published-verified", - "version": "0.1.5", - "sha256": "3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9", + "version": "0.3.0", + "sha256": "a8cd6d602e1203257e1241df0b5dfdb9d867334b46dc406d8cdaa8e6d2b3019c", + "sourceCommit": "8eef1ab7f205132553632e03dc650e7ecdd03679", "servedPackageVerified": true, - "sourceCommit": "1268908d2d5a38d30a764c927f3e0591e53138aa", - "releaseTag": "v0.1.5", - "publisherRunId": 36437778484, + "requiredBeforeActivation": true, "requiredCapability": "net-v1 selected source", - "requiredBeforeActivation": true + "publisherRunId": 37983508705, + "releaseTag": "v0.3.0" }, "credentialInventory": [ { diff --git a/tests/v2-ci-ordinary-observe/run.py b/tests/v2-ci-ordinary-observe/run.py index 323a80e..acce3ce 100644 --- a/tests/v2-ci-ordinary-observe/run.py +++ b/tests/v2-ci-ordinary-observe/run.py @@ -104,12 +104,12 @@ def test_workflow_prepares_bounded_settlement_with_exact_receipt_guard(self): 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) - 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("python3 tools/v2-ci-ordinary-observe.py verify-main", workflow) + self.assertIn("PACKAGE_VERSION: 0.3.0", workflow) + self.assertIn("PACKAGE_SHA256: a8cd6d602e1203257e1241df0b5dfdb9d867334b46dc406d8cdaa8e6d2b3019c", 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) + self.assertIn("ordinary-settlement execute-main", 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"): @@ -134,8 +134,8 @@ def test_policy_anchor_environment_and_published_package_are_bounded(self): }, set(observation["secretNames"])) self.assertEqual("published-verified", policy["packagePin"]["status"]) - self.assertEqual("0.1.5", policy["packagePin"]["version"]) - self.assertEqual("3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9", + self.assertEqual("0.3.0", policy["packagePin"]["version"]) + self.assertEqual("a8cd6d602e1203257e1241df0b5dfdb9d867334b46dc406d8cdaa8e6d2b3019c", policy["packagePin"]["sha256"]) self.assertTrue(policy["packagePin"]["servedPackageVerified"]) self.assertEqual(3, len(policy["credentialInventory"])) diff --git a/tests/v2-ci-ordinary-qualification/run.py b/tests/v2-ci-ordinary-qualification/run.py index 7fea65c..8fca792 100644 --- a/tests/v2-ci-ordinary-qualification/run.py +++ b/tests/v2-ci-ordinary-qualification/run.py @@ -148,10 +148,10 @@ def test_policy_installs_only_the_verified_net_profile_release(self): self.assertEqual(3, len(self.policy["credentialInventory"])) pin = self.policy["packagePin"] self.assertEqual("published-verified", pin["status"]) - self.assertEqual("0.1.5", pin["version"]) - self.assertEqual("3567a92825917a7d537f6c5c545d3a7947bc35edd666fc3a1898de3bf97267c9", + self.assertEqual("0.3.0", pin["version"]) + self.assertEqual("a8cd6d602e1203257e1241df0b5dfdb9d867334b46dc406d8cdaa8e6d2b3019c", pin["sha256"]) - self.assertEqual("1268908d2d5a38d30a764c927f3e0591e53138aa", pin["sourceCommit"]) + self.assertEqual("8eef1ab7f205132553632e03dc650e7ecdd03679", pin["sourceCommit"]) self.assertTrue(pin["servedPackageVerified"]) self.assertEqual(["OpenV2"], self.policy["unchangedGates"]) self.assertEqual({"v1Admission": False, "receiverStateImport": False}, diff --git a/tools/v2-ci-ordinary-observe.py b/tools/v2-ci-ordinary-observe.py index 547c00d..3d46994 100644 --- a/tools/v2-ci-ordinary-observe.py +++ b/tools/v2-ci-ordinary-observe.py @@ -73,7 +73,8 @@ def current_file(repository: str, path: str, revision: str) -> bytes: raise QUALIFICATION.Refusal(f"current protected file malformed: {path}") from error -def current_authority(repository: str, policy: dict, policy_path: pathlib.Path = POLICY_PATH) -> None: +def current_authority(repository: str, policy: dict, policy_path: pathlib.Path = POLICY_PATH, + *, main_storage: bool = False) -> None: """Fence a queued or rerun job against the present protected source.""" before = api(f"repos/{repository}/git/ref/heads/main") revision = before.get("object", {}).get("sha") if isinstance(before, dict) else None @@ -84,6 +85,8 @@ def current_authority(repository: str, policy: dict, policy_path: pathlib.Path = paths.append("policy/v2-ci-ordinary-settlement-rehearsal-anchor.json" if policy_path == REHEARSAL_POLICY_PATH else "policy/v2-ci-ordinary-settlement-anchor.json") + if main_storage: + paths.append("policy/v2-ci-ordinary-settlement-main-anchor.json") for path in paths: if current_file(repository, path, revision) != (ROOT / path).read_bytes(): raise QUALIFICATION.Refusal(f"current protected authority changed: {path}") @@ -163,7 +166,8 @@ def equivalent_tree(repository: str, head: str, source: str, base: str) -> str: return head_tree -def observe(environ: dict[str, str], rehearsal: bool = False) -> dict: +def observe(environ: dict[str, str], rehearsal: bool = False, + *, main_storage: bool = False) -> dict: policy_path = REHEARSAL_POLICY_PATH if rehearsal else POLICY_PATH policy = QUALIFICATION.read_json(str(policy_path)) source = environ.get("GITHUB_SHA", "") @@ -204,7 +208,7 @@ def observe(environ: dict[str, str], rehearsal: bool = False) -> dict: capture_output=True, text=True, cwd=ROOT).stdout.strip() if checkout != source: raise QUALIFICATION.Refusal("checkout differs from triggering source") - current_authority(policy["repository"], policy, policy_path) + current_authority(policy["repository"], policy, policy_path, main_storage=main_storage) run_id, attempt = environ.get("GITHUB_RUN_ID", ""), environ.get("GITHUB_RUN_ATTEMPT", "") if not run_id.isdecimal() or not attempt.isdecimal() or int(run_id) < 1 or int(attempt) < 1: raise QUALIFICATION.Refusal("invalid workflow run identity") @@ -343,12 +347,13 @@ def rank(check: dict) -> tuple[datetime, int]: def main() -> int: - actions = ("produce", "verify", "produce-rehearsal", "verify-rehearsal") + actions = ("produce", "verify", "verify-main", "produce-rehearsal", "verify-rehearsal") if len(sys.argv) != 3 or sys.argv[1] not in actions: - print("usage: v2-ci-ordinary-observe.py produce|verify|produce-rehearsal|verify-rehearsal RECEIPT_PATH", file=sys.stderr) + print("usage: v2-ci-ordinary-observe.py produce|verify|verify-main|produce-rehearsal|verify-rehearsal RECEIPT_PATH", file=sys.stderr) return 2 try: - actual = observe(dict(os.environ), rehearsal=sys.argv[1].endswith("-rehearsal")) + actual = observe(dict(os.environ), rehearsal=sys.argv[1].endswith("-rehearsal"), + main_storage=sys.argv[1] == "verify-main") target = pathlib.Path(sys.argv[2]) encoded = json.dumps(actual, sort_keys=True, separators=(",", ":")) + "\n" if sys.argv[1].startswith("produce"):