From e96ef197cfecb7f08d9cc55d758a05ebe0b7194a Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 5 Sep 2026 16:39:02 +0200 Subject: [PATCH 1/2] feat: add API-only preflight signing delivery and rotation acceptance Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102 --- .../state-hub/templates/deployment.yaml | 8 +++ .../apps/charts/state-hub/values.yaml | 5 ++ tests/test_preflight_signing_rotation.py | 38 ++++++++++++++ ...88-preflight-signing-runtime-acceptance.md | 50 +++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 tests/test_preflight_signing_rotation.py create mode 100644 workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md diff --git a/deploy/railiance/apps/charts/state-hub/templates/deployment.yaml b/deploy/railiance/apps/charts/state-hub/templates/deployment.yaml index 1e4988c..41c1faf 100644 --- a/deploy/railiance/apps/charts/state-hub/templates/deployment.yaml +++ b/deploy/railiance/apps/charts/state-hub/templates/deployment.yaml @@ -97,6 +97,14 @@ spec: readOnly: true {{- end }} env: + {{- if .Values.renamePreflight.enabled }} + - name: REPOSITORY_RENAME_PREFLIGHT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.renamePreflight.secretName | quote }} + key: REPOSITORY_RENAME_PREFLIGHT_SECRET + optional: false + {{- end }} - name: HOME value: /tmp {{- if .Values.sweep.enabled }} diff --git a/deploy/railiance/apps/charts/state-hub/values.yaml b/deploy/railiance/apps/charts/state-hub/values.yaml index bc7bf9f..97bb087 100644 --- a/deploy/railiance/apps/charts/state-hub/values.yaml +++ b/deploy/railiance/apps/charts/state-hub/values.yaml @@ -190,3 +190,8 @@ sweep: hostname: "" hostPath: /home/tegwick sshHostPath: /home/tegwick/.ssh + +# RPF-WP-0035-T04: API-only, separately owned ESO Secret; no secret values here. +renamePreflight: + enabled: false + secretName: state-hub-rename-preflight diff --git a/tests/test_preflight_signing_rotation.py b/tests/test_preflight_signing_rotation.py new file mode 100644 index 0000000..ae0e3d6 --- /dev/null +++ b/tests/test_preflight_signing_rotation.py @@ -0,0 +1,38 @@ +"""Single-key invalidation guarantees needed by the platform rotation fence.""" +from datetime import datetime, timedelta, timezone + +import pytest + +from api.config import settings +from api.services.repository_rename import ( + RenamePreconditionFailed, RenameServiceUnavailable, + _sign_preflight, _verify_preflight_token, +) + + +def test_rotation_invalidates_predecessor_and_accepts_successor(monkeypatch): + payload = {'expires_at': (datetime.now(timezone.utc) + timedelta(minutes=15)).isoformat()} + monkeypatch.setattr(settings, 'repository_rename_preflight_secret', 'fixture-old-key') + predecessor = _sign_preflight(payload) + assert _verify_preflight_token(predecessor) == payload + monkeypatch.setattr(settings, 'repository_rename_preflight_secret', 'fixture-new-key') + with pytest.raises(RenamePreconditionFailed): + _verify_preflight_token(predecessor) + assert _verify_preflight_token(_sign_preflight(payload)) == payload + + +def test_tamper_expiry_and_no_key_fail_closed(monkeypatch): + monkeypatch.setattr(settings, 'repository_rename_preflight_secret', 'fixture-key') + future = {'expires_at': (datetime.now(timezone.utc) + timedelta(minutes=15)).isoformat()} + token = _sign_preflight(future) + encoded, signature = token.split('.') + with pytest.raises(RenamePreconditionFailed): + _verify_preflight_token(encoded + '.' + ('A' if signature[0] != 'A' else 'B') + signature[1:]) + past = {'expires_at': (datetime.now(timezone.utc) - timedelta(seconds=1)).isoformat()} + with pytest.raises(RenamePreconditionFailed, match='expired'): + _verify_preflight_token(_sign_preflight(past)) + monkeypatch.setattr(settings, 'repository_rename_preflight_secret', None) + with pytest.raises(RenameServiceUnavailable): + _sign_preflight(future) + with pytest.raises(RenameServiceUnavailable): + _verify_preflight_token(token) diff --git a/workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md b/workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md new file mode 100644 index 0000000..b9f5eb6 --- /dev/null +++ b/workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md @@ -0,0 +1,50 @@ +--- +id: STATE-WP-0088 +type: workplan +title: "Accept the platform preflight signing lane in the State Hub API" +domain: infotech +repo: state-hub +status: active +owner: codex +topic_slug: infotech +created: "2026-09-05" +updated: "2026-09-05" +related: + - RPF-WP-0035 + - STATE-WP-0085 + - FLEX-WP-0020 +quality_dor: DoR-Ok +quality_dor_note: "Exact platform design and user-requested task reviewed against live primary; bounded API-only delivery and controlled-outage rotation fence." +--- + +## Wire and validate API-only delivery + +```task +id: STATE-WP-0088-T01 +status: done +priority: high +``` + +Chart opt-in `renamePreflight.enabled` adds a required explicit Secret ref only +to the API container. Default disabled; no plaintext chart values or shared env +Secret ownership. Helm rendering proves MCP/migration exclusion. Existing +repository-rename API tests pass (13 tests). Platform owns CCR-2026-0015 and ESO. + +## Accept live signing and fenced rotation + +```task +id: STATE-WP-0088-T02 +status: progress +priority: high +``` + +Fresh live flex-auth -> access-engine preflight returns exactly the +`preflight_signing_unavailable` blocker. Target is primary/railiance01, +namespace/release/deployment state-hub, current API SA state-hub and one replica. +After platform custody verification, enable the chart, prove API-only delivery, +all-replica key equality, health and non-mutating signed preflight. Then stop all +API replicas (including terminating pods), rotate with CAS through platform, +wait ESO, restart and prove predecessor invalidation and forward recovery. +Runbook: railiance-platform/docs/credential-lane-designs/state-hub-preflight-activation.md. +No repository rename is in scope. Live completion is pending attended OpenBao +OIDC/MFA; ambient session returned 403. From 74c687c6bcdfacb965b2f513858e464046d91357 Mon Sep 17 00:00:00 2001 From: repo-manager Date: Sat, 5 Sep 2026 16:39:04 +0200 Subject: [PATCH 2/2] repo.work.assign_missing_identifiers source: repo-manager reason: deterministic projection registration Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102 --- .../STATE-WP-0088-preflight-signing-runtime-acceptance.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md b/workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md index b9f5eb6..4d602da 100644 --- a/workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md +++ b/workplans/STATE-WP-0088-preflight-signing-runtime-acceptance.md @@ -15,6 +15,7 @@ related: - FLEX-WP-0020 quality_dor: DoR-Ok quality_dor_note: "Exact platform design and user-requested task reviewed against live primary; bounded API-only delivery and controlled-outage rotation fence." +state_hub_workstream_id: "22f2d7dc-5766-5b09-9f18-12abd4b5512b" --- ## Wire and validate API-only delivery @@ -23,6 +24,7 @@ quality_dor_note: "Exact platform design and user-requested task reviewed agains id: STATE-WP-0088-T01 status: done priority: high +state_hub_task_id: "1d6a3deb-588c-5b3f-a334-8ab5ff9a8ee6" ``` Chart opt-in `renamePreflight.enabled` adds a required explicit Secret ref only @@ -36,6 +38,7 @@ repository-rename API tests pass (13 tests). Platform owns CCR-2026-0015 and ESO id: STATE-WP-0088-T02 status: progress priority: high +state_hub_task_id: "55b32d0d-d30a-50c2-bae0-d6e3a357e6d1" ``` Fresh live flex-auth -> access-engine preflight returns exactly the