From 4487b6c61ca424c89c5537b91d264d74622c9931 Mon Sep 17 00:00:00 2001 From: abrichr Date: Wed, 2 Sep 2026 13:15:55 -0400 Subject: [PATCH] ci: point inactive issuers at software Ed25519 The synthetic decision workflow now checks qualification_software_ed25519.py. It stays refuse-closed: activation_state is inactive, signer_registry is null, and the job still exits 1. No secrets, no environment, no KMS, no OIDC. Add the missing issue-production-release-admission.yml that qualification_issuer.py already names. Same inactive refuse path until a signer registry exists. --- .../issue-production-release-admission.yml | 54 +++++++++++++++++++ ...thetic-qualification-evidence-decision.yml | 18 +++++-- tests/test_governance_workflows.py | 27 +++++++++- 3 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/issue-production-release-admission.yml diff --git a/.github/workflows/issue-production-release-admission.yml b/.github/workflows/issue-production-release-admission.yml new file mode 100644 index 0000000..546fd7c --- /dev/null +++ b/.github/workflows/issue-production-release-admission.yml @@ -0,0 +1,54 @@ +name: Release admission issuer + +on: + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + refuse-inactive-issuer: + permissions: + contents: read + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout the exact requested source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Verify the inactive release admission issuer interface and refuse issuance + env: + EVENT_NAME: ${{ github.event_name }} + EXPECTED_ACTIVATION_STATE: inactive + REF: ${{ github.ref }} + REPOSITORY: ${{ github.repository }} + SOURCE_COMMIT: ${{ github.sha }} + run: | + set -euo pipefail + test "$REPOSITORY" = 'OpenAdaptAI/.github' + test "$REF" = 'refs/heads/main' + test "$EVENT_NAME" = 'workflow_dispatch' + test "$(git rev-parse HEAD)" = "$SOURCE_COMMIT" + python3 scripts/qualification_issuer.py interface > "$RUNNER_TEMP/interface.json" + INTERFACE_PATH="$RUNNER_TEMP/interface.json" python3 - <<'PY' + import json + import os + from pathlib import Path + + interface = json.loads(Path(os.environ["INTERFACE_PATH"]).read_text()) + if interface.get("activation_state") != os.environ["EXPECTED_ACTIVATION_STATE"]: + raise SystemExit("the release admission issuer interface is not inactive") + if interface.get("accepted_evidence_class") != "remote-safe-synthetic": + raise SystemExit("the release admission issuer evidence class differs") + registry = json.loads(Path("evidence-registry.json").read_text()) + if registry.get("signer_registry") is not None: + raise SystemExit("a live signer registry is present; keep this issuer refuse-closed") + PY + echo '::error::The release admission issuer is installed but inactive.' >&2 + echo 'The signer registry, authority state, revocation state, and shared one-use storage must exist before activation.' >&2 + exit 1 diff --git a/.github/workflows/issue-synthetic-qualification-evidence-decision.yml b/.github/workflows/issue-synthetic-qualification-evidence-decision.yml index 27a86af..4eca900 100644 --- a/.github/workflows/issue-synthetic-qualification-evidence-decision.yml +++ b/.github/workflows/issue-synthetic-qualification-evidence-decision.yml @@ -21,10 +21,11 @@ jobs: with: persist-credentials: false - - name: Verify the inactive KMS issuer interface and refuse issuance + - name: Verify the inactive software issuer interface and refuse issuance env: EVENT_NAME: ${{ github.event_name }} EXPECTED_ACTIVATION_STATE: inactive + EXPECTED_GITHUB_SECRET_NAME: OPENADAPT_QUALIFICATION_ED25519_PRIVATE_KEY REF: ${{ github.ref }} REPOSITORY: ${{ github.repository }} SOURCE_COMMIT: ${{ github.sha }} @@ -34,7 +35,7 @@ jobs: test "$REF" = 'refs/heads/main' test "$EVENT_NAME" = 'workflow_dispatch' test "$(git rev-parse HEAD)" = "$SOURCE_COMMIT" - python3 scripts/qualification_kms_ed25519.py interface > "$RUNNER_TEMP/interface.json" + python3 scripts/qualification_software_ed25519.py interface > "$RUNNER_TEMP/interface.json" INTERFACE_PATH="$RUNNER_TEMP/interface.json" python3 - <<'PY' import json import os @@ -42,10 +43,17 @@ jobs: interface = json.loads(Path(os.environ["INTERFACE_PATH"]).read_text()) if interface.get("activation_state") != os.environ["EXPECTED_ACTIVATION_STATE"]: - raise SystemExit("the KMS issuer interface is not inactive") + raise SystemExit("the software issuer interface is not inactive") if interface.get("allowed_evidence_class") != "remote-safe-synthetic": - raise SystemExit("the KMS issuer evidence class differs") + raise SystemExit("the software issuer evidence class differs") + if interface.get("aws_required") is not False: + raise SystemExit("the software issuer must not require AWS") + if interface.get("github_secret_name") != os.environ["EXPECTED_GITHUB_SECRET_NAME"]: + raise SystemExit("the software issuer GitHub secret name differs") + registry = json.loads(Path("evidence-registry.json").read_text()) + if registry.get("signer_registry") is not None: + raise SystemExit("a live signer registry is present; keep this issuer refuse-closed") PY echo '::error::The synthetic qualification decision issuer is installed but inactive.' >&2 - echo 'KMS/OIDC, the protected environment, the signer registry, revocation state, and shared one-use storage must exist before activation.' >&2 + echo 'The GitHub Ed25519 secret can sign only after a published signer registry exists. Keep this issuer refuse-closed until then. This path does not use KMS or OIDC.' >&2 exit 1 diff --git a/tests/test_governance_workflows.py b/tests/test_governance_workflows.py index bb3a3ac..ce18cb1 100644 --- a/tests/test_governance_workflows.py +++ b/tests/test_governance_workflows.py @@ -25,11 +25,15 @@ "interface_command": "python3 scripts/qualification_issuer.py interface", "interface_label": "admission issuer", }, + "issue-production-release-admission.yml": { + "interface_command": "python3 scripts/qualification_issuer.py interface", + "interface_label": "release admission issuer", + }, "issue-synthetic-qualification-evidence-decision.yml": { "interface_command": ( - "python3 scripts/qualification_kms_ed25519.py interface" + "python3 scripts/qualification_software_ed25519.py interface" ), - "interface_label": "KMS issuer", + "interface_label": "software issuer", }, } @@ -174,6 +178,25 @@ def test_inactive_issuer_workflows_cannot_issue_or_acquire_authority(self) -> No for marker in forbidden: self.assertNotIn(marker, content, f"{filename}: {marker}") + def test_synthetic_decision_issuer_uses_software_ed25519_not_kms(self) -> None: + content = _read( + ".github/workflows/issue-synthetic-qualification-evidence-decision.yml" + ) + self.assertIn( + "python3 scripts/qualification_software_ed25519.py interface", + content, + ) + self.assertNotIn("qualification_kms_ed25519.py", content) + self.assertNotIn("KMS/OIDC", content) + self.assertIn("aws_required", content) + self.assertIn( + "OPENADAPT_QUALIFICATION_ED25519_PRIVATE_KEY", + content, + ) + self.assertNotIn("${{ secrets.", content) + self.assertNotIn("id-token:", content) + self.assertIn("signer_registry", content) + def test_lifecycle_workflows_are_app_only_review_paths(self) -> None: for filename, environment in LIFECYCLE_WORKFLOWS.items(): content = _read(f".github/workflows/{filename}")