Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 84 additions & 4 deletions .github/workflows/v2-ci-ordinary-settlement.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: V2 ordinary post-merge settlement (source only)
name: V2 ordinary post-merge settlement

on:
push:
Expand All @@ -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
Expand All @@ -39,10 +39,90 @@ 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:
name: ordinary-v2-preflight-${{ github.run_id }}-${{ github.run_attempt }}
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
<?xml version="1.0" encoding="utf-8"?><configuration><packageSources><clear/><add key="pinned" value="$package_dir"/></packageSources></configuration>
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
64 changes: 48 additions & 16 deletions docs/roadmaps/v2-ordinary-adoption.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -30,26 +31,57 @@ 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)

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
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.

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`,
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.
30 changes: 16 additions & 14 deletions policy/v2-ci-ordinary-settlement.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -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",
Expand All @@ -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": [
{
Expand Down Expand Up @@ -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": [
Expand All @@ -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",
Expand Down
35 changes: 21 additions & 14 deletions tests/v2-ci-ordinary-observe/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand All @@ -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"])
Expand Down
Loading
Loading