diff --git a/.github/workflows/beta-authorization.yml b/.github/workflows/beta-authorization.yml deleted file mode 100644 index f64c53d..0000000 --- a/.github/workflows/beta-authorization.yml +++ /dev/null @@ -1,157 +0,0 @@ -name: Beta authorization - -run-name: Authorize beta plan ${{ fromJSON(inputs.authorization_request).authorization.candidate }} - -on: - workflow_dispatch: - inputs: - authorization_request: - description: Exact protected beta authorization request JSON - required: true - type: string - -permissions: {} - -concurrency: - group: beta-authorization-registry - cancel-in-progress: false - -jobs: - authorize: - name: Verify and record protected beta authorization - if: >- - github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' - runs-on: ubuntu-latest - timeout-minutes: 45 - environment: beta-authorization - permissions: - actions: read - checks: read - contents: write - issues: read - steps: - - name: Check out the protected authorization controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install changed-file quality tooling - run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - - - name: Qualify beta authorization source - run: | - python -m ruff check scripts tests - python -m yamllint .github/workflows - python -m compileall -q scripts tests - python -m unittest discover -v - python -m json.tool beta-authorization/request-schema.json >/dev/null - python -m json.tool beta-authorization/record-schema.json >/dev/null - python -m json.tool beta-authorization/evidence-schema.json >/dev/null - - - name: Canonicalize the exact authorization request - env: - AUTHORIZATION_REQUEST: ${{ inputs.authorization_request }} - run: | - python - <<'PYTHON' - import os - from pathlib import Path - - Path("requested-beta-authorization.json").write_text( - os.environ["AUTHORIZATION_REQUEST"], encoding="utf-8" - ) - PYTHON - python scripts/beta_authorization.py validate \ - requested-beta-authorization.json beta-authorization-request.json - - - name: Compare existing authorization before mutable qualification - id: compatibility - run: | - python scripts/beta_authorization.py check beta-authorization-request.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Qualify the exact intended source commits - if: ${{ steps.compatibility.outputs.status == 'new' }} - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/beta_authorization.py expected-commits \ - beta-authorization-request.json beta-authorization-expected-commits.json - python scripts/qualification_policy.py audit \ - --expected-commits beta-authorization-expected-commits.json \ - --evidence target-qualification-evidence.json - - - name: Create or compare the immutable authorization - id: record - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - git_authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$git_authorization" - GIT_CONFIG_COUNT=1 \ - GIT_CONFIG_KEY_0=http.https://github.com/.extraheader \ - GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $git_authorization" \ - python scripts/beta_authorization.py record beta-authorization-request.json \ - --qualification target-qualification-evidence.json \ - --authoritative-authorization authoritative-beta-authorization.json \ - --authoritative-evidence authoritative-beta-authorization-evidence.json \ - --authoritative-qualification authoritative-target-qualification-evidence.json \ - --actor "$GITHUB_ACTOR" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --workflow-commit "$GITHUB_SHA" \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or repair the durable GitHub Release mirror - env: - AUTHORIZATION_TAG: ${{ steps.record.outputs.tag }} - GH_TOKEN: ${{ github.token }} - RECORD_COMMIT: ${{ steps.record.outputs.commit }} - run: | - set -euo pipefail - mirror_dir="$(mktemp -d)" - trap 'rm -rf "$mirror_dir"' EXIT - if ! gh release view "$AUTHORIZATION_TAG" >/dev/null 2>&1; then - gh release create "$AUTHORIZATION_TAG" --verify-tag --prerelease \ - --title "Protected beta authorization" \ - --notes "Protected beta decision at immutable Git commit ${RECORD_COMMIT}. Stable 2.0 is not authorized." - fi - repair_asset() { - source_path="$1" - asset_name="$2" - rm -f "$mirror_dir/$asset_name" - if gh release download "$AUTHORIZATION_TAG" --pattern "$asset_name" --dir "$mirror_dir" >/dev/null 2>&1; then - cmp --silent "$source_path" "$mirror_dir/$asset_name" || { - printf 'release asset %s differs from immutable Git authority\n' "$asset_name" >&2 - exit 1 - } - else - upload_dir="$(mktemp -d)" - cp "$source_path" "$upload_dir/$asset_name" - gh release upload "$AUTHORIZATION_TAG" "$upload_dir/$asset_name" - rm -rf "$upload_dir" - fi - } - repair_asset authoritative-beta-authorization.json beta-authorization.json - repair_asset authoritative-beta-authorization-evidence.json beta-authorization-evidence.json - repair_asset authoritative-target-qualification-evidence.json target-qualification-evidence.json - - - name: Retain workflow evidence mirror - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: beta-authorization-${{ steps.record.outputs.candidate }} - path: | - authoritative-beta-authorization.json - authoritative-beta-authorization-evidence.json - authoritative-target-qualification-evidence.json - if-no-files-found: error - retention-days: 90 diff --git a/.github/workflows/beta-candidate.yml b/.github/workflows/beta-candidate.yml deleted file mode 100644 index e9df621..0000000 --- a/.github/workflows/beta-candidate.yml +++ /dev/null @@ -1,307 +0,0 @@ -name: Beta candidate - -run-name: >- - Candidate ${{ inputs.candidate_manifest && fromJSON(inputs.candidate_manifest).candidate || 'main' }} - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - candidate_manifest: - description: Exact beta candidate JSON matching candidates/schema.json - required: true - type: string - recorder_recovery_probe: - description: Fail the first recorder attempt before mutation to exercise recorder-only recovery - required: false - type: boolean - default: false - -permissions: {} - -concurrency: - group: beta-candidate-registry - cancel-in-progress: false - -jobs: - qualify: - name: Qualify immutable candidate without write authority - runs-on: ubuntu-latest - timeout-minutes: 30 - outputs: - artifact-digest: ${{ steps.privileged-handoff.outputs.artifact-digest }} - artifact-id: ${{ steps.privileged-handoff.outputs.artifact-id }} - handoff-attempt: ${{ steps.handoff.outputs.producer_attempt }} - source-run-attempt: ${{ github.run_attempt }} - source-run-id: ${{ github.run_id }} - permissions: - actions: read - attestations: read - checks: read - contents: read - steps: - - name: Check out the public control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install changed-file quality tooling - run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - - - name: Qualify the control-plane source - run: | - python -m ruff check scripts tests - python -m yamllint .github/workflows - python -m compileall -q scripts tests - python -m unittest discover -v - python -m json.tool candidates/schema.json >/dev/null - python -m json.tool candidates/verification-schema.json >/dev/null - - - name: Require qualified and protected public target commits - env: - GITHUB_TOKEN: ${{ github.token }} - QUALIFICATION_EVENT: ${{ github.event_name }} - run: | - args=(audit --evidence target-qualification-evidence.json) - if [ "$QUALIFICATION_EVENT" = push ]; then - args+=(--skip-check-runs-for github-control-plane) - fi - python scripts/qualification_policy.py "${args[@]}" - - - name: Retain beta qualification evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: beta-target-qualification-${{ github.run_id }}-${{ github.run_attempt }} - path: target-qualification-evidence.json - if-no-files-found: error - - - name: Select requested candidate - env: - DISPATCH_MANIFEST: ${{ inputs.candidate_manifest }} - run: | - if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - python - <<'PYTHON' - import os - from pathlib import Path - - Path("requested-candidate.json").write_text(os.environ["DISPATCH_MANIFEST"], encoding="utf-8") - PYTHON - else - cp candidates/main.json requested-candidate.json - fi - validation_args=() - if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - validation_args+=(--require-supported-train) - fi - python scripts/beta_candidate.py validate \ - requested-candidate.json candidate.json "${validation_args[@]}" - - - name: Reject mutation of an existing candidate identity - run: python scripts/beta_candidate.py check candidate.json - - - name: Verify public artifacts and immutable source releases - env: - GH_TOKEN: ${{ github.token }} - GITHUB_TOKEN: ${{ github.token }} - run: python scripts/beta_candidate.py verify candidate.json verification.json - - - name: Bind candidate evidence to this producing attempt - id: handoff - run: | - python scripts/handoff_recovery.py create candidate handoff.json \ - --directory . \ - --repository "$GITHUB_REPOSITORY" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --github-output "$GITHUB_OUTPUT" - - - name: Pass only candidate verification evidence to the recorder - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: beta-candidate-verification-${{ github.run_id }}-${{ github.run_attempt }} - path: | - candidate.json - verification.json - handoff.json - if-no-files-found: error - retention-days: 30 - - - name: Package the privileged recorder handoff as one immutable file - run: tar -cf candidate-recorder-handoff.tar candidate.json verification.json handoff.json - - - name: Bind the privileged recorder handoff identity and digest - id: privileged-handoff - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: candidate-recorder-handoff.tar - - record: - name: Record qualified immutable candidate from a fresh checkout - needs: qualify - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - actions: read - attestations: read - contents: write - steps: - - name: Check out a fresh qualified control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Restore isolated candidate verification evidence - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.qualify.outputs.artifact-id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-candidate-handoff - repository: ${{ github.repository }} - run-id: ${{ needs.qualify.outputs.source-run-id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-candidate-handoff - EXPECTED_ARTIFACT_DIGEST: ${{ needs.qualify.outputs.artifact-digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.qualify.outputs.artifact-id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.qualify.outputs.source-run-attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.qualify.outputs.source-run-id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Extract the validated candidate handoff - run: | - mkdir isolated-verification - tar -xf isolated-candidate-handoff/candidate-recorder-handoff.tar -C isolated-verification - - - name: Validate the selected attempt-bound handoff - run: | - python scripts/handoff_recovery.py validate candidate \ - isolated-verification/handoff.json \ - --directory isolated-verification \ - --repository "$GITHUB_REPOSITORY" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --current-attempt "$GITHUB_RUN_ATTEMPT" \ - --producer-attempt "${{ needs.qualify.outputs.handoff-attempt }}" - - - name: Rebuild and validate the authorized verification handoff - env: - DISPATCH_MANIFEST: ${{ inputs.candidate_manifest }} - run: | - if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - python - <<'PYTHON' - import os - from pathlib import Path - - Path("requested-candidate.json").write_text(os.environ["DISPATCH_MANIFEST"], encoding="utf-8") - PYTHON - else - cp candidates/main.json requested-candidate.json - fi - python scripts/beta_candidate.py validate requested-candidate.json candidate.json - python scripts/beta_candidate.py compare candidate.json isolated-verification/candidate.json - python scripts/beta_candidate.py validate-verification \ - candidate.json isolated-verification/verification.json - - - name: Exercise recorder-only recovery before any mutation - if: ${{ inputs.recorder_recovery_probe && github.run_attempt == 1 }} - run: | - echo "Recorder recovery probe stopped the first writer attempt before mutation." >&2 - exit 1 - - - name: Create or compare the immutable Git record - id: record - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - git_authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$git_authorization" - GIT_CONFIG_COUNT=1 \ - GIT_CONFIG_KEY_0=http.https://github.com/.extraheader \ - GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $git_authorization" \ - python scripts/beta_candidate.py record \ - candidate.json isolated-verification/verification.json \ - --authoritative-verification authoritative-verification.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or repair the durable GitHub Release mirror - env: - GH_TOKEN: ${{ github.token }} - CANDIDATE: ${{ steps.record.outputs.candidate }} - CANDIDATE_TAG: ${{ steps.record.outputs.tag }} - RECORD_COMMIT: ${{ steps.record.outputs.commit }} - run: | - set -euo pipefail - - if ! gh release view "$CANDIDATE_TAG" >/dev/null 2>&1; then - notes="$(mktemp)" - { - printf 'Immutable beta candidate `%s`.\n\n' "$CANDIDATE" - printf 'The authoritative Git record is commit `%s` at tag `%s`. ' "$RECORD_COMMIT" "$CANDIDATE_TAG" - printf 'The attached JSON files mirror that record for durable API queries and recovery.\n' - } > "$notes" - gh release create "$CANDIDATE_TAG" --verify-tag \ - --title "Beta candidate ${CANDIDATE}" --notes-file "$notes" - fi - - python scripts/release_asset_mirror.py repair \ - "$CANDIDATE_TAG" candidate.json candidate.json \ - --repository "$GITHUB_REPOSITORY" - python scripts/release_asset_mirror.py repair \ - "$CANDIDATE_TAG" authoritative-verification.json verification.json \ - --repository "$GITHUB_REPOSITORY" diff --git a/.github/workflows/beta-conformance-retention.yml b/.github/workflows/beta-conformance-retention.yml deleted file mode 100644 index 0c8061f..0000000 --- a/.github/workflows/beta-conformance-retention.yml +++ /dev/null @@ -1,219 +0,0 @@ -name: Beta conformance retention - -run-name: >- - Retain conformance - ${{ inputs.source_run_id || github.event.workflow_run.id }}.${{ - inputs.source_run_attempt || github.event.workflow_run.run_attempt }} - -on: - workflow_run: - workflows: [Beta conformance] - types: [completed] - workflow_dispatch: - inputs: - source_run_id: - description: Completed Beta conformance run to retain without re-executing it - required: true - type: string - source_run_attempt: - description: Exact completed attempt to retain - required: true - type: string - -permissions: - contents: read - -concurrency: - group: >- - beta-conformance-retention-${{ - inputs.source_run_id || github.event.workflow_run.id }}-${{ - inputs.source_run_attempt || github.event.workflow_run.run_attempt }} - cancel-in-progress: false - -jobs: - bind: - name: Bind completed conformance run - if: ${{ github.server_url == 'https://github.com' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - actions: read - contents: read - outputs: - source_candidate: ${{ steps.source.outputs.source_candidate }} - source_completed_at: ${{ steps.source.outputs.source_completed_at }} - source_head_sha: ${{ steps.source.outputs.source_head_sha }} - source_run_attempt: ${{ steps.source.outputs.source_run_attempt }} - source_run_id: ${{ steps.source.outputs.source_run_id }} - steps: - - name: Check out the trusted retention controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Resolve and validate the completed source run - id: source - env: - GH_TOKEN: ${{ github.token }} - REQUESTED_RUN_ATTEMPT: >- - ${{ inputs.source_run_attempt || github.event.workflow_run.run_attempt }} - REQUESTED_RUN_ID: ${{ inputs.source_run_id || github.event.workflow_run.id }} - run: | - python scripts/beta_conformance.py retention-source \ - --expected-run-id "$REQUESTED_RUN_ID" \ - --expected-run-attempt "$REQUESTED_RUN_ATTEMPT" \ - --github-output "$GITHUB_OUTPUT" - - retain: - name: Publish durable conformance evidence - needs: bind - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: beta-conformance - permissions: - actions: read - contents: write - steps: - - name: Check out the trusted retention controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Restore immutable execution plan from the completed run - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: beta-conformance-plan-${{ needs.bind.outputs.source_run_id }} - path: aggregate-input - github-token: ${{ github.token }} - run-id: ${{ needs.bind.outputs.source_run_id }} - digest-mismatch: error - - - name: Restore experiment evidence from the completed run - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - pattern: >- - beta-conformance-*-${{ needs.bind.outputs.source_run_id }}-${{ - needs.bind.outputs.source_run_attempt }} - path: evidence - github-token: ${{ github.token }} - run-id: ${{ needs.bind.outputs.source_run_id }} - digest-mismatch: error - - - name: Aggregate exact-tuple evidence - id: aggregate - run: | - python scripts/beta_conformance.py aggregate \ - aggregate-input/execution-plan.json \ - evidence \ - suite-result.json \ - release-assets \ - --contract beta-conformance/contract.json \ - --run-id "${{ needs.bind.outputs.source_run_id }}" \ - --run-attempt "${{ needs.bind.outputs.source_run_attempt }}" \ - --generated-at "${{ needs.bind.outputs.source_completed_at }}" \ - --source-candidate "${{ needs.bind.outputs.source_candidate }}" \ - --source-head-sha "${{ needs.bind.outputs.source_head_sha }}" \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or verify durable GitHub Release evidence - env: - GH_TOKEN: ${{ github.token }} - CANDIDATE: ${{ steps.aggregate.outputs.candidate }} - EVIDENCE_TAG: ${{ steps.aggregate.outputs.evidence_tag }} - OUTCOME: ${{ steps.aggregate.outputs.outcome }} - SOURCE_HEAD_SHA: ${{ needs.bind.outputs.source_head_sha }} - SOURCE_RUN_ATTEMPT: ${{ needs.bind.outputs.source_run_attempt }} - SOURCE_RUN_ID: ${{ needs.bind.outputs.source_run_id }} - run: | - set -euo pipefail - download_dir="$(mktemp -d)" - trap 'rm -rf "$download_dir"' EXIT - - tag_endpoint="/repos/$GITHUB_REPOSITORY/git/ref/tags/$EVIDENCE_TAG" - validate_evidence_ref() { - ref_sha="$(python - <<'PYTHON' - import json - import re - from pathlib import Path - - ref = json.loads(Path("evidence-ref.json").read_bytes()) - sha = ref.get("object", {}).get("sha") if isinstance(ref, dict) else None - if not isinstance(sha, str) or not re.fullmatch(r"[0-9a-f]{40}", sha): - raise SystemExit("conformance evidence ref does not name a commit") - print(sha) - PYTHON - )" - gh api "/repos/$GITHUB_REPOSITORY/compare/${ref_sha}...${GITHUB_SHA}" \ - > evidence-ref-comparison.json - python scripts/beta_conformance.py retention-ref \ - evidence-ref.json evidence-ref-comparison.json \ - --expected-tag "$EVIDENCE_TAG" --source-sha "$SOURCE_HEAD_SHA" \ - --controller-sha "$GITHUB_SHA" - } - - if ! gh release view "$EVIDENCE_TAG" >/dev/null 2>&1; then - notes="$(mktemp)" - create_log="$(mktemp)" - { - printf 'Beta conformance `%s` for immutable candidate `%s`.\n\n' "$OUTCOME" "$CANDIDATE" - printf 'Every attached experiment result binds the seven-artifact tuple, source and distribution ' - printf 'identities, runner revision, owning contract, retry record, and bounded diagnostics.\n' - } > "$notes" - for attempt in 1 2 3; do - create_target=(--target "$GITHUB_SHA") - if gh api "$tag_endpoint" > evidence-ref.json 2>/dev/null; then - validate_evidence_ref - create_target=(--verify-tag) - fi - if gh release create "$EVIDENCE_TAG" "${create_target[@]}" \ - --title "Beta conformance ${CANDIDATE} (${SOURCE_RUN_ID}.${SOURCE_RUN_ATTEMPT})" \ - --notes-file "$notes" --latest=false >"$create_log" 2>&1; then - break - fi - if gh release view "$EVIDENCE_TAG" >/dev/null 2>&1; then - break - fi - if [ "$attempt" -eq 3 ]; then - cat "$create_log" >&2 - exit 1 - fi - printf 'release creation was denied; retrying attempt %s/3\n' "$((attempt + 1))" >&2 - sleep "$((attempt * 5))" - done - fi - - gh api "$tag_endpoint" > evidence-ref.json - validate_evidence_ref - - for asset in release-assets/*.json; do - name="$(basename "$asset")" - if gh release download "$EVIDENCE_TAG" --pattern "$name" --dir "$download_dir" >/dev/null 2>&1; then - cmp --silent "$asset" "$download_dir/$name" || { - printf 'durable evidence asset %s differs from the existing release\n' "$name" >&2 - exit 1 - } - else - gh release upload "$EVIDENCE_TAG" "$asset" - fi - done - - - name: Retain Actions mirror - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: >- - beta-conformance-suite-${{ needs.bind.outputs.source_run_id }}-${{ - needs.bind.outputs.source_run_attempt }} - path: release-assets/*.json - if-no-files-found: error - retention-days: 90 diff --git a/.github/workflows/beta-conformance.yml b/.github/workflows/beta-conformance.yml deleted file mode 100644 index 97fc6ad..0000000 --- a/.github/workflows/beta-conformance.yml +++ /dev/null @@ -1,180 +0,0 @@ -name: Beta conformance - -run-name: Conformance ${{ fromJSON(inputs.candidate_manifest).candidate }} - -on: - workflow_dispatch: - inputs: - candidate_manifest: - description: Canonical immutable candidate JSON matching candidates/schema.json - required: true - type: string - injected_failure_experiment: - description: Exercise deterministic distribution-identity failure retention without retry - required: true - default: none - type: choice - options: - - none - - replay - - polyglot - - heartbeats - - signals-queries - injected_canary_failure_experiment: - description: Exercise deterministic synthetic-credential redaction retention without retry - required: true - default: none - type: choice - options: - - none - - replay - - polyglot - - heartbeats - - signals-queries - -permissions: - contents: read - -concurrency: - group: beta-conformance-${{ github.run_id }} - cancel-in-progress: false - -jobs: - prepare: - name: Bind immutable candidate - runs-on: ubuntu-latest - timeout-minutes: 10 - outputs: - candidate: ${{ steps.plan.outputs.candidate }} - experiments: ${{ steps.plan.outputs.experiments }} - manifest_sha256: ${{ steps.plan.outputs.manifest_sha256 }} - steps: - - name: Check out the public control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Fetch immutable candidate records - if: ${{ github.run_attempt == 1 }} - run: git fetch --force --tags origin 'refs/tags/beta-candidate/*:refs/tags/beta-candidate/*' - - - name: Write requested candidate without shell interpolation - env: - CANDIDATE_MANIFEST: ${{ inputs.candidate_manifest }} - run: | - python - <<'PYTHON' - import os - from pathlib import Path - - Path("requested-candidate.json").write_text(os.environ["CANDIDATE_MANIFEST"], encoding="utf-8") - PYTHON - - - name: Bind candidate, runtime dependencies, runner, and experiment set once - if: ${{ github.run_attempt == 1 }} - run: | - python scripts/beta_conformance.py prepare requested-candidate.json execution-plan.json \ - --contract beta-conformance/contract.json \ - --runner-revision "$GITHUB_SHA" - - - name: Restore first-attempt immutable execution plan - if: ${{ github.run_attempt > 1 }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: beta-conformance-plan-${{ github.run_id }} - path: . - - - name: Validate the retained plan and expose the experiment set - id: plan - run: | - python scripts/beta_conformance.py restore-plan execution-plan.json requested-candidate.json \ - --contract beta-conformance/contract.json \ - --runner-revision "$GITHUB_SHA" \ - --github-output "$GITHUB_OUTPUT" - - - name: Retain the first-attempt execution plan for every recovery path - if: ${{ github.run_attempt == 1 }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: beta-conformance-plan-${{ github.run_id }} - path: execution-plan.json - if-no-files-found: error - retention-days: 30 - - conformance: - name: ${{ matrix.experiment }} exact artifacts - needs: prepare - runs-on: ubuntu-latest - timeout-minutes: 105 - strategy: - fail-fast: false - matrix: - experiment: ${{ fromJSON(needs.prepare.outputs.experiments) }} - steps: - - name: Check out the public conformance wrapper - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Restore immutable execution plan - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: beta-conformance-plan-${{ github.run_id }} - path: conformance-input - - - name: Extract orchestration and fixtures from exact server image - run: | - python scripts/beta_conformance.py extract \ - conformance-input/execution-plan.json \ - "$RUNNER_TEMP/published-server" \ - "$RUNNER_TEMP/extraction.json" - - - name: Run time-bounded isolated experiment - id: execute - continue-on-error: true - env: - EXPERIMENT: ${{ matrix.experiment }} - INJECTED_CANARY_FAILURE_EXPERIMENT: ${{ inputs.injected_canary_failure_experiment }} - INJECTED_FAILURE_EXPERIMENT: ${{ inputs.injected_failure_experiment }} - DOCKER_CONFIG: ${{ runner.temp }}/docker-config - run: | - mkdir -p "$DOCKER_CONFIG" "$RUNNER_TEMP/result" - injection=() - if [ "$INJECTED_CANARY_FAILURE_EXPERIMENT" = "$EXPERIMENT" ]; then - injection+=(--inject-product-failure) - elif [ "$INJECTED_FAILURE_EXPERIMENT" = "$EXPERIMENT" ]; then - injection+=(--inject-identity-failure) - fi - python scripts/beta_conformance.py run \ - conformance-input/execution-plan.json \ - "$EXPERIMENT" \ - "$RUNNER_TEMP/published-server" \ - "$RUNNER_TEMP/result" \ - --contract beta-conformance/contract.json \ - "${injection[@]}" - - - name: Retain bounded experiment evidence for aggregation - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: beta-conformance-${{ matrix.experiment }}-${{ github.run_id }}-${{ github.run_attempt }} - path: ${{ runner.temp }}/result/experiment-result.json - if-no-files-found: error - retention-days: 30 - - - name: Clean experiment Docker resources - if: always() - run: docker system prune --all --force --volumes - - - name: Keep product and runner failures red - if: always() - env: - EXECUTION_OUTCOME: ${{ steps.execute.outcome }} - run: test "$EXECUTION_OUTCOME" = success diff --git a/.github/workflows/beta-continuity-resolution.yml b/.github/workflows/beta-continuity-resolution.yml deleted file mode 100644 index f827a2a..0000000 --- a/.github/workflows/beta-continuity-resolution.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Beta continuity resolution - -run-name: >- - Retain continuity resolution from - ${{ inputs.source_run_id || github.event.workflow_run.id }}.${{ - inputs.source_run_attempt || github.event.workflow_run.run_attempt }} - -on: - workflow_run: - workflows: [Beta candidate] - types: [completed] - workflow_dispatch: - inputs: - source_run_id: - description: Completed Beta candidate push run to retain - required: true - type: string - source_run_attempt: - description: Exact completed attempt to retain - required: true - type: string - -permissions: - contents: read - -concurrency: - group: beta-continuity-successor-resolution - cancel-in-progress: false - -jobs: - bind: - name: Bind completed Beta candidate qualification - if: ${{ github.server_url == 'https://github.com' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - actions: read - contents: read - outputs: - source_head_sha: ${{ steps.source.outputs.source_head_sha }} - source_run_attempt: ${{ steps.source.outputs.source_run_attempt }} - source_run_id: ${{ steps.source.outputs.source_run_id }} - steps: - - name: Check out the trusted resolution controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Resolve and validate the completed qualification run - id: source - env: - GH_TOKEN: ${{ github.token }} - REQUESTED_RUN_ATTEMPT: >- - ${{ inputs.source_run_attempt || github.event.workflow_run.run_attempt }} - REQUESTED_RUN_ID: ${{ inputs.source_run_id || github.event.workflow_run.id }} - run: | - python scripts/beta_continuity.py resolution-source \ - --expected-run-id "$REQUESTED_RUN_ID" \ - --expected-run-attempt "$REQUESTED_RUN_ATTEMPT" \ - --github-output "$GITHUB_OUTPUT" - - record: - name: Publish the resolution from qualified source - needs: bind - if: >- - github.ref == 'refs/heads/main' && - github.sha == needs.bind.outputs.source_head_sha - runs-on: ubuntu-latest - timeout-minutes: 10 - environment: beta-product-work - permissions: - actions: read - contents: write - steps: - - name: Check out the exact qualified controller revision - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - ref: ${{ needs.bind.outputs.source_head_sha }} - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Retain the exact qualified continuity successor resolution - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/beta_continuity.py record-resolution \ - release-plans/continuity-successor-selection.json \ - --source-sha "${{ needs.bind.outputs.source_head_sha }}" \ - --run-id "${{ needs.bind.outputs.source_run_id }}" \ - --run-attempt "${{ needs.bind.outputs.source_run_attempt }}" diff --git a/.github/workflows/beta-continuity.yml b/.github/workflows/beta-continuity.yml deleted file mode 100644 index c4b23fe..0000000 --- a/.github/workflows/beta-continuity.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: Beta continuity - -run-name: Continue ${{ inputs.plan_tag || 'scheduled workspace-unavailable beta drill' }} - -on: - push: - branches: [main] - paths: - - .github/workflows/beta-continuity.yml - - .github/workflows/beta-conformance.yml - - .github/workflows/beta-conformance-retention.yml - - beta-continuity/** - - qualification/** - - release-plans/continuity-resolution-schema.json - - release-plans/continuity-successor-selection.json - - scripts/beta_continuity.py - - scripts/component_release_recovery.py - - scripts/qualification_policy.py - - scripts/release_plan.py - - tests/test_beta_continuity.py - - tests/test_component_release_recovery.py - - tests/test_qualification_policy.py - - tests/test_release_plan.py - schedule: - - cron: '7,17,27,37,47,57 * * * *' - workflow_dispatch: - inputs: - plan_tag: - description: Exact immutable release plan accepted by this continuity drill - required: false - type: string - -permissions: - actions: read - checks: read - contents: read - issues: read - -concurrency: - group: workspace-unavailable-beta-continuity - cancel-in-progress: false - -jobs: - advance: - name: Advance only from durable GitHub authority - if: >- - github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' - runs-on: ubuntu-latest - timeout-minutes: 30 - environment: beta-product-work - permissions: - actions: write - checks: read - contents: write - issues: read - steps: - - name: Check out the public control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install contract validators - run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - - - name: Qualify continuity controller source - run: | - python -m ruff check scripts tests - python -m yamllint .github/workflows - python -m compileall -q scripts tests - python -m unittest -v \ - tests.test_beta_continuity \ - tests.test_component_release_recovery \ - tests.test_qualification_policy \ - tests.test_release_plan - python -m jsonschema -i beta-continuity/config.json beta-continuity/schema.json - python -m json.tool release-plans/continuity-successor-selection.json >/dev/null - - - name: Select or recover the exact drill plan - id: plan - continue-on-error: true - env: - CALLBACK_PLAN_TAG: ${{ inputs.plan_tag }} - GITHUB_TOKEN: ${{ github.token }} - run: | - callback_plan=() - if [ -n "$CALLBACK_PLAN_TAG" ]; then - callback_plan+=(--expected-plan-tag "$CALLBACK_PLAN_TAG") - fi - python scripts/beta_continuity.py plan \ - beta-continuity/config.json \ - release-plan.json \ - expected-qualification-commits.json \ - continuity-state.json \ - "${callback_plan[@]}" \ - --github-output "$GITHUB_OUTPUT" - - - name: Route discovered release dependencies to their owning repositories - if: ${{ steps.plan.outcome == 'failure' }} - env: - BETA_PRODUCT_WORK_TOKEN: ${{ secrets.BETA_PRODUCT_WORK_TOKEN }} - run: | - python - <<'PYTHON' - import json - from pathlib import Path - - state = json.loads(Path("continuity-state.json").read_text(encoding="utf-8")) - if state.get("outcome") != "blocked": - raise SystemExit("continuity planning failed without focused blocker evidence") - PYTHON - python scripts/beta_continuity.py route-blockers \ - beta-continuity/config.json continuity-state.json - - - name: Qualify the exact seven target commits before acceptance - if: ${{ steps.plan.outputs.needs_qualification == 'true' }} - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/qualification_policy.py audit \ - --expected-commits expected-qualification-commits.json \ - --evidence target-qualification-evidence.json - - - name: Advance one durable continuity phase - id: advance - if: ${{ steps.plan.outcome == 'success' }} - env: - BETA_PRODUCT_WORK_TOKEN: ${{ secrets.BETA_PRODUCT_WORK_TOKEN }} - GITHUB_TOKEN: ${{ github.token }} - run: | - qualification=() - if [ -f target-qualification-evidence.json ]; then - qualification+=(--qualification target-qualification-evidence.json) - fi - python scripts/beta_continuity.py advance \ - beta-continuity/config.json release-plan.json continuity-state.json \ - "${qualification[@]}" --github-output "$GITHUB_OUTPUT" - - - name: Retain the current GitHub observation - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: beta-continuity-${{ github.run_id }}-${{ github.run_attempt }} - path: | - continuity-state.json - expected-qualification-commits.json - release-plan.json - target-qualification-evidence.json - if-no-files-found: warn - retention-days: 90 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..924ddb8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: Repository checks + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: repository-checks-${{ github.ref }} + cancel-in-progress: true + +jobs: + contracts: + name: Shared contract tests + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: "3.13" + + - name: Install Python test dependencies + run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 + + - name: Test retained shared contracts + run: >- + python -m unittest + tests.test_regression_corpus + tests.test_regression_evidence_schema + tests.test_visual_evidence + + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: 24 + cache: npm + + - name: Install Node dependencies + run: npm ci + + - name: Test browser evidence boundary + run: npm run test:visual-capture diff --git a/.github/workflows/current-release-plan.yml b/.github/workflows/current-release-plan.yml deleted file mode 100644 index fcddf07..0000000 --- a/.github/workflows/current-release-plan.yml +++ /dev/null @@ -1,328 +0,0 @@ -name: Current release plan - -run-name: Record and verify the current completed product train - -on: - push: - branches: - - main - paths: - - '.github/workflows/current-release-plan.yml' - - 'product-train/**' - - 'release-plans/**' - - 'scripts/beta_candidate.py' - - 'scripts/component_release_recovery.py' - - 'scripts/current_plan_publication.py' - - 'scripts/product_train.py' - - 'scripts/release_plan.py' - workflow_dispatch: - -permissions: {} - -jobs: - authorize: - name: Await explicit current-plan publication approval - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 5 - environment: beta-authorization - permissions: - contents: read - outputs: - handoff: ${{ steps.approval.outputs.handoff }} - producer-attempt: ${{ steps.approval.outputs.producer-attempt }} - steps: - - name: Check out the protected current-plan controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - ref: ${{ github.sha }} - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Bind protected approval to this workflow run - id: approval - run: | - python scripts/current_plan_publication.py create-approved-writer-handoff \ - --repository "$GITHUB_REPOSITORY" \ - --ref "$GITHUB_REF" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --github-output "$GITHUB_OUTPUT" - - record: - name: Record protected current-plan and completion authority - needs: authorize - if: >- - github.ref == 'refs/heads/main' && - needs.authorize.result == 'success' - runs-on: ubuntu-latest - timeout-minutes: 45 - concurrency: - group: release-plan-registry - cancel-in-progress: false - permissions: - actions: read - attestations: read - contents: write - steps: - - name: Check out the public control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Validate aggregate current-plan publication authority - env: - APPROVED_WRITER_HANDOFF: ${{ needs.authorize.outputs.handoff }} - APPROVAL_PRODUCER_ATTEMPT: ${{ needs.authorize.outputs.producer-attempt }} - run: | - python scripts/current_plan_publication.py validate-runtime \ - --repository "$GITHUB_REPOSITORY" \ - --ref "$GITHUB_REF" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" - python scripts/current_plan_publication.py validate-approved-writer-handoff \ - --repository "$GITHUB_REPOSITORY" \ - --ref "$GITHUB_REF" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --current-attempt "$GITHUB_RUN_ATTEMPT" \ - --producer-attempt "$APPROVAL_PRODUCER_ATTEMPT" \ - --handoff "$APPROVED_WRITER_HANDOFF" - - - name: Install changed-file quality tooling - run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - - - name: Qualify release-plan tooling and structured authority - run: | - python -m ruff check scripts tests - python -m yamllint .github/workflows - python -m compileall -q scripts tests - python -m unittest discover -v - python -m json.tool product-train/current.json >/dev/null - python -m json.tool product-train/sdk-server-qualification.json >/dev/null - python -m json.tool release-plans/current.json >/dev/null - python -m json.tool release-plans/current-source-preparation.json >/dev/null - python -m jsonschema -i product-train/current.json product-train/schema.json - python -m jsonschema \ - -i product-train/sdk-server-qualification.json \ - product-train/sdk-server-qualification-schema.json - python -m jsonschema -i release-plans/current.json release-plans/schema.json - python -m jsonschema \ - -i release-plans/current-source-preparation.json \ - release-plans/source-preparation-schema.json - - - name: Bind the exact supported train to one current plan - id: current - run: | - python scripts/release_plan.py materialize-current \ - release-plans/current.json \ - current-release-plan.json current-candidate.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Read an already-completed immutable current authority - id: existing - run: | - python scripts/release_plan.py current-completion \ - current-release-plan.json \ - --authoritative-plan authoritative-release-plan.json \ - --authoritative-preparation authoritative-release-preparation.json \ - --authoritative-completion authoritative-release-candidate.json \ - --authoritative-verification authoritative-verification.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Verify every existing public source and distribution identity - if: steps.existing.outputs.status != 'existing' - env: - GH_TOKEN: ${{ github.token }} - GITHUB_TOKEN: ${{ github.token }} - run: python scripts/beta_candidate.py verify current-candidate.json verification.json - - - name: Create or verify protected beta authorization - id: authorization - if: >- - steps.existing.outputs.status != 'existing' && - steps.current.outputs.channel == 'beta' - run: | - python scripts/release_plan.py record-current-authorization \ - current-release-plan.json \ - --authoritative-authorization authoritative-beta-authorization.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Wait for protected authorization to become publicly observable - if: >- - steps.existing.outputs.status != 'existing' && - steps.current.outputs.channel == 'beta' - env: - AUTHORIZATION_TAG: ${{ steps.authorization.outputs.tag }} - GH_TOKEN: ${{ github.token }} - run: | - for attempt in 1 2 3 4 5; do - gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$AUTHORIZATION_TAG" >/dev/null 2>&1 && exit 0 - sleep 2 - done - printf 'protected authorization tag did not become publicly observable\n' >&2 - exit 1 - - - name: Verify public branch, source, tag, note, and prior-plan authority - if: steps.existing.outputs.status != 'existing' - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/release_plan.py preflight \ - current-release-plan.json release-plan-preflight.json \ - --preparation release-preparation.json \ - --release-date 2026-07-28 - - - name: Create or verify the immutable release plan - id: plan - if: steps.existing.outputs.status != 'existing' - run: | - python scripts/release_plan.py record \ - current-release-plan.json release-preparation.json \ - --authoritative-plan authoritative-release-plan.json \ - --authoritative-preparation authoritative-release-preparation.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Wait for the immutable plan tag to become publicly observable - if: steps.existing.outputs.status != 'existing' - env: - GH_TOKEN: ${{ github.token }} - PLAN_TAG: ${{ steps.plan.outputs.tag }} - run: | - for attempt in 1 2 3 4 5; do - gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$PLAN_TAG" >/dev/null 2>&1 && exit 0 - sleep 2 - done - printf 'immutable plan tag did not become publicly observable\n' >&2 - exit 1 - - - name: Create or verify the durable completion record - id: completion - if: steps.existing.outputs.status != 'existing' - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/release_plan.py complete \ - authoritative-release-plan.json verification.json \ - --authoritative-completion authoritative-release-candidate.json \ - --authoritative-verification authoritative-verification.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or verify durable GitHub Release mirrors - env: - AUTHORIZATION_COMMIT: ${{ steps.authorization.outputs.commit }} - CHANNEL: ${{ steps.current.outputs.channel }} - COMPLETION_COMMIT: ${{ steps.existing.outputs.completion_commit || steps.completion.outputs.commit }} - COMPLETION_TAG: ${{ steps.existing.outputs.completion_tag || steps.completion.outputs.tag }} - GH_TOKEN: ${{ github.token }} - PLAN_COMMIT: ${{ steps.existing.outputs.plan_commit || steps.plan.outputs.commit }} - PLAN_TAG: ${{ steps.existing.outputs.plan_tag || steps.plan.outputs.tag }} - run: | - set -euo pipefail - mirror_dir="$(mktemp -d)" - upload_dir="$(mktemp -d)" - trap 'rm -rf "$mirror_dir" "$upload_dir"' EXIT - - repair_asset() { - tag="$1" - source_path="$2" - asset_name="$3" - rm -f "$mirror_dir/$asset_name" - if gh release download "$tag" --pattern "$asset_name" --dir "$mirror_dir" >/dev/null 2>&1; then - cmp --silent "$source_path" "$mirror_dir/$asset_name" || { - printf 'release asset %s differs from immutable Git authority\n' "$asset_name" >&2 - exit 1 - } - else - rm -f "$upload_dir/$asset_name" - cp "$source_path" "$upload_dir/$asset_name" - gh release upload "$tag" "$upload_dir/$asset_name" - fi - } - - if ! gh release view "$PLAN_TAG" >/dev/null 2>&1; then - notes="Protected current-plan authority at Git record ${PLAN_COMMIT}." - if [ -n "$AUTHORIZATION_COMMIT" ]; then - notes="${notes} Beta authorization ${AUTHORIZATION_COMMIT}." - fi - gh release create "$PLAN_TAG" --verify-tag --prerelease \ - --title "Release plan ${PLAN_TAG#release-plan/}" \ - --notes "$notes" - fi - repair_asset "$PLAN_TAG" authoritative-release-plan.json release-plan.json - repair_asset "$PLAN_TAG" authoritative-release-preparation.json release-preparation.json - - if ! gh release view "$COMPLETION_TAG" >/dev/null 2>&1; then - gh release create "$COMPLETION_TAG" --verify-tag --prerelease \ - --title "Completed ${CHANNEL} release candidate ${COMPLETION_TAG##*/}" \ - --notes "All declared public artifacts were verified. The authoritative Git record is ${COMPLETION_COMMIT}." - fi - repair_asset "$COMPLETION_TAG" authoritative-release-candidate.json release-candidate.json - repair_asset "$COMPLETION_TAG" authoritative-verification.json verification.json - - - name: Read and validate the published aggregate authority - env: - COMPLETION_TAG: ${{ steps.existing.outputs.completion_tag || steps.completion.outputs.tag }} - GH_TOKEN: ${{ github.token }} - PLAN_TAG: ${{ steps.existing.outputs.plan_tag || steps.plan.outputs.tag }} - run: | - set -euo pipefail - published_dir="$(mktemp -d)" - trap 'rm -rf "$published_dir"' EXIT - mkdir -p "$published_dir/plan" "$published_dir/completion" - for source_path in \ - product-train/current.json \ - product-train/sdk-server-qualification.json \ - release-plans/current.json; do - gh api \ - -H "Accept: application/vnd.github.raw+json" \ - "repos/$GITHUB_REPOSITORY/contents/$source_path?ref=main" \ - > "$published_dir/$(basename "$source_path")" - cmp --silent "$source_path" "$published_dir/$(basename "$source_path")" - done - gh release download "$PLAN_TAG" \ - --pattern release-plan.json --dir "$published_dir/plan" - gh release download "$COMPLETION_TAG" \ - --pattern release-candidate.json --dir "$published_dir/completion" - cmp --silent \ - authoritative-release-plan.json \ - "$published_dir/plan/release-plan.json" - cmp --silent \ - authoritative-release-candidate.json \ - "$published_dir/completion/release-candidate.json" - python - <<'PYTHON' - from scripts.product_train import load_product_train - - authority = load_product_train() - print(f"Published aggregate authority selects {authority['current']}") - PYTHON - - - name: Stage validated protected current-plan evidence - run: | - python scripts/release_plan.py stage-current-evidence \ - current-release-plan.json \ - "${RUNNER_TEMP}/current-release-plan-evidence" \ - --completion-status "${{ steps.existing.outputs.status }}" \ - --authoritative-plan authoritative-release-plan.json \ - --authoritative-preparation authoritative-release-preparation.json \ - --authoritative-completion authoritative-release-candidate.json \ - --authoritative-verification authoritative-verification.json \ - --preflight release-plan-preflight.json - - - name: Retain protected current-plan evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: current-release-plan-${{ github.run_id }}-${{ github.run_attempt }} - path: ${{ runner.temp }}/current-release-plan-evidence/ - if-no-files-found: error diff --git a/.github/workflows/issue-authority.yml b/.github/workflows/issue-authority.yml deleted file mode 100644 index 64c32c6..0000000 --- a/.github/workflows/issue-authority.yml +++ /dev/null @@ -1,581 +0,0 @@ -name: GitHub Issue Authority - -on: - push: - branches: [main] - paths: - - .github/ISSUE_TEMPLATE/** - - .github/workflows/issue-authority.yml - - issue-authority/** - - qualification/policy.json - - scripts/issue_authority.py - - scripts/cross_repository_lifecycle.py - issues: - types: [opened, edited, closed, reopened, labeled, unlabeled, milestoned, demilestoned] - repository_dispatch: - types: [public-lifecycle] - schedule: - - cron: '*/10 * * * *' - workflow_dispatch: - inputs: - mode: - description: Activate prerelease supersessions, apply migration, or audit authority - required: true - default: audit - type: choice - options: - - audit - - apply - - activate - -permissions: - contents: read - -concurrency: - group: >- - ${{ github.server_url == 'https://github.com' && - 'github-issue-authority-public' || - format('github-issue-authority-local-{0}-{1}', github.repository, github.event.issue.number) }} - cancel-in-progress: >- - ${{ (github.server_url == 'https://github.com' && github.event_name == 'push') || - (github.server_url != 'https://github.com' && github.event_name == 'issues') }} - -jobs: - validate: - name: Validate public intake and selective migration - if: ${{ github.event_name != 'issues' || github.server_url == 'https://github.com' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install schema validator - run: python -m pip install jsonschema==4.23.0 - - - name: Validate machine-owned authority contracts - run: | - python scripts/issue_authority.py validate \ - issue-authority/policy.json issue-authority/backlog.json - python -m json.tool issue-authority/policy.json >/dev/null - python -m json.tool issue-authority/policy-schema.json >/dev/null - python -m json.tool issue-authority/backlog.json >/dev/null - python -m json.tool issue-authority/backlog-schema.json >/dev/null - - metadata-age-audit: - name: Audit metadata-only public lifecycle age - if: >- - ${{ github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' && - (github.event_name == 'schedule' || - (github.event_name == 'workflow_dispatch' && inputs.mode == 'audit')) }} - needs: validate - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - issues: read - pull-requests: read - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install schema validator - run: python -m pip install jsonschema==4.23.0 - - - name: Audit admitted repository metadata without issue prose - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/issue_authority.py metadata-audit \ - issue-authority/policy.json issue-authority/backlog.json \ - --evidence public-issue-age-audit.json - - - name: Retain the exact public age contract artifact - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: public-issue-age-audit-${{ github.run_id }}-${{ github.run_attempt }} - path: public-issue-age-audit.json - if-no-files-found: error - retention-days: 90 - - release_completion: - name: Reconcile verified releases before issue intake - if: >- - ${{ github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' && - github.event_name == 'repository_dispatch' }} - needs: validate - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: beta-product-work - permissions: - contents: read - issues: read - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install schema validator - run: python -m pip install jsonschema==4.23.0 - - - name: Materialize the authenticated release projection - env: - PROJECTION_JSON: ${{ toJSON(github.event.client_payload.projection) }} - run: | - python - <<'PY' - import json - import os - from pathlib import Path - - projection = json.loads(os.environ["PROJECTION_JSON"]) - Path("public-lifecycle-projection.json").write_text( - json.dumps(projection, separators=(",", ":")) + "\n", - encoding="utf-8", - ) - PY - - - name: Close source-bound releases before intake can reserve them - env: - BETA_PRODUCT_WORK_TOKEN: ${{ secrets.BETA_PRODUCT_WORK_TOKEN }} - GITHUB_TOKEN: ${{ github.token }} - PROJECTION_ACTOR: ${{ github.actor }} - run: | - python scripts/issue_authority.py complete-before-intake \ - issue-authority/policy.json issue-authority/backlog.json \ - --lifecycle-projection public-lifecycle-projection.json \ - --projection-actor "$PROJECTION_ACTOR" \ - --evidence pre-intake-release-completion.json - - - name: Retain pre-intake completion evidence - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: pre-intake-release-completion-${{ github.run_id }}-${{ github.run_attempt }} - path: pre-intake-release-completion.json - if-no-files-found: error - retention-days: 90 - - intake: - name: Reconstruct vetted issue revisions with read-only access - if: >- - ${{ always() && - needs.validate.result == 'success' && - (needs.release_completion.result == 'success' || needs.release_completion.result == 'skipped') && - github.server_url == 'https://github.com' && - (github.event_name == 'push' || github.event_name == 'schedule' || - github.event_name == 'issues' || - github.event_name == 'repository_dispatch' || - (github.event_name == 'workflow_dispatch' && inputs.mode != 'activate')) }} - needs: [validate, release_completion] - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - issues: read - statuses: read - outputs: - intake_ready: ${{ steps.discover.outputs.intake_ready }} - manifest_artifact_digest: ${{ steps.intake_manifest.outputs.artifact-digest }} - manifest_artifact_id: ${{ steps.intake_manifest.outputs.artifact-id }} - source_run_attempt: ${{ github.run_attempt }} - source_run_id: ${{ github.run_id }} - trigger_approved: ${{ steps.discover.outputs.trigger_approved }} - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install schema validator - run: python -m pip install jsonschema==4.23.0 - - - name: Bind trusted approval to current issue revisions - id: discover - env: - EVENT_ACTION: ${{ github.event.action }} - EVENT_ACTOR: ${{ github.event.sender.login }} - EVENT_LABEL: ${{ github.event.label.name }} - EVENT_NAME: ${{ github.event_name }} - GITHUB_TOKEN: ${{ github.token }} - PROJECTION_ACTOR: ${{ github.actor }} - PROJECTION_JSON: ${{ toJSON(github.event.client_payload.projection) }} - TRIGGER_NUMBER: ${{ github.event.issue.number }} - TRIGGER_REPOSITORY: ${{ github.event.repository.name }} - run: | - arguments=() - if [[ "$EVENT_NAME" == "repository_dispatch" ]]; then - python - <<'PY' - import json - import os - from pathlib import Path - - projection = json.loads(os.environ["PROJECTION_JSON"]) - Path("public-lifecycle-projection.json").write_text( - json.dumps(projection, separators=(",", ":")) + "\n", - encoding="utf-8", - ) - PY - arguments+=( - --lifecycle-projection public-lifecycle-projection.json - --projection-actor "$PROJECTION_ACTOR" - ) - fi - if [[ "$EVENT_NAME" == "issues" ]]; then - arguments+=( - --trigger-repository "$TRIGGER_REPOSITORY" - --trigger-number "$TRIGGER_NUMBER" - --trigger-action "$EVENT_ACTION" - --trigger-actor "$EVENT_ACTOR" - --trigger-label "$EVENT_LABEL" - ) - fi - python scripts/issue_authority.py discover \ - issue-authority/policy.json issue-authority/backlog.json \ - --output issue-intake.json \ - --github-output "$GITHUB_OUTPUT" \ - "${arguments[@]}" - - - name: Retain the exact vetted intake manifest - id: intake_manifest - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: issue-intake.json - - activate: - name: Activate immutable prerelease supersession authority - if: >- - ${{ github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' && - github.event_name == 'workflow_dispatch' && - inputs.mode == 'activate' }} - needs: validate - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: beta-product-work - permissions: - contents: read - issues: read - statuses: write - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install schema validator - run: python -m pip install jsonschema==4.23.0 - - - name: Create only missing trusted supersession statuses - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/issue_authority.py activate \ - issue-authority/policy.json issue-authority/backlog.json \ - --evidence issue-authority-activation-evidence.json - - - name: Retain prerelease activation evidence - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: github-issue-authority-activate-${{ github.run_id }}-${{ github.run_attempt }} - path: issue-authority-activation-evidence.json - if-no-files-found: error - retention-days: 90 - - apply: - name: Apply reviewed beta backlog to GitHub - if: >- - ${{ github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' && - needs.intake.outputs.intake_ready == 'true' && - (github.event_name == 'push' || - (github.event_name == 'workflow_dispatch' && inputs.mode == 'apply')) }} - needs: [validate, intake] - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: beta-product-work - permissions: - actions: read - checks: read - contents: read - issues: read - pull-requests: read - statuses: write - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Download the exact vetted intake manifest - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.intake.outputs.manifest_artifact_id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-issue-intake - repository: ${{ github.repository }} - run-id: ${{ needs.intake.outputs.source_run_id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-issue-intake - EXPECTED_ARTIFACT_DIGEST: ${{ needs.intake.outputs.manifest_artifact_digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.intake.outputs.manifest_artifact_id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.intake.outputs.source_run_attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.intake.outputs.source_run_id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install schema validator - run: python -m pip install jsonschema==4.23.0 - - - name: Create reviewed issues and reconcile blocker context - env: - BETA_PRODUCT_WORK_TOKEN: ${{ secrets.BETA_PRODUCT_WORK_TOKEN }} - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/issue_authority.py apply \ - issue-authority/policy.json issue-authority/backlog.json \ - --intake-manifest isolated-issue-intake/issue-intake.json \ - --evidence issue-authority-evidence.json - - - name: Preserve a failure record when authority evidence is unavailable - if: failure() - run: | - if [ ! -f issue-authority-evidence.json ]; then - printf '%s%s\n' \ - '{"error":"workflow failed before authority evidence was written",' \ - '"mode":"apply","outcome":"fail","schema":"durable-workflow.github-issue-authority-evidence/v1"}' \ - >issue-authority-evidence.json - fi - - - name: Retain migration or failure evidence - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: github-issue-authority-apply-${{ github.run_id }}-${{ github.run_attempt }} - path: issue-authority-evidence.json - if-no-files-found: ignore - retention-days: 90 - - audit: - name: Audit GitHub lifecycle authority - if: >- - ${{ github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' && - needs.intake.outputs.intake_ready == 'true' && - (github.event_name == 'schedule' || - github.event_name == 'repository_dispatch' || - (github.event_name == 'issues' && needs.intake.outputs.trigger_approved == 'true') || - (github.event_name == 'workflow_dispatch' && inputs.mode == 'audit')) }} - needs: [validate, intake] - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: beta-product-work - permissions: - actions: read - checks: read - contents: read - issues: read - pull-requests: read - statuses: write - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Download the exact vetted intake manifest - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.intake.outputs.manifest_artifact_id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-issue-intake - repository: ${{ github.repository }} - run-id: ${{ needs.intake.outputs.source_run_id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-issue-intake - EXPECTED_ARTIFACT_DIGEST: ${{ needs.intake.outputs.manifest_artifact_digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.intake.outputs.manifest_artifact_id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.intake.outputs.source_run_attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.intake.outputs.source_run_id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install schema validator - run: python -m pip install jsonschema==4.23.0 - - - name: Reconcile lifecycle and audit public issue and pull-request age - env: - BETA_PRODUCT_WORK_TOKEN: ${{ secrets.BETA_PRODUCT_WORK_TOKEN }} - EVENT_NAME: ${{ github.event_name }} - GITHUB_TOKEN: ${{ github.token }} - PROJECTION_ACTOR: ${{ github.actor }} - PROJECTION_JSON: ${{ toJSON(github.event.client_payload.projection) }} - run: | - arguments=() - if [[ "$EVENT_NAME" == "repository_dispatch" ]]; then - python - <<'PY' - import json - import os - from pathlib import Path - - projection = json.loads(os.environ["PROJECTION_JSON"]) - Path("public-lifecycle-projection.json").write_text( - json.dumps(projection, separators=(",", ":")) + "\n", - encoding="utf-8", - ) - PY - arguments+=( - --lifecycle-projection public-lifecycle-projection.json - --projection-actor "$PROJECTION_ACTOR" - ) - fi - python scripts/issue_authority.py audit \ - issue-authority/policy.json issue-authority/backlog.json \ - --intake-manifest isolated-issue-intake/issue-intake.json \ - --evidence issue-authority-evidence.json \ - "${arguments[@]}" - - - name: Preserve a failure record when authority evidence is unavailable - if: failure() - run: | - if [ ! -f issue-authority-evidence.json ]; then - printf '%s%s\n' \ - '{"error":"workflow failed before authority evidence was written",' \ - '"mode":"audit","outcome":"fail","schema":"durable-workflow.github-issue-authority-evidence/v1"}' \ - >issue-authority-evidence.json - fi - - - name: Materialize the exact metadata age audit - if: always() - run: | - python - <<'PY' - import json - from pathlib import Path - - evidence_path = Path("issue-authority-evidence.json") - if not evidence_path.exists(): - raise SystemExit(0) - evidence = json.loads(evidence_path.read_text(encoding="utf-8")) - age_audit = evidence.get("age_audit") - if age_audit is not None: - Path("public-issue-age-audit.json").write_text( - json.dumps(age_audit, indent=2, sort_keys=True) + "\n", - encoding="utf-8", - ) - pipeline_health = age_audit.get("pipeline_health") - if pipeline_health is not None: - Path("public-issue-lifecycle-health.json").write_text( - json.dumps(pipeline_health, indent=2, sort_keys=True) + "\n", - encoding="utf-8", - ) - issue_sweep = evidence.get("issue_sweep") - if issue_sweep is not None: - Path("public-issue-sweep.json").write_text( - json.dumps(issue_sweep, indent=2, sort_keys=True) + "\n", - encoding="utf-8", - ) - PY - - - name: Retain authority audit evidence - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: github-issue-authority-audit-${{ github.run_id }}-${{ github.run_attempt }} - path: | - issue-authority-evidence.json - public-issue-age-audit.json - public-issue-lifecycle-health.json - public-issue-sweep.json - if-no-files-found: ignore - retention-days: 90 diff --git a/.github/workflows/recovery-authority-reconciliation.yml b/.github/workflows/recovery-authority-reconciliation.yml deleted file mode 100644 index 327a60d..0000000 --- a/.github/workflows/recovery-authority-reconciliation.yml +++ /dev/null @@ -1,212 +0,0 @@ -name: Recovery workflow authority reconciliation - -on: - schedule: - - cron: '13,43 * * * *' - workflow_dispatch: - -permissions: {} - -concurrency: - group: recovery-workflow-authority-reconciliation - cancel-in-progress: false - -jobs: - observe: - name: Observe qualified protected recovery workflow identities - if: ${{ github.server_url == 'https://github.com' && github.ref == 'refs/heads/main' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - actions: read - checks: read - contents: read - outputs: - artifact-digest: ${{ steps.proposal.outputs.artifact-digest }} - artifact-id: ${{ steps.proposal.outputs.artifact-id }} - changed: ${{ steps.observe.outputs.changed }} - components: ${{ steps.observe.outputs.components }} - source-run-attempt: ${{ github.run_attempt }} - source-run-id: ${{ github.run_id }} - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Build one exact qualified reconciliation proposal - id: observe - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - mkdir authority-proposal - python scripts/recovery_authority_reconciliation.py reconcile \ - --proposed-authority authority-proposal/authority.json \ - --proposed-source-identities authority-proposal/protected-source-identities.json \ - --observation authority-proposal/observation.json \ - --github-output "$GITHUB_OUTPUT" - tar -cf recovery-authority-proposal.tar -C authority-proposal \ - authority.json protected-source-identities.json observation.json - - - name: Retain the exact reconciliation proposal - id: proposal - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: recovery-authority-proposal.tar - - reconcile: - name: Coalesce drift into one reviewable authority reconciliation - needs: observe - if: >- - ${{ github.server_url == 'https://github.com' && - github.ref == 'refs/heads/main' && - needs.observe.outputs.changed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 10 - environment: beta-product-work - permissions: - actions: read - checks: read - contents: write - pull-requests: write - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Restore the exact observed proposal - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.observe.outputs.artifact-id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-recovery-authority-proposal - repository: ${{ github.repository }} - run-id: ${{ needs.observe.outputs.source-run-id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-recovery-authority-proposal - EXPECTED_ARTIFACT_DIGEST: ${{ needs.observe.outputs.artifact-digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.observe.outputs.artifact-id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.observe.outputs.source-run-attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.observe.outputs.source-run-id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Revalidate exact protected evidence before mutation - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - mkdir validated-recovery-authority-proposal - tar -xf \ - isolated-recovery-authority-proposal/recovery-authority-proposal.tar \ - -C validated-recovery-authority-proposal - python scripts/recovery_authority_reconciliation.py verify-proposal \ - --proposed-authority validated-recovery-authority-proposal/authority.json \ - --proposed-source-identities \ - validated-recovery-authority-proposal/protected-source-identities.json \ - --observation validated-recovery-authority-proposal/observation.json - - - name: Create or update the single reconciliation pull request - env: - GH_TOKEN: ${{ secrets.BETA_PRODUCT_WORK_TOKEN }} - RECONCILIATION_BRANCH: automation/recovery-workflow-authority - RECONCILIATION_COMPONENTS: ${{ needs.observe.outputs.components }} - run: | - set -euo pipefail - if [ -z "$GH_TOKEN" ]; then - printf 'beta product work token is required for reviewed reconciliation\n' >&2 - exit 1 - fi - git_authorization="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$git_authorization" - git_config=( - -c http.https://github.com/.extraheader="AUTHORIZATION: basic $git_authorization" - ) - remote_exists=false - if git "${git_config[@]}" fetch origin \ - "refs/heads/$RECONCILIATION_BRANCH:refs/remotes/origin/$RECONCILIATION_BRANCH"; then - remote_exists=true - fi - - git switch -C "$RECONCILIATION_BRANCH" - cp validated-recovery-authority-proposal/authority.json \ - release-recovery/authority.json - cp validated-recovery-authority-proposal/protected-source-identities.json \ - release-recovery/protected-source-identities.json - authority_paths=( - release-recovery/authority.json - release-recovery/protected-source-identities.json - ) - push_required=true - if [ "$remote_exists" = true ] && git diff --quiet \ - "refs/remotes/origin/$RECONCILIATION_BRANCH" -- "${authority_paths[@]}"; then - push_required=false - fi - if [ "$push_required" = true ]; then - git add "${authority_paths[@]}" - git config user.name "durable-workflow automation" - git config user.email "support@durable-workflow.com" - git commit -m "Reconcile protected recovery workflow authority" - git "${git_config[@]}" push --force-with-lease origin \ - "HEAD:refs/heads/$RECONCILIATION_BRANCH" - else - echo "The existing reconciliation branch already has the exact proposal." - fi - - title="Reconcile protected recovery workflow authority" - body="Protected workflow drift was observed for: ${RECONCILIATION_COMPONENTS}. " - body+="This proposal binds each successor to exact protected-branch source bytes " - body+="and successful qualification evidence." - pull_number="$(gh pr list --state open --head "$RECONCILIATION_BRANCH" \ - --json number --jq '.[0].number // empty')" - if [ -n "$pull_number" ]; then - gh pr edit "$pull_number" --title "$title" --body "$body" - else - gh pr create --base main --head "$RECONCILIATION_BRANCH" \ - --title "$title" --body "$body" - fi diff --git a/.github/workflows/release-plan-observer.yml b/.github/workflows/release-plan-observer.yml deleted file mode 100644 index 610a753..0000000 --- a/.github/workflows/release-plan-observer.yml +++ /dev/null @@ -1,417 +0,0 @@ -name: Release plan observer - -run-name: Observe ${{ inputs.plan_tag || 'current actionable release plan' }} - -on: - schedule: - - cron: '7,22,37,52 * * * *' - workflow_dispatch: - inputs: - plan_tag: - description: Immutable release-plan tag; empty selects the newest public plan - required: false - type: string - default: '' - recorder_recovery_probe: - description: Fail the first recorder attempt before mutation to exercise recorder-only recovery - required: false - type: boolean - default: false - -permissions: {} - -concurrency: - group: release-plan-registry - cancel-in-progress: false - -jobs: - observe: - name: Observe real public release surfaces without write authority - if: ${{ github.server_url == 'https://github.com' }} - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - attestations: read - contents: read - outputs: - artifact-digest: ${{ steps.privileged-handoff.outputs.artifact-digest }} - artifact-id: ${{ steps.privileged-handoff.outputs.artifact-id }} - plan-tag: ${{ steps.plan.outputs.tag }} - plan-sha256: ${{ steps.plan.outputs.plan_sha256 }} - preparation-sha256: ${{ steps.plan.outputs.preparation_sha256 }} - verification-outcome: ${{ steps.verification.outcome }} - handoff-attempt: ${{ steps.handoff.outputs.producer_attempt }} - source-run-attempt: ${{ github.run_attempt }} - source-run-id: ${{ github.run_id }} - plan-available: ${{ steps.plan.outputs.available }} - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Discover immutable plan - id: plan - env: - GITHUB_TOKEN: ${{ github.token }} - REQUESTED_PLAN_TAG: ${{ inputs.plan_tag }} - run: | - arguments=(release-plan.json --preparation release-preparation.json \ - --github-output "$GITHUB_OUTPUT") - if [ -n "$REQUESTED_PLAN_TAG" ]; then - arguments+=(--tag "$REQUESTED_PLAN_TAG") - else - arguments+=(--allow-empty) - fi - python scripts/release_plan.py discover "${arguments[@]}" - - - name: Report truthful scheduled no-op - if: steps.plan.outputs.available == 'false' - run: echo "No current actionable release plan is available." - - - name: Verify all declared source and distribution identities - id: verification - if: steps.plan.outputs.available == 'true' - continue-on-error: true - env: - GH_TOKEN: ${{ github.token }} - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/release_plan.py observe release-plan.json release-preparation.json \ - candidate-verifier-input.json verification.json release-state.json - - - name: Bind observation evidence to this producing attempt - id: handoff - if: ${{ always() && steps.plan.outputs.available == 'true' }} - continue-on-error: true - run: | - python scripts/handoff_recovery.py create release-plan-observation handoff.json \ - --directory . \ - --repository "$GITHUB_REPOSITORY" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --github-output "$GITHUB_OUTPUT" - - - name: Pass only observation evidence to the recorder - if: ${{ always() && steps.plan.outputs.available == 'true' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: release-plan-observation-${{ github.run_id }}-${{ github.run_attempt }} - path: | - release-plan.json - release-preparation.json - candidate-verifier-input.json - release-state.json - verification.json - handoff.json - if-no-files-found: warn - retention-days: 30 - - - name: Package the privileged recorder handoff as one immutable file - env: - PLAN_AVAILABLE: ${{ steps.plan.outputs.available }} - run: | - if [ "$PLAN_AVAILABLE" = true ]; then - handoff_files=( - release-plan.json - release-preparation.json - candidate-verifier-input.json - release-state.json - handoff.json - ) - if [ -f verification.json ]; then - handoff_files+=(verification.json) - fi - tar -cf observation-recorder-handoff.tar "${handoff_files[@]}" - else - tar -cf observation-recorder-handoff.tar --files-from /dev/null - fi - - - name: Bind the privileged recorder handoff identity and digest - id: privileged-handoff - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: observation-recorder-handoff.tar - - record: - name: Record observation from a fresh checkout - needs: observe - if: >- - github.server_url == 'https://github.com' && - needs.observe.outputs.plan-available == 'true' && - github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - actions: read - attestations: read - contents: write - outputs: - current-plan: ${{ steps.aggregate.outputs.current }} - current-plan-sha256: ${{ steps.aggregate.outputs.current-plan-sha256 }} - current-plan-tag: ${{ steps.aggregate.outputs.current-plan-tag }} - steps: - - name: Check out a fresh qualified control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Restore isolated observation evidence - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.observe.outputs.artifact-id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-observation-handoff - repository: ${{ github.repository }} - run-id: ${{ needs.observe.outputs.source-run-id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-observation-handoff - EXPECTED_ARTIFACT_DIGEST: ${{ needs.observe.outputs.artifact-digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.observe.outputs.artifact-id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.observe.outputs.source-run-attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.observe.outputs.source-run-id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Extract the validated observation handoff - run: | - mkdir isolated-observation - tar -xf isolated-observation-handoff/observation-recorder-handoff.tar -C isolated-observation - - - name: Validate the selected attempt-bound handoff - run: | - python scripts/handoff_recovery.py validate release-plan-observation \ - isolated-observation/handoff.json \ - --directory isolated-observation \ - --repository "$GITHUB_REPOSITORY" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --current-attempt "$GITHUB_RUN_ATTEMPT" \ - --producer-attempt "${{ needs.observe.outputs.handoff-attempt }}" - - - name: Re-read the originally selected public plan - env: - GITHUB_TOKEN: ${{ github.token }} - EXPECTED_PLAN_TAG: ${{ needs.observe.outputs.plan-tag }} - run: | - mkdir trusted-public-plan - python scripts/release_plan.py discover \ - trusted-public-plan/release-plan.json \ - --preparation trusted-public-plan/release-preparation.json \ - --tag "$EXPECTED_PLAN_TAG" - - - name: Validate and canonicalize the isolated observation handoff - id: handoff - env: - GH_TOKEN: ${{ github.token }} - GITHUB_TOKEN: ${{ github.token }} - EXPECTED_PLAN_TAG: ${{ needs.observe.outputs.plan-tag }} - EXPECTED_PLAN_SHA256: ${{ needs.observe.outputs.plan-sha256 }} - EXPECTED_PREPARATION_SHA256: ${{ needs.observe.outputs.preparation-sha256 }} - EXPECTED_VERIFICATION_OUTCOME: ${{ needs.observe.outputs.verification-outcome }} - run: | - python scripts/release_plan.py validate-observation-handoff \ - isolated-observation/release-plan.json \ - isolated-observation/release-preparation.json \ - isolated-observation/candidate-verifier-input.json \ - isolated-observation/verification.json \ - isolated-observation/release-state.json \ - validated-observation \ - --authoritative-plan trusted-public-plan/release-plan.json \ - --authoritative-preparation trusted-public-plan/release-preparation.json \ - --expected-plan-tag "$EXPECTED_PLAN_TAG" \ - --expected-plan-sha256 "$EXPECTED_PLAN_SHA256" \ - --expected-preparation-sha256 "$EXPECTED_PREPARATION_SHA256" \ - --expected-verification-outcome "$EXPECTED_VERIFICATION_OUTCOME" \ - --github-output "$GITHUB_OUTPUT" - - - name: Exercise recorder-only recovery before any mutation - if: ${{ inputs.recorder_recovery_probe && github.run_attempt == 1 }} - run: | - echo "Recorder recovery probe stopped the first writer attempt before mutation." >&2 - exit 1 - - - name: Retain aggregate state on the plan release - env: - GH_TOKEN: ${{ github.token }} - PLAN_TAG: ${{ needs.observe.outputs.plan-tag }} - run: gh release upload "$PLAN_TAG" validated-observation/release-state.json --clobber - - - name: Stop with an actionable recovery classification - if: needs.observe.outputs.verification-outcome != 'success' - run: | - python - <<'PYTHON' - import json - from pathlib import Path - - state = json.loads( - Path("validated-observation/release-state.json").read_text(encoding="utf-8") - ) - print(f"::error::{state['reason']}") - PYTHON - exit 1 - - - name: Record channel-bound completed candidate - id: record - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - git_authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$git_authorization" - GIT_CONFIG_COUNT=1 \ - GIT_CONFIG_KEY_0=http.https://github.com/.extraheader \ - GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $git_authorization" \ - python scripts/release_plan.py complete \ - validated-observation/release-plan.json \ - validated-observation/verification.json \ - --authoritative-completion authoritative-release-candidate.json \ - --authoritative-verification authoritative-verification.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or repair completed candidate mirror - env: - GH_TOKEN: ${{ github.token }} - CANDIDATE: ${{ steps.record.outputs.candidate }} - CHANNEL: ${{ steps.record.outputs.channel }} - CANDIDATE_TAG: ${{ steps.record.outputs.tag }} - RECORD_COMMIT: ${{ steps.record.outputs.commit }} - run: | - set -euo pipefail - mirror_dir="$(mktemp -d)" - trap 'rm -rf "$mirror_dir"' EXIT - if ! gh release view "$CANDIDATE_TAG" >/dev/null 2>&1; then - gh release create "$CANDIDATE_TAG" --verify-tag --prerelease \ - --title "Completed ${CHANNEL} release candidate ${CANDIDATE}" \ - --notes "All declared public artifacts were verified. The authoritative Git record is ${RECORD_COMMIT}." - fi - repair_asset() { - source_path="$1" - asset_name="$2" - rm -f "$mirror_dir/$asset_name" - if gh release download "$CANDIDATE_TAG" --pattern "$asset_name" --dir "$mirror_dir" >/dev/null 2>&1; then - cmp --silent "$source_path" "$mirror_dir/$asset_name" || { - printf 'release asset %s differs from immutable Git authority\n' "$asset_name" >&2 - exit 1 - } - else - upload_dir="$(mktemp -d)" - cp "$source_path" "$upload_dir/$asset_name" - gh release upload "$CANDIDATE_TAG" "$upload_dir/$asset_name" - fi - } - repair_asset authoritative-release-candidate.json release-candidate.json - repair_asset authoritative-verification.json verification.json - - - name: Classify whether the completed plan is current - id: aggregate - env: - OBSERVED_PLAN_SHA256: ${{ needs.observe.outputs.plan-sha256 }} - OBSERVED_PLAN_TAG: ${{ needs.observe.outputs.plan-tag }} - run: | - set -euo pipefail - if cmp --silent \ - validated-observation/release-plan.json \ - release-plans/current.json; then - echo "current=true" >> "$GITHUB_OUTPUT" - echo "current-plan-sha256=$OBSERVED_PLAN_SHA256" >> "$GITHUB_OUTPUT" - echo "current-plan-tag=$OBSERVED_PLAN_TAG" >> "$GITHUB_OUTPUT" - else - echo "current=false" >> "$GITHUB_OUTPUT" - echo "Completed plan is not the declared current product train." - fi - - publish-current: - name: Reconcile aggregate publication after current-plan completion - needs: record - if: >- - success() && - github.server_url == 'https://github.com' && - github.ref == 'refs/heads/main' && - needs.record.outputs.current-plan == 'true' - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - actions: write - contents: read - steps: - - name: Check out the exact protected coalescing controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - ref: ${{ github.sha }} - sparse-checkout: | - release-plans/current.json - scripts/current_plan_publication.py - - - name: Publish the matching aggregate current authority - id: publication - env: - GH_TOKEN: ${{ github.token }} - CURRENT_PLAN_SHA256: ${{ needs.record.outputs.current-plan-sha256 }} - CURRENT_PLAN_TAG: ${{ needs.record.outputs.current-plan-tag }} - TARGET_REF: main - TARGET_REPOSITORY: durable-workflow/.github - TARGET_WORKFLOW: current-release-plan.yml - run: | - set -euo pipefail - python scripts/current_plan_publication.py reconcile-dispatch \ - --plan release-plans/current.json \ - --repository "$TARGET_REPOSITORY" \ - --workflow "$TARGET_WORKFLOW" \ - --ref "$TARGET_REF" \ - --observer-workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --plan-tag "$CURRENT_PLAN_TAG" \ - --plan-sha256 "$CURRENT_PLAN_SHA256" \ - --github-output "$GITHUB_OUTPUT" diff --git a/.github/workflows/release-plan-supersession.yml b/.github/workflows/release-plan-supersession.yml deleted file mode 100644 index 1122a13..0000000 --- a/.github/workflows/release-plan-supersession.yml +++ /dev/null @@ -1,304 +0,0 @@ -name: Release plan supersession - -run-name: Supersede ${{ inputs.failed_plan_tag }} after ${{ inputs.conflicting_components }} conflicts - -on: - workflow_dispatch: - inputs: - failed_plan_tag: - description: Immutable release-plan tag made unrecoverable by terminal allocation conflicts - required: true - type: string - conflicting_components: - description: Comma-separated terminal-conflict components in release-plan order - required: true - type: string - successor_release_plan: - description: Exact successor release plan resolving every conflicting allocation and no others - required: true - type: string - -permissions: {} - -jobs: - qualify: - name: Prove protected terminal failure without write authority - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 30 - environment: release-plan-supersession - permissions: - actions: read - attestations: read - contents: read - outputs: - approval-handoff: ${{ steps.approval.outputs.handoff }} - approval-producer-attempt: ${{ steps.approval.outputs.producer-attempt }} - artifact-digest: ${{ steps.privileged-handoff.outputs.artifact-digest }} - artifact-id: ${{ steps.privileged-handoff.outputs.artifact-id }} - source-run-attempt: ${{ github.run_attempt }} - source-run-id: ${{ github.run_id }} - steps: - - name: Check out the public control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install changed-file quality tooling - run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - - - name: Qualify release-plan tooling - run: | - python -m ruff check scripts tests - python -m yamllint .github/workflows - python -m compileall -q scripts tests - python -m unittest discover -v - python -m json.tool release-plans/failure-schema.json >/dev/null - - - name: Canonicalize exact successor plan - env: - SUCCESSOR_PLAN: ${{ inputs.successor_release_plan }} - run: | - python - <<'PYTHON' - import os - from pathlib import Path - - Path("requested-successor.json").write_text(os.environ["SUCCESSOR_PLAN"], encoding="utf-8") - PYTHON - python scripts/release_plan.py validate requested-successor.json successor-release-plan.json - - - name: Prove terminal conflicts and protected authorization - env: - CONFLICTING_COMPONENTS: ${{ inputs.conflicting_components }} - FAILED_PLAN_TAG: ${{ inputs.failed_plan_tag }} - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/release_plan.py prepare-supersession \ - "$FAILED_PLAN_TAG" "$CONFLICTING_COMPONENTS" successor-release-plan.json \ - release-plan-failure.json authoritative-successor-release-plan.json \ - --actor "$GITHUB_ACTOR" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --workflow-commit "$GITHUB_SHA" - - - name: Pass only protected supersession evidence to the recorder - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: release-plan-supersession-${{ github.run_id }}-${{ github.run_attempt }} - path: | - release-plan-failure.json - authoritative-successor-release-plan.json - if-no-files-found: error - - - name: Package the privileged supersession handoff as one immutable file - run: | - tar -cf supersession-handoff.tar \ - release-plan-failure.json authoritative-successor-release-plan.json - - - name: Bind the privileged supersession handoff identity and digest - id: privileged-handoff - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: supersession-handoff.tar - - - name: Bind protected qualification to this workflow run - id: approval - run: | - python scripts/protected_release_plan_writer.py create \ - --workflow release-plan-supersession.yml \ - --repository "$GITHUB_REPOSITORY" \ - --ref "$GITHUB_REF" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --github-output "$GITHUB_OUTPUT" - - record: - name: Record protected terminal failure from a fresh checkout - needs: qualify - if: >- - github.ref == 'refs/heads/main' && - needs.qualify.result == 'success' - runs-on: ubuntu-latest - timeout-minutes: 10 - concurrency: - group: release-plan-registry - cancel-in-progress: false - permissions: - actions: read - attestations: read - contents: write - steps: - - name: Check out a fresh qualified control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Restore isolated supersession evidence - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.qualify.outputs.artifact-id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-supersession-handoff - repository: ${{ github.repository }} - run-id: ${{ needs.qualify.outputs.source-run-id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-supersession-handoff - EXPECTED_ARTIFACT_DIGEST: ${{ needs.qualify.outputs.artifact-digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.qualify.outputs.artifact-id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.qualify.outputs.source-run-attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.qualify.outputs.source-run-id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Validate the exact protected qualification handoff - env: - APPROVED_WRITER_HANDOFF: ${{ needs.qualify.outputs.approval-handoff }} - APPROVAL_PRODUCER_ATTEMPT: ${{ needs.qualify.outputs.approval-producer-attempt }} - run: | - python scripts/protected_release_plan_writer.py validate \ - --workflow release-plan-supersession.yml \ - --repository "$GITHUB_REPOSITORY" \ - --ref "$GITHUB_REF" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --current-attempt "$GITHUB_RUN_ATTEMPT" \ - --producer-attempt "$APPROVAL_PRODUCER_ATTEMPT" \ - --handoff "$APPROVED_WRITER_HANDOFF" - - - name: Extract the validated supersession handoff - run: | - mkdir isolated-supersession - tar -xf isolated-supersession-handoff/supersession-handoff.tar -C isolated-supersession - - - name: Rebuild and validate the authorized successor handoff - env: - CONFLICTING_COMPONENTS: ${{ inputs.conflicting_components }} - FAILED_PLAN_TAG: ${{ inputs.failed_plan_tag }} - SUCCESSOR_PLAN: ${{ inputs.successor_release_plan }} - run: | - python - <<'PYTHON' - import os - from pathlib import Path - - Path("requested-successor.json").write_text(os.environ["SUCCESSOR_PLAN"], encoding="utf-8") - PYTHON - python scripts/release_plan.py validate requested-successor.json successor-release-plan.json - cmp --silent \ - successor-release-plan.json \ - isolated-supersession/authoritative-successor-release-plan.json - python scripts/release_plan.py validate-supersession-handoff \ - isolated-supersession/release-plan-failure.json \ - successor-release-plan.json authorized-release-plan-failure.json \ - --expected-failed-plan-tag "$FAILED_PLAN_TAG" \ - --expected-conflict-components "$CONFLICTING_COMPONENTS" - - - name: Create or compare immutable terminal record - id: record - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - git_authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$git_authorization" - GIT_CONFIG_COUNT=1 \ - GIT_CONFIG_KEY_0=http.https://github.com/.extraheader \ - GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $git_authorization" \ - python scripts/release_plan.py record-supersession \ - authorized-release-plan-failure.json successor-release-plan.json \ - --authoritative-record authoritative-release-plan-failure.json \ - --authoritative-successor durable-successor-release-plan.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or repair durable GitHub Release mirror - env: - FAILURE_TAG: ${{ steps.record.outputs.tag }} - GH_TOKEN: ${{ github.token }} - RECORD_COMMIT: ${{ steps.record.outputs.commit }} - run: | - set -euo pipefail - mirror_dir="$(mktemp -d)" - trap 'rm -rf "$mirror_dir"' EXIT - if ! gh release view "$FAILURE_TAG" >/dev/null 2>&1; then - gh release create "$FAILURE_TAG" --verify-tag --prerelease \ - --title "Terminal release plan failure" \ - --notes "Protected terminal record at immutable Git commit ${RECORD_COMMIT}." - fi - repair_asset() { - source_path="$1" - asset_name="$2" - rm -f "$mirror_dir/$asset_name" - if gh release download "$FAILURE_TAG" --pattern "$asset_name" --dir "$mirror_dir" >/dev/null 2>&1; then - cmp --silent "$source_path" "$mirror_dir/$asset_name" || { - printf 'release asset %s differs from immutable Git authority\n' "$asset_name" >&2 - exit 1 - } - else - upload_dir="$(mktemp -d)" - cp "$source_path" "$upload_dir/$asset_name" - gh release upload "$FAILURE_TAG" "$upload_dir/$asset_name" - fi - } - repair_asset authoritative-release-plan-failure.json release-plan-failure.json - repair_asset durable-successor-release-plan.json successor-release-plan.json - - - name: Upload recorded supersession evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: release-plan-supersession-${{ steps.record.outputs.failed_plan }} - path: | - authoritative-release-plan-failure.json - durable-successor-release-plan.json - if-no-files-found: error diff --git a/.github/workflows/release-plan.yml b/.github/workflows/release-plan.yml deleted file mode 100644 index 8948471..0000000 --- a/.github/workflows/release-plan.yml +++ /dev/null @@ -1,338 +0,0 @@ -name: Release plan - -run-name: Record an immutable release plan - -on: - push: - branches: - - main - paths: - - release-plans/first-release-candidate.json - - release-plans/first-release-candidate-source-preparation.json - workflow_dispatch: - inputs: - release_plan: - description: Exact channel-aware release plan JSON matching release-plans/schema.json - required: true - type: string - -permissions: {} - -jobs: - authorize: - name: Await immutable release-plan approval - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 5 - environment: beta-authorization - permissions: - contents: read - outputs: - handoff: ${{ steps.approval.outputs.handoff }} - producer-attempt: ${{ steps.approval.outputs.producer-attempt }} - steps: - - name: Check out the protected release-plan controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - ref: ${{ github.sha }} - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Bind protected approval to this workflow run - id: approval - run: | - python scripts/protected_release_plan_writer.py create \ - --workflow release-plan.yml \ - --repository "$GITHUB_REPOSITORY" \ - --ref "$GITHUB_REF" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --github-output "$GITHUB_OUTPUT" - - validate-and-record: - name: Validate and record immutable release plan - needs: authorize - if: >- - github.ref == 'refs/heads/main' && - needs.authorize.result == 'success' - runs-on: ubuntu-latest - timeout-minutes: 15 - concurrency: - group: release-plan-registry - cancel-in-progress: false - permissions: - contents: write - outputs: - artifact-digest: ${{ steps.privileged-handoff.outputs.artifact-digest }} - artifact-id: ${{ steps.privileged-handoff.outputs.artifact-id }} - source-run-attempt: ${{ github.run_attempt }} - source-run-id: ${{ github.run_id }} - channel: ${{ steps.plan.outputs.channel }} - steps: - - name: Check out the public control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Validate the exact protected approval handoff - env: - APPROVED_WRITER_HANDOFF: ${{ needs.authorize.outputs.handoff }} - APPROVAL_PRODUCER_ATTEMPT: ${{ needs.authorize.outputs.producer-attempt }} - run: | - python scripts/protected_release_plan_writer.py validate \ - --workflow release-plan.yml \ - --repository "$GITHUB_REPOSITORY" \ - --ref "$GITHUB_REF" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --source-sha "$GITHUB_SHA" \ - --run-id "$GITHUB_RUN_ID" \ - --current-attempt "$GITHUB_RUN_ATTEMPT" \ - --producer-attempt "$APPROVAL_PRODUCER_ATTEMPT" \ - --handoff "$APPROVED_WRITER_HANDOFF" - - - name: Install changed-file quality tooling - run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - - - name: Qualify release-plan tooling - run: | - python -m ruff check scripts tests - python -m yamllint .github/workflows - python -m compileall -q scripts tests - python -m unittest discover -v - python -m json.tool release-plans/schema.json >/dev/null - python -m json.tool release-plans/preparation-schema.json >/dev/null - python -m json.tool release-plans/source-preparation-schema.json >/dev/null - python -m json.tool release-plans/current-source-preparation.json >/dev/null - python -m json.tool release-plans/first-release-candidate-source-preparation.json >/dev/null - python -m json.tool release-plans/first-release-candidate.json >/dev/null - python -m json.tool release-plans/current.json >/dev/null - python -m json.tool release-plans/candidate-schema.json >/dev/null - python -m json.tool product-train/schema.json >/dev/null - python -m json.tool product-train/current.json >/dev/null - python -m json.tool product-train/sdk-server-qualification.json >/dev/null - python -m json.tool waterline-train/contract.json >/dev/null - python -m json.tool waterline-train/schema.json >/dev/null - python -m json.tool waterline-train/completion-evidence-schema.json >/dev/null - python -m jsonschema -i product-train/current.json product-train/schema.json - python -m jsonschema -i waterline-train/contract.json waterline-train/schema.json - python -m jsonschema \ - -i product-train/sdk-server-qualification.json \ - product-train/sdk-server-qualification-schema.json - python -m jsonschema -i release-plans/current.json release-plans/schema.json - python -m jsonschema \ - -i release-plans/first-release-candidate-source-preparation.json \ - release-plans/source-preparation-schema.json - python -m jsonschema \ - -i release-plans/first-release-candidate.json \ - release-plans/schema.json - - - name: Canonicalize requested plan - env: - DISPATCH_PLAN: ${{ inputs.release_plan }} - EVENT_NAME: ${{ github.event_name }} - run: | - python - <<'PYTHON' - import os - from pathlib import Path - - source = ( - Path("release-plans/first-release-candidate.json").read_text(encoding="utf-8") - if os.environ["EVENT_NAME"] == "push" - else os.environ["DISPATCH_PLAN"] - ) - Path("requested-release-plan.json").write_text(source, encoding="utf-8") - PYTHON - - - name: Validate the exact requested plan - id: plan - run: | - python scripts/release_plan.py validate requested-release-plan.json release-plan.json - python - <<'PYTHON' - import json - import os - from pathlib import Path - - plan = json.loads(Path("release-plan.json").read_text(encoding="utf-8")) - with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output: - output.write(f"channel={plan['channel']}\n") - PYTHON - - - name: Reject mutation of an existing plan identity - run: python scripts/release_plan.py check release-plan.json - - - name: Verify public branch, source, tag, and channel preconditions - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/release_plan.py preflight release-plan.json release-plan-preflight.json \ - --preparation release-preparation.json --release-date "$(date -u +%F)" - - - name: Create or compare the immutable Git record - id: record - run: | - python scripts/release_plan.py record release-plan.json \ - release-preparation.json \ - --authoritative-plan authoritative-release-plan.json \ - --authoritative-preparation authoritative-release-preparation.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or repair the durable GitHub Release mirror - env: - GH_TOKEN: ${{ github.token }} - PLAN: ${{ steps.record.outputs.plan }} - PLAN_TAG: ${{ steps.record.outputs.tag }} - RECORD_COMMIT: ${{ steps.record.outputs.commit }} - run: | - set -euo pipefail - mirror_dir="$(mktemp -d)" - trap 'rm -rf "$mirror_dir"' EXIT - if ! gh release view "$PLAN_TAG" >/dev/null 2>&1; then - notes="$(mktemp)" - { - printf 'Immutable pre-publication plan at Git record `%s`.\n\n' "$RECORD_COMMIT" - printf 'Component repositories discover its exact prepared notes and recover independently.\n' - } > "$notes" - gh release create "$PLAN_TAG" --verify-tag --prerelease \ - --title "Release plan ${PLAN}" --notes-file "$notes" - fi - if gh release download "$PLAN_TAG" --pattern release-plan.json --dir "$mirror_dir" >/dev/null 2>&1; then - cmp --silent authoritative-release-plan.json "$mirror_dir/release-plan.json" || { - printf 'release-plan.json differs from immutable Git authority\n' >&2 - exit 1 - } - else - upload_dir="$(mktemp -d)" - cp authoritative-release-plan.json "$upload_dir/release-plan.json" - gh release upload "$PLAN_TAG" "$upload_dir/release-plan.json" - fi - rm -f "$mirror_dir/release-preparation.json" - if gh release download "$PLAN_TAG" --pattern release-preparation.json \ - --dir "$mirror_dir" >/dev/null 2>&1; then - cmp --silent authoritative-release-preparation.json \ - "$mirror_dir/release-preparation.json" || { - printf 'release-preparation.json differs from immutable Git authority\n' >&2 - exit 1 - } - else - upload_dir="$(mktemp -d)" - cp authoritative-release-preparation.json "$upload_dir/release-preparation.json" - gh release upload "$PLAN_TAG" "$upload_dir/release-preparation.json" - fi - - - name: Upload preflight evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: release-plan-preflight - path: | - authoritative-release-plan.json - release-plan-preflight.json - release-preparation.json - if-no-files-found: error - - - name: Package the privileged continuity handoff as one immutable file - run: | - tar -cf continuity-handoff.tar \ - authoritative-release-plan.json release-plan-preflight.json release-preparation.json - - - name: Bind the privileged continuity handoff identity and digest - id: privileged-handoff - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: continuity-handoff.tar - - dispatch-accepted-continuity: - name: Continue an accepted exact plan - needs: validate-and-record - if: github.ref == 'refs/heads/main' && needs.validate-and-record.outputs.channel != 'rc' - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - actions: write - contents: read - steps: - - name: Check out the public control plane - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Download the recorded plan - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.validate-and-record.outputs.artifact-id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-continuity-handoff - repository: ${{ github.repository }} - run-id: ${{ needs.validate-and-record.outputs.source-run-id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-continuity-handoff - EXPECTED_ARTIFACT_DIGEST: ${{ needs.validate-and-record.outputs.artifact-digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.validate-and-record.outputs.artifact-id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.validate-and-record.outputs.source-run-attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.validate-and-record.outputs.source-run-id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Extract the validated continuity handoff - run: | - mkdir recorded-plan - tar -xf isolated-continuity-handoff/continuity-handoff.tar -C recorded-plan - - - name: Dispatch continuity for an exact public acceptance - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/beta_continuity.py dispatch-accepted \ - recorded-plan/authoritative-release-plan.json diff --git a/.github/workflows/rust-docs-visual.yml b/.github/workflows/rust-docs-visual.yml index c04a119..bdde7fd 100644 --- a/.github/workflows/rust-docs-visual.yml +++ b/.github/workflows/rust-docs-visual.yml @@ -134,7 +134,7 @@ jobs: if [ "$width" = 640 ] && [ "$height" = 360 ]; then capture_args+=(--full-page) fi - node visual-controller/scripts/pipeline_visual_capture.mjs \ + node visual-controller/scripts/visual_capture.mjs \ --url http://127.0.0.1:4173/ \ --surface rust-sdk-reference --state "$state" \ --width "$width" --height "$height" \ diff --git a/.github/workflows/source-qualification.yml b/.github/workflows/source-qualification.yml deleted file mode 100644 index 31a4357..0000000 --- a/.github/workflows/source-qualification.yml +++ /dev/null @@ -1,174 +0,0 @@ -name: Source Qualification - -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - - cron: '41 5 * * *' - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: source-qualification-${{ github.sha }} - cancel-in-progress: false - -jobs: - source: - name: Control-plane source qualification - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 - with: - node-version: "24" - - - name: Install quality tooling - run: | - python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - npm ci - - - name: Validate source and policy - run: | - python -m ruff check scripts tests - python -m yamllint .github/workflows - python -m compileall -q scripts tests - python -m unittest discover -v - python -m json.tool qualification/policy.json >/dev/null - python -m json.tool qualification/schema.json >/dev/null - python -m jsonschema -i qualification/policy.json qualification/schema.json - python -m json.tool visual-evidence/policy.json >/dev/null - python -m json.tool visual-evidence/schema.json >/dev/null - python -m jsonschema -i visual-evidence/policy.json visual-evidence/schema.json - python -m json.tool regression-corpus/policy-schema.json >/dev/null - python -m json.tool regression-corpus/evidence-schema.json >/dev/null - python - <<'PY' - import json - from pathlib import Path - - from jsonschema import Draft202012Validator - - for path in ( - Path("regression-corpus/policy-schema.json"), - Path("regression-corpus/evidence-schema.json"), - ): - Draft202012Validator.check_schema(json.loads(path.read_text())) - PY - python -m json.tool release-recovery/authority.json >/dev/null - python -m json.tool release-recovery/schema.json >/dev/null - python -m jsonschema -i release-recovery/authority.json release-recovery/schema.json - python -m json.tool release-recovery/protected-source-identities.json >/dev/null - python -m json.tool release-recovery/protected-source-identities-schema.json >/dev/null - python -m jsonschema \ - -i release-recovery/protected-source-identities.json \ - release-recovery/protected-source-identities-schema.json - python -m json.tool product-train/current.json >/dev/null - python -m json.tool product-train/schema.json >/dev/null - python -m json.tool product-train/sdk-server-qualification.json >/dev/null - python -m json.tool product-train/sdk-server-qualification-schema.json >/dev/null - python -m jsonschema -i product-train/current.json product-train/schema.json - python -m jsonschema \ - -i product-train/sdk-server-qualification.json \ - product-train/sdk-server-qualification-schema.json - python -m json.tool release-plans/current.json >/dev/null - python -m json.tool release-plans/current-source-preparation.json >/dev/null - python -m json.tool release-plans/first-release-candidate.json >/dev/null - python -m json.tool release-plans/first-release-candidate-source-preparation.json >/dev/null - python -m json.tool release-plans/schema.json >/dev/null - python -m json.tool release-plans/source-preparation-schema.json >/dev/null - python -m jsonschema -i release-plans/current.json release-plans/schema.json - python -m jsonschema \ - -i release-plans/current-source-preparation.json \ - release-plans/source-preparation-schema.json - python -m jsonschema \ - -i release-plans/first-release-candidate.json \ - release-plans/schema.json - python -m jsonschema \ - -i release-plans/first-release-candidate-source-preparation.json \ - release-plans/source-preparation-schema.json - python scripts/release_plan.py validate \ - release-plans/first-release-candidate.json \ - /tmp/first-release-candidate.json - python scripts/release_plan.py materialize-current \ - release-plans/current.json \ - /tmp/current-release-plan.json /tmp/current-candidate.json - python -m json.tool release-recovery/consumer-conformance/contract.json >/dev/null - python -m json.tool release-recovery/consumer-conformance/contract-schema.json >/dev/null - python -m json.tool release-recovery/consumer-conformance/adapter-schema.json >/dev/null - python -m jsonschema \ - -i release-recovery/consumer-conformance/contract.json \ - release-recovery/consumer-conformance/contract-schema.json - conformance_args=() - if [ -n "$CONFORMANCE_BASE_REF" ]; then - conformance_args+=(--previous-ref "$CONFORMANCE_BASE_REF") - fi - python scripts/release_recovery_consumer_conformance.py \ - --contract release-recovery/consumer-conformance/contract.json \ - "${conformance_args[@]}" - python -m json.tool beta-authorization/request-schema.json >/dev/null - python -m json.tool beta-authorization/record-schema.json >/dev/null - python -m json.tool beta-authorization/evidence-schema.json >/dev/null - python scripts/issue_authority.py validate \ - issue-authority/policy.json issue-authority/backlog.json - python -m json.tool issue-authority/policy-schema.json >/dev/null - python -m json.tool issue-authority/backlog-schema.json >/dev/null - python scripts/beta_conformance.py validate \ - beta-conformance/contract.json \ - beta-conformance/contract-schema.json \ - beta-conformance/plan-schema.json \ - beta-conformance/result-schema.json \ - beta-conformance/suite-result-schema.json - python scripts/qualification_policy.py validate - env: - CONFORMANCE_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} - - - name: Validate visual capture - run: | - mkdir -p "$TMPDIR" - npm run test:visual-capture - env: - TMPDIR: ${{ runner.temp }}/visual-chromium-${{ github.run_id }}-${{ github.run_attempt }} - - - name: Verify live public recovery authority - if: ${{ github.server_url == 'https://github.com' }} - run: | - python scripts/release_plan.py verify-recovery-authority \ - release-recovery/authority.json \ - /tmp/recovery-workflow-authority-verification.json - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Audit synchronized release-recovery consumers - if: >- - github.server_url == 'https://github.com' && - github.ref == 'refs/heads/main' && - github.event_name != 'pull_request' - run: >- - python scripts/release_recovery_target_qualification.py - --contract release-recovery/consumer-conformance/contract.json - --evidence release-recovery-target-qualification-evidence.json - - - name: Retain release-recovery target evidence - if: >- - github.server_url == 'https://github.com' && - github.ref == 'refs/heads/main' && - github.event_name != 'pull_request' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: release-recovery-target-qualification-${{ github.run_id }}-${{ github.run_attempt }} - path: release-recovery-target-qualification-evidence.json - if-no-files-found: error - retention-days: 90 diff --git a/.github/workflows/stable-authorization.yml b/.github/workflows/stable-authorization.yml deleted file mode 100644 index b715d76..0000000 --- a/.github/workflows/stable-authorization.yml +++ /dev/null @@ -1,306 +0,0 @@ -name: Stable authorization - -run-name: Evaluate stable 2.0 authorization - -on: - workflow_dispatch: - inputs: - authorization_request: - description: Exact stable authorization request JSON - required: true - type: string - -permissions: {} - -concurrency: - group: stable-authorization-registry - cancel-in-progress: false - -jobs: - evidence: - name: Evaluate fixed release-critical tier - if: >- - github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - outputs: - artifact-digest: ${{ steps.privileged-handoff.outputs.artifact-digest }} - artifact-id: ${{ steps.privileged-handoff.outputs.artifact-id }} - source-run-attempt: ${{ github.run_attempt }} - source-run-id: ${{ github.run_id }} - steps: - - name: Check out the protected stable controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install release-authority validation tooling - run: python -m pip install jsonschema==4.23.0 PyYAML==6.0.2 ruff==0.12.3 yamllint==1.37.1 - - - name: Qualify stable authorization source - run: | - python -m ruff check scripts/stable_authorization.py tests/test_stable_authorization.py - python -m yamllint .github/workflows/stable-authorization.yml - python -m compileall -q scripts/stable_authorization.py tests/test_stable_authorization.py - python -m unittest -v tests.test_stable_authorization - python -m json.tool stable-authorization/contract.json >/dev/null - python -m json.tool stable-authorization/contract-schema.json >/dev/null - python -m json.tool stable-authorization/request-schema.json >/dev/null - python -m json.tool stable-authorization/readout-schema.json >/dev/null - python -m json.tool stable-authorization/authorization-schema.json >/dev/null - - - name: Canonicalize the stable authorization request - env: - AUTHORIZATION_REQUEST: ${{ inputs.authorization_request }} - run: | - python - <<'PYTHON' - import json - import os - from pathlib import Path - - request = json.loads(os.environ["AUTHORIZATION_REQUEST"]) - Path("stable-authorization-request.json").write_text( - json.dumps(request, indent=2, sort_keys=True) + "\n", - encoding="utf-8", - ) - PYTHON - - - name: Verify public evidence and create the release readout - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - python scripts/stable_authorization.py readout \ - stable-authorization/contract.json \ - stable-authorization-request.json \ - release-critical-readout.json \ - --verify-public-sources \ - --github-summary "$GITHUB_STEP_SUMMARY" - - - name: Retain the readout before enforcing readiness - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: stable-authorization-readout-${{ github.run_id }}-${{ github.run_attempt }} - path: | - stable-authorization-request.json - release-critical-readout.json - if-no-files-found: error - retention-days: 90 - - - name: Package the privileged authorization handoff as one immutable file - run: | - tar -cf stable-authorization-handoff.tar \ - stable-authorization-request.json release-critical-readout.json - - - name: Bind the privileged authorization handoff identity and digest - id: privileged-handoff - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: stable-authorization-handoff.tar - - - name: Fail closed unless every release-critical cell is ready - run: | - python scripts/stable_authorization.py require-ready \ - stable-authorization/contract.json \ - stable-authorization-request.json \ - release-critical-readout.json - - authorize: - name: Record explicit human stable decision - needs: evidence - if: >- - github.ref == 'refs/heads/main' && - github.server_url == 'https://github.com' - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: stable-authorization - permissions: - actions: read - contents: write - steps: - - name: Check out the protected stable controller - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ github.sha }} - - - name: Download the pre-decision evidence readout - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{ needs.evidence.outputs.artifact-id }} - digest-mismatch: error - github-token: ${{ github.token }} - path: isolated-stable-authorization-handoff - repository: ${{ github.repository }} - run-id: ${{ needs.evidence.outputs.source-run-id }} - - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-stable-authorization-handoff - EXPECTED_ARTIFACT_DIGEST: ${{ needs.evidence.outputs.artifact-digest }} - EXPECTED_ARTIFACT_ID: ${{ needs.evidence.outputs.artifact-id }} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{ needs.evidence.outputs.source-run-attempt }} - EXPECTED_SOURCE_RUN_ID: ${{ needs.evidence.outputs.source-run-id }} - run: | - set -euo pipefail - if [[ ! "$EXPECTED_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ]]; then - printf 'producer artifact digest is not an exact SHA-256 digest\n' >&2 - exit 1 - fi - for identity in "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT"; do - if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then - printf 'producer artifact identity is invalid\n' >&2 - exit 1 - fi - done - if [[ ! "$ARTIFACT_DIRECTORY" =~ ^isolated-[a-z0-9][a-z0-9._-]*$ ]]; then - printf 'artifact validation directory is unsafe\n' >&2 - exit 1 - fi - mapfile -d '' entries < <( - /usr/bin/find "$ARTIFACT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0 - ) - if [ "${#entries[@]}" -ne 1 ] || [ ! -f "${entries[0]}" ] || [ -L "${entries[0]}" ]; then - printf 'artifact handoff must contain exactly one regular file\n' >&2 - exit 1 - fi - observed_digest="$(/usr/bin/sha256sum "${entries[0]}")" - observed_digest="${observed_digest%% *}" - if [ "$observed_digest" != "$EXPECTED_ARTIFACT_DIGEST" ]; then - printf 'artifact digest mismatch: expected %s, got %s\n' \ - "$EXPECTED_ARTIFACT_DIGEST" "$observed_digest" >&2 - exit 1 - fi - printf 'validated artifact %s from run %s attempt %s\n' \ - "$EXPECTED_ARTIFACT_ID" "$EXPECTED_SOURCE_RUN_ID" "$EXPECTED_SOURCE_RUN_ATTEMPT" - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Extract the validated authorization handoff - run: | - mkdir evaluated - tar -xf isolated-stable-authorization-handoff/stable-authorization-handoff.tar \ - -C evaluated - - - name: Recheck the exact public evidence after approval - env: - AUTHORIZATION_REQUEST: ${{ inputs.authorization_request }} - GITHUB_TOKEN: ${{ github.token }} - run: | - python - <<'PYTHON' - import json - import os - from pathlib import Path - - request = json.loads(os.environ["AUTHORIZATION_REQUEST"]) - Path("stable-authorization-request.json").write_text( - json.dumps(request, indent=2, sort_keys=True) + "\n", - encoding="utf-8", - ) - PYTHON - cmp --silent stable-authorization-request.json evaluated/stable-authorization-request.json - python scripts/stable_authorization.py readout \ - stable-authorization/contract.json \ - stable-authorization-request.json \ - release-critical-readout.json \ - --verify-public-sources - cmp --silent release-critical-readout.json evaluated/release-critical-readout.json - python scripts/stable_authorization.py require-ready \ - stable-authorization/contract.json \ - stable-authorization-request.json \ - release-critical-readout.json - - - name: Compare an existing immutable decision - id: compatibility - run: | - python scripts/stable_authorization.py check \ - stable-authorization/contract.json \ - stable-authorization-request.json \ - release-critical-readout.json \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or compare the immutable authorization - id: record - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - git_authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$git_authorization" - GIT_CONFIG_COUNT=1 \ - GIT_CONFIG_KEY_0=http.https://github.com/.extraheader \ - GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $git_authorization" \ - python scripts/stable_authorization.py record \ - stable-authorization/contract.json \ - stable-authorization-request.json \ - release-critical-readout.json \ - --authoritative-authorization authoritative-stable-authorization.json \ - --authoritative-request authoritative-stable-authorization-request.json \ - --authoritative-readout authoritative-release-critical-readout.json \ - --authoritative-contract authoritative-release-critical-contract.json \ - --actor "$GITHUB_ACTOR" \ - --run-id "$GITHUB_RUN_ID" \ - --run-attempt "$GITHUB_RUN_ATTEMPT" \ - --workflow-ref "$GITHUB_WORKFLOW_REF" \ - --workflow-commit "$GITHUB_SHA" \ - --github-output "$GITHUB_OUTPUT" - - - name: Create or repair the durable GitHub Release mirror - env: - AUTHORIZATION_TAG: ${{ steps.record.outputs.tag }} - GH_TOKEN: ${{ github.token }} - RECORD_COMMIT: ${{ steps.record.outputs.commit }} - run: | - set -euo pipefail - mirror_dir="$(mktemp -d)" - trap 'rm -rf "$mirror_dir"' EXIT - if ! gh release view "$AUTHORIZATION_TAG" >/dev/null 2>&1; then - release_notes="Stable authorization at immutable Git commit ${RECORD_COMMIT}. " - release_notes+="This record authorizes, but does not publish, the product artifact tuple." - gh release create "$AUTHORIZATION_TAG" --verify-tag --prerelease \ - --title "Protected stable 2.0 authorization" \ - --notes "$release_notes" - fi - repair_asset() { - source_path="$1" - asset_name="$2" - rm -f "$mirror_dir/$asset_name" - if gh release download "$AUTHORIZATION_TAG" --pattern "$asset_name" --dir "$mirror_dir" >/dev/null 2>&1; then - cmp --silent "$source_path" "$mirror_dir/$asset_name" || { - printf 'release asset %s differs from immutable Git authority\n' "$asset_name" >&2 - exit 1 - } - else - upload_dir="$(mktemp -d)" - cp "$source_path" "$upload_dir/$asset_name" - gh release upload "$AUTHORIZATION_TAG" "$upload_dir/$asset_name" - rm -rf "$upload_dir" - fi - } - repair_asset authoritative-stable-authorization.json stable-authorization.json - repair_asset authoritative-stable-authorization-request.json stable-authorization-request.json - repair_asset authoritative-release-critical-readout.json release-critical-readout.json - repair_asset authoritative-release-critical-contract.json release-critical-contract.json - - - name: Retain protected stable authorization evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: stable-authorization-${{ github.run_id }}-${{ github.run_attempt }} - path: authoritative-*.json - if-no-files-found: error - retention-days: 90 diff --git a/.github/workflows/target-qualification-audit.yml b/.github/workflows/target-qualification-audit.yml deleted file mode 100644 index 49b02c6..0000000 --- a/.github/workflows/target-qualification-audit.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Target Qualification Audit - -on: - schedule: - - cron: '23 4 * * *' - workflow_dispatch: - -permissions: - actions: read - checks: read - contents: read - -concurrency: - group: target-qualification-audit - cancel-in-progress: false - -jobs: - audit: - name: Audit exact target commits and branch rules - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Install qualification dependencies - run: python -m pip install PyYAML==6.0.2 - - - name: Audit GitHub qualification authority - env: - GITHUB_TOKEN: ${{ github.token }} - run: >- - python scripts/qualification_policy.py audit - --evidence target-qualification-evidence.json - - - name: Retain exact-commit audit evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: target-qualification-${{ github.run_id }}-${{ github.run_attempt }} - path: target-qualification-evidence.json - if-no-files-found: error diff --git a/.github/workflows/waterline-release-train.yml b/.github/workflows/waterline-release-train.yml deleted file mode 100644 index f05e12e..0000000 --- a/.github/workflows/waterline-release-train.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Waterline release train - -on: - workflow_dispatch: - inputs: - plan_tag: - description: Immutable release-plan tag containing the exact Waterline successor - required: true - type: string - -permissions: - contents: read - -jobs: - qualify: - name: Qualify exact-current Waterline successor - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: '3.13' - - - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 - with: - php-version: '8.4' - tools: composer:v2 - - - name: Install contract validators - run: python -m pip install jsonschema==4.23.0 - - - name: Verify immutable plan and fresh public evidence - env: - GITHUB_TOKEN: ${{ github.token }} - PLAN_TAG: ${{ inputs.plan_tag }} - run: | - python scripts/waterline_train.py qualify-public \ - --plan-tag "$PLAN_TAG" \ - --evidence waterline-release-completion.json - - - name: Validate the generated completion record - run: | - python scripts/waterline_train.py validate-contract - python -m jsonschema \ - -i waterline-train/contract.json \ - waterline-train/schema.json - python -m jsonschema \ - -i waterline-release-completion.json \ - waterline-train/completion-evidence-schema.json - - - name: Retain exact public successor evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: waterline-release-completion-${{ github.run_id }}-${{ github.run_attempt }} - path: waterline-release-completion.json - if-no-files-found: error diff --git a/AGENTS.md b/AGENTS.md index 0babf2b..b14c7a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,8 +134,9 @@ not post lease heartbeats, raw local logs, or vague status text. ## Conformance -- The public [conformance runbook](conformance/README.md) is the single list of - release-critical experiments for stable 2.0. +- The public [conformance runbook](conformance/README.md) defines the shared + product experiments. A release issue identifies which experiments are + required for that release or compatibility change. - Use only published packages and images from one exact artifact tuple. - Run ordinary cells in GitHub Actions. Run Docker failure-mode or private Cloud experiments locally when hosted Actions are unsuitable. @@ -151,27 +152,27 @@ not post lease heartbeats, raw local logs, or vague status text. ## Releases -- Do not publish stable `2.0.0` without explicit human authorization after the - fixed conformance tier passes on the exact proposed tuple. - Never move or reuse a published tag. Advance prerelease versions. - Workflow and Waterline develop on `v2`; `master` is only for explicitly approved 1.x maintenance. - Server, CLI, AI, PHP SDK, Python SDK, and Rust SDK develop on `main`. -- During the synchronized 2.0 train, release repositories publish the exact - prerelease declared by source. Sample App, Cloud, and the docs site do not - publish versioned packages. -- A release issue states the proposed tuple, checks, conformance links, and - human decision. Do not hide release readiness in local output. - -## Documentation Priority Before 2.0 - -- Until stable 2.0 ships, defer non-release-critical docs redesign and repeated - screenshot churn. Track deferred work in GitHub. -- Fix release-blocking correctness, broken links, security, and unusable - onboarding when encountered. -- After stable, make 2.0 the primary docs and search surface, remove prerelease - framing, and position Durable Workflow as language-neutral rather than - Laravel-only. +- Each package repository owns ordinary semantic-version releases and + publication through a small repository-local workflow. Sample App, Cloud, + and the documentation site do not publish versioned packages. +- A major release issue states the proposed versions, checks, conformance links, + and human decision. Patch and minor releases follow repository policy and + semantic versioning without a central cross-repository release controller. +- Historical 2.0 prerelease plans, candidates, and authorization records are + immutable launch history, not current release authority. + +## Documentation After Stable 2.0 + +- Keep 2.0 as the primary documentation and search surface. +- Position Durable Workflow as language-neutral while preserving a clear, + first-class Laravel embedded path. +- Prioritize correct, runnable onboarding and broken-link fixes. Use focused + visual checks for changed user journeys instead of repeated site-wide + screenshot churn. ## Human Boundaries diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9209f9d..7051873 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ and private deployment details. ## Visible work -GitHub is the project control plane. Start substantial work from an issue in the +GitHub is the project record. Start substantial work from an issue in the owning repository and open a draft pull request early. Issues record priority, decisions, and blockers; pull requests record implementation, review, checks, and merge evidence. Local worktrees and containers are execution environments, @@ -26,12 +26,11 @@ override product judgment, priority, or roadmap. ## Conformance -The [conformance runbook](conformance/README.md) is the source of truth for the -fixed 2.0 release-critical experiment tier. GitHub Actions runs suitable cells. -Infrastructure experiments may run locally from the versioned runbook, but the -exact tuple, runner revision, outcome, and sanitized evidence must be reported -to the public GitHub run ledger. Machine-only experiment state is not valid -release evidence. +The [conformance runbook](conformance/README.md) defines the shared product +experiments. GitHub Actions runs suitable cells. Infrastructure experiments may +run locally from the versioned runbook, but the exact versions, runner revision, +outcome, and sanitized findings must be recorded on the owning GitHub issue or +pull request. Machine-only experiment state is not release evidence. ## Replay and payload codecs diff --git a/README.md b/README.md index a0e6378..98dc76a 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,31 @@ # Durable Workflow -Welcome to the Durable Workflow project! This project is dedicated to creating tools and libraries for developers to build and manage durable, long-running, persistent, distributed workflows. - -## GitHub project control plane - -GitHub issues, pull requests, Actions, releases, and retained conformance -evidence are the complete project record. Contributors do not need access to a -separate tracker or build coordinator. The working rules are in -[`AGENTS.md`](AGENTS.md) and [`CONTRIBUTING.md`](CONTRIBUTING.md), and the fixed 2.0 release-critical -experiment set is in the [conformance runbook](conformance/README.md). - -## Release authority - -The machine-readable [product-train authority](product-train/README.md) maps -one Durable Workflow 2.0 choice to the exact seven-component tuple, every -required distribution, and their ecosystem install commands. It also defines -synchronized beta progression and the historical-prerelease policy. - -This repository also owns the organization-level automation that records -verified, immutable beta candidate tuples on GitHub. See the -[candidate record contract](candidates/README.md) for the public artifact -surfaces, source-identity checks, durable query paths, and recovery behavior. - -It also records and observes channel-aware, pre-publication -[release plans](release-plans/README.md). Publication remains repository-owned: -each component discovers plans and resumes with only its own GitHub token and -repository-local publication environment. - -The protected [beta authorization authority](beta-authorization/README.md) -binds a product-owner decision to the exact proposed beta plan and its public -qualification, candidate, conformance, continuity, and backlog evidence. Its -append-only Git and GitHub Release record authorizes beta only; stable 2.0 has -a separate decision boundary. - -The [stable 2.0 authorization authority](stable-authorization/README.md) -evaluates a fixed release-critical conformance tier against one tagged -seven-component artifact tuple. It publishes per-experiment and per-SDK-cell -readouts, rejects incomplete or stale evidence, and reaches its protected -human decision only after the evidence gate passes. - -The scheduled [workspace-unavailable continuity controller](beta-continuity/README.md) -assembles those public authorities into an interruption-and-resume drill. Its -append-only phase records bind GitHub issue intake, exact qualification, -release recovery, public verification, and clean-runner conformance without a -bootstrap workspace. - -Published candidate tuples can be exercised by the independently runnable -[beta conformance workflow](beta-conformance/README.md). Its retained GitHub -Release evidence binds every experiment to the exact seven-component tuple, -all required distribution identities, their source identities, and the -conformance runner revision. - -New product work and the deliberately selected unresolved beta backlog use -[GitHub issue authority](issue-authority/README.md). Organization issue forms -capture durable intake context, and GitHub issue state is authoritative. - -Confirmed replay and payload-codec defects are also governed by an append-only -[regression-corpus contract](regression-corpus/README.md). Its repository-local -completion checks distinguish durable protocol evidence from tests that merely -repeat implementation details. - -The [public target qualification policy](qualification/README.md) also enforces -the organization-wide Actions trust boundary: immutable action and container -references, job-scoped credentials, isolated pull requests and caches, and -reviewed artifact consumers. - -The machine-readable -[public repository hygiene inventory](repository-hygiene/inventory.json) -records the maintained protected branches, the synchronized release train, and -the cleanup completed for the 2.0 product surface. +Durable Workflow is a durable execution platform for PHP, Python, and Rust. +It supports embedded Laravel applications, self-hosted Server deployments, and +the managed Durable Workflow Cloud runtime. + +## Project coordination + +GitHub issues record accepted work, priority, decisions, and blockers. Pull +requests contain implementation and review, repository Actions run ordinary +checks and publication, and releases identify published artifacts. There is no +separate work tracker or cross-repository lifecycle controller. + +Organization-wide working rules are in [`AGENTS.md`](AGENTS.md), and contributor +guidance is in [`CONTRIBUTING.md`](CONTRIBUTING.md). + +## Shared contracts + +- [`conformance/`](conformance/) describes the product experiments used to + qualify releases and important compatibility changes. +- [`regression-corpus/`](regression-corpus/) defines the shared evidence format + for replay and payload-codec regressions. +- [`product-train/`](product-train/) currently supplies the version authority + consumed by the documentation site while stable-version discovery is being + simplified. +- [`visual-evidence/`](visual-evidence/) contains the bounded browser-evidence + contract used by the Rust SDK documentation workflow. + +Historical prerelease tags and GitHub Releases remain available as immutable +launch history. They are not active project state and no scheduled workflow +advances or repairs them. diff --git a/package-lock.json b/package-lock.json index 1f54b03..d912a2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "durable-workflow-github-control-plane", + "name": "durable-workflow-project-guidance", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "durable-workflow-github-control-plane", + "name": "durable-workflow-project-guidance", "dependencies": { "@sparticuz/chromium": "140.0.0", "playwright-core": "1.55.0" diff --git a/package.json b/package.json index 3f0ada9..348b062 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "durable-workflow-github-control-plane", + "name": "durable-workflow-project-guidance", "private": true, "scripts": { - "test:visual-capture": "node --check scripts/visual_capture_boundary.mjs && node --check scripts/pipeline_visual_capture.mjs && node --test tests/test_visual_capture.mjs" + "test:visual-capture": "node --check scripts/visual_capture_boundary.mjs && node --check scripts/visual_capture.mjs && node --test tests/test_visual_capture.mjs" }, "dependencies": { "@sparticuz/chromium": "140.0.0", diff --git a/scripts/__init__.py b/scripts/__init__.py index bc1628c..8c241d1 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -1 +1 @@ -"""Durable Workflow public control-plane tooling.""" +"""Durable Workflow shared project tooling.""" diff --git a/scripts/pipeline_visual_capture.mjs b/scripts/visual_capture.mjs similarity index 98% rename from scripts/pipeline_visual_capture.mjs rename to scripts/visual_capture.mjs index 1629bc3..886bfd6 100755 --- a/scripts/pipeline_visual_capture.mjs +++ b/scripts/visual_capture.mjs @@ -45,8 +45,8 @@ function loadChromium() { const runtimes = [ ['playwright-core', '@sparticuz/chromium'], [ - '/opt/pipeline-visual/node_modules/playwright-core', - '/opt/pipeline-visual/node_modules/@sparticuz/chromium', + '/opt/durable-workflow-visual/node_modules/playwright-core', + '/opt/durable-workflow-visual/node_modules/@sparticuz/chromium', ], ]; for (const [playwrightModuleName, chromiumModuleName] of runtimes) { @@ -94,8 +94,10 @@ function isExecutable(candidate) { } async function chromiumExecutable(chromium, chromiumRuntime) { - if (process.env.PIPELINE_CHROMIUM_PATH) { - if (isExecutable(process.env.PIPELINE_CHROMIUM_PATH)) return process.env.PIPELINE_CHROMIUM_PATH; + if (process.env.DURABLE_WORKFLOW_CHROMIUM_PATH) { + if (isExecutable(process.env.DURABLE_WORKFLOW_CHROMIUM_PATH)) { + return process.env.DURABLE_WORKFLOW_CHROMIUM_PATH; + } throw new Error('the configured Chromium executable is unavailable'); } const candidates = [ @@ -639,7 +641,7 @@ function installWorkerPersistentConnectionBoundary({ bindingName, captureOrigin, const trustedMessagePortStart = MessagePort.prototype.start; const markerEntropy = new Uint32Array(4); crypto.getRandomValues(markerEntropy); - const marker = `pipeline-visual-capture-${[...markerEntropy].join('-')}`; + const marker = `durable-workflow-visual-capture-${[...markerEntropy].join('-')}`; const relayMarker = `${marker}-relay`; const sharedBoundaryRelayPorts = new Set(); const boundaryArguments = JSON.stringify({ bindingName, captureOrigin }); @@ -863,7 +865,7 @@ function relativeArtifactPath(manifestPath, artifactPath) { function updateManifest(options, screenshotPath, reportPath, report) { const manifestPath = ensureOutputPath(options.manifest); - let manifest = { schema: 'durable-workflow.pipeline.visual-review/v1', captures: [] }; + let manifest = { schema: 'durable-workflow.visual-review/v1', captures: [] }; if (fs.existsSync(manifestPath)) { try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); @@ -871,7 +873,7 @@ function updateManifest(options, screenshotPath, reportPath, report) { fail('existing visual manifest is not valid JSON'); } } - if (manifest.schema !== 'durable-workflow.pipeline.visual-review/v1' || !Array.isArray(manifest.captures)) { + if (manifest.schema !== 'durable-workflow.visual-review/v1' || !Array.isArray(manifest.captures)) { fail('existing visual manifest has an unsupported schema'); } if (options.source && manifest.source && JSON.stringify(manifest.source) !== JSON.stringify(options.source)) { @@ -1298,7 +1300,7 @@ try { reducedMotion: 'reduce', serviceWorkers: 'block', }); - const persistentBoundaryBinding = '__pipelineVisualCaptureRejectPersistentConnection'; + const persistentBoundaryBinding = '__durableWorkflowVisualCaptureRejectPersistentConnection'; await context.exposeBinding(persistentBoundaryBinding, (_source, connection) => { if (connection?.transport === 'webrtc') { boundary.reject(rejectedWebRtcDestination(connection.url), 'WebRTC persistent connection'); @@ -1483,7 +1485,7 @@ try { })); const report = { - schema: 'durable-workflow.pipeline.visual-capture/v1', + schema: 'durable-workflow.visual-capture/v1', captured_at: new Date().toISOString(), surface: String(options.surface).trim(), state: String(options.state || 'default').trim() || 'default', diff --git a/scripts/visual_evidence.py b/scripts/visual_evidence.py index 68c19af..7188f19 100644 --- a/scripts/visual_evidence.py +++ b/scripts/visual_evidence.py @@ -13,8 +13,8 @@ ROOT = Path(__file__).resolve().parents[1] DEFAULT_POLICY = ROOT / "visual-evidence" / "policy.json" -MANIFEST_SCHEMA = "durable-workflow.pipeline.visual-review/v1" -REPORT_SCHEMA = "durable-workflow.pipeline.visual-capture/v1" +MANIFEST_SCHEMA = "durable-workflow.visual-review/v1" +REPORT_SCHEMA = "durable-workflow.visual-capture/v1" class VisualEvidenceError(RuntimeError): diff --git a/tests/fixtures/visual-evidence/network-request.html b/tests/fixtures/visual-evidence/network-request.html index 232964a..7edb0a6 100644 --- a/tests/fixtures/visual-evidence/network-request.html +++ b/tests/fixtures/visual-evidence/network-request.html @@ -21,7 +21,7 @@ new Worker(URL.createObjectURL(new Blob([workerSource], { type: 'text/javascript' }))); } else if (kind === 'worker-webtransport-binding-replacement') { const workerSource = ` - globalThis.__pipelineVisualCaptureRejectPersistentConnection = () => Promise.resolve(); + globalThis.__durableWorkflowVisualCaptureRejectPersistentConnection = () => Promise.resolve(); try { new WebTransport(${JSON.stringify(target)}); } catch {} `; new Worker(URL.createObjectURL(new Blob([workerSource], { type: 'text/javascript' }))); @@ -93,7 +93,7 @@ ) { try { if (kind === 'webrtc-binding-replacement') { - globalThis.__pipelineVisualCaptureRejectPersistentConnection = () => Promise.resolve(); + globalThis.__durableWorkflowVisualCaptureRejectPersistentConnection = () => Promise.resolve(); } let PeerConnection = RTCPeerConnection; if (kind === 'webrtc-constructor-alias') { diff --git a/tests/test_visual_capture.mjs b/tests/test_visual_capture.mjs index 2ef1fd1..3b254dd 100644 --- a/tests/test_visual_capture.mjs +++ b/tests/test_visual_capture.mjs @@ -15,7 +15,7 @@ import { allowedRepositoryFactsRequest } from '../scripts/visual_capture_boundar const execFileAsync = promisify(execFile); const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const fixtureRoot = path.join(root, 'tests', 'fixtures', 'visual-evidence'); -const captureScript = path.join(root, 'scripts', 'pipeline_visual_capture.mjs'); +const captureScript = path.join(root, 'scripts', 'visual_capture.mjs'); let artifactRoot; let server; let rejectedServer; @@ -815,7 +815,7 @@ test('rejects unsafe URLs and preserves sanitized pre-report runtime errors', as ); await assert.rejects( capture('missing-browser', { - env: { PIPELINE_CHROMIUM_PATH: path.join(artifactRoot, 'missing-chromium') }, + env: { DURABLE_WORKFLOW_CHROMIUM_PATH: path.join(artifactRoot, 'missing-chromium') }, }), (error) => { assert.match(error.message, /visual capture exited before writing its report/); diff --git a/tests/test_visual_evidence.py b/tests/test_visual_evidence.py index 812d304..cd850a5 100644 --- a/tests/test_visual_evidence.py +++ b/tests/test_visual_evidence.py @@ -34,7 +34,7 @@ def test_policy_matches_its_schema(self) -> None: Draft202012Validator.check_schema(schema) Draft202012Validator(schema).validate(self.policy) - def test_source_qualification_pins_an_isolated_browser_runtime(self) -> None: + def test_browser_runtime_dependencies_are_pinned(self) -> None: package = json.loads((ROOT / "package.json").read_bytes()) lock = json.loads((ROOT / "package-lock.json").read_bytes()) self.assertEqual("140.0.0", package["dependencies"]["@sparticuz/chromium"]) @@ -47,22 +47,6 @@ def test_source_qualification_pins_an_isolated_browser_runtime(self) -> None: self.assertEqual(expected_version, locked["version"]) self.assertTrue(locked["integrity"].startswith("sha512-")) - workflow = yaml.safe_load((ROOT / ".github/workflows/source-qualification.yml").read_bytes()) - self.assertEqual({"contents": "read"}, workflow["permissions"]) - steps = workflow["jobs"]["source"]["steps"] - setup_node = next(step for step in steps if str(step.get("uses", "")).startswith("actions/setup-node@")) - self.assertNotIn("cache", setup_node.get("with", {})) - visual_capture = next(step for step in steps if step.get("name") == "Validate visual capture") - self.assertEqual( - "${{ runner.temp }}/visual-chromium-${{ github.run_id }}-${{ github.run_attempt }}", - visual_capture["env"]["TMPDIR"], - ) - self.assertIn("npm run test:visual-capture", visual_capture["run"]) - for step in steps: - if "actions/upload-artifact@" not in str(step.get("uses", "")): - continue - self.assertIn("github.event_name != 'pull_request'", step["if"]) - def test_rust_reusable_workflow_captures_and_retains_the_exact_candidate_matrix(self) -> None: workflow = yaml.safe_load((ROOT / ".github/workflows/rust-docs-visual.yml").read_bytes()) self.assertEqual({"contents": "read"}, workflow["permissions"]) @@ -252,7 +236,7 @@ def capture(self, root: Path, interaction: str, state: str, width: int, selector report.write_text( json.dumps( { - "schema": "durable-workflow.pipeline.visual-capture/v1", + "schema": "durable-workflow.visual-capture/v1", "surface": "docs", "state": state, "viewport": viewport, @@ -369,7 +353,7 @@ def test_admission_checks_a_supplied_manifest_without_interaction_classification def write_manifest(self, root: Path, captures: list[dict[str, Any]]) -> Path: manifest = root / "manifest.json" manifest.write_text( - json.dumps({"schema": "durable-workflow.pipeline.visual-review/v1", "captures": captures}), + json.dumps({"schema": "durable-workflow.visual-review/v1", "captures": captures}), encoding="utf-8", ) return manifest @@ -390,7 +374,7 @@ def rust_capture( viewport = {"width": width, "height": height} interactions = [{"type": "click", "selector": selector} for selector in selectors or []] report = { - "schema": "durable-workflow.pipeline.visual-capture/v1", + "schema": "durable-workflow.visual-capture/v1", "surface": "rust-sdk-reference", "state": state, "viewport": viewport, @@ -441,7 +425,7 @@ def rust_manifest( manifest.write_text( json.dumps( { - "schema": "durable-workflow.pipeline.visual-review/v1", + "schema": "durable-workflow.visual-review/v1", "source": source, "captures": captures, } diff --git a/visual-evidence/README.md b/visual-evidence/README.md index 47b09f4..c2d57b4 100644 --- a/visual-evidence/README.md +++ b/visual-evidence/README.md @@ -38,8 +38,8 @@ manifest, every capture entry, and every report must bind to the same expected ## Shared capture runtime -`scripts/pipeline_visual_capture.mjs` is the canonical source for the shared -`pipeline-visual-capture` command. `npm ci` installs an integrity-locked browser +`scripts/visual_capture.mjs` is the canonical source for the shared visual +capture command. `npm ci` installs an integrity-locked browser driver and Linux Chromium payload with exact package identities. CI extracts that payload under a run-specific job temporary directory; pull-request code does not receive a writable shared browser or package cache, and browser setup