From 886924d86420413fdcaffd0949b5fb1a86b33f5c Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Tue, 1 Sep 2026 06:41:11 +0000 Subject: [PATCH] Remove retired prerelease lifecycle contracts --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/ISSUE_TEMPLATE/cross_repository.yml | 4 +- .github/ISSUE_TEMPLATE/product_defect.yml | 2 +- .github/workflows/ci.yml | 2 + .../release-recovery-consumer-conformance.yml | 47 - AGENTS.md | 13 +- CONTRIBUTING.md | 6 +- README.md | 14 +- beta-authorization/README.md | 54 - beta-authorization/evidence-schema.json | 263 - beta-authorization/record-schema.json | 59 - beta-authorization/request-schema.json | 73 - beta-conformance/README.md | 134 - beta-conformance/contract-schema.json | 124 - beta-conformance/contract.json | 266 - beta-conformance/plan-schema.json | 200 - beta-conformance/result-schema.json | 271 - beta-conformance/suite-result-schema.json | 97 - beta-continuity/README.md | 68 - beta-continuity/config.json | 53 - beta-continuity/schema.json | 104 - candidates/README.md | 56 - candidates/main.json | 34 - candidates/schema.json | 60 - candidates/verification-schema.json | 411 - issue-authority/README.md | 242 - issue-authority/backlog-schema.json | 153 - issue-authority/backlog.json | 146 - ...egacy-cross-repository-targets-schema.json | 332 - .../legacy-cross-repository-targets.json | 557 -- issue-authority/policy-schema.json | 272 - issue-authority/policy.json | 300 - product-train/README.md | 43 - product-train/current.json | 69 - .../payload-codec-contract-schema.json | 57 - product-train/payload-codec-contract.json | 30 - product-train/schema.json | 181 - .../sdk-server-qualification-schema.json | 208 - product-train/sdk-server-qualification.json | 147 - qualification/README.md | 67 +- qualification/policy.json | 118 +- release-plans/README.md | 161 - release-plans/candidate-schema.json | 74 - .../continuity-resolution-schema.json | 140 - .../continuity-successor-selection.json | 46 - release-plans/current-source-preparation.json | 74 - release-plans/current.json | 40 - release-plans/failure-schema.json | 323 - ...-release-candidate-source-preparation.json | 74 - release-plans/first-release-candidate.json | 43 - release-plans/preparation-schema.json | 111 - release-plans/schema.json | 118 - release-plans/source-preparation-schema.json | 168 - release-recovery/README.md | 43 - release-recovery/authority.json | 63 - .../consumer-conformance/README.md | 34 - .../consumer-conformance/adapter-schema.json | 109 - .../consumer-conformance/contract-schema.json | 106 - .../consumer-conformance/contract.json | 102 - .../protected-source-identities-schema.json | 195 - .../protected-source-identities.json | 329 - release-recovery/schema.json | 68 - repository-hygiene/inventory-schema.json | 120 - repository-hygiene/inventory.json | 188 - scripts/beta_authorization.py | 1777 ----- scripts/beta_candidate.py | 1694 ---- scripts/beta_conformance.py | 3416 -------- scripts/beta_continuity.py | 3002 ------- scripts/component_release_recovery.py | 3420 -------- scripts/cross_repository_lifecycle.py | 1214 --- scripts/current_plan_publication.py | 772 -- scripts/handoff_recovery.py | 428 - scripts/issue_authority.py | 5637 ------------- scripts/packagist_metadata.py | 120 - scripts/product_train.py | 435 - scripts/protected_release_plan_writer.py | 222 - scripts/recovery_authority_reconciliation.py | 483 -- scripts/recovery_workflow_authority.py | 1074 --- scripts/release_asset_mirror.py | 278 - scripts/release_plan.py | 4196 ---------- .../release_recovery_consumer_conformance.py | 1961 ----- .../release_recovery_target_qualification.py | 197 - scripts/stable_authorization.py | 1035 --- scripts/waterline_service_conformance.py | 229 - scripts/waterline_train.py | 817 -- stable-authorization/README.md | 35 - .../authorization-schema.json | 197 - stable-authorization/contract-schema.json | 80 - stable-authorization/contract.json | 47 - stable-authorization/readout-schema.json | 192 - stable-authorization/request-schema.json | 210 - .../activities-cross-language-php-sdk.json | 92 - .../github/cross-referenced-events.json | 50 - .../github/pipeline-completion-comment.json | 33 - tests/test_beta_authorization.py | 1093 --- tests/test_beta_candidate.py | 1015 --- tests/test_beta_conformance.py | 3385 -------- tests/test_beta_continuity.py | 2252 ------ tests/test_cli_release_authority.py | 711 -- tests/test_component_release_recovery.py | 2247 ------ tests/test_current_plan_publication.py | 619 -- tests/test_handoff_recovery.py | 272 - tests/test_issue_authority.py | 7043 ----------------- tests/test_product_train.py | 320 - tests/test_protected_release_plan_writer.py | 262 - tests/test_qualification_policy.py | 1944 +---- .../test_recovery_authority_reconciliation.py | 848 -- tests/test_recovery_workflow_authority.py | 298 - tests/test_release_asset_mirror.py | 201 - tests/test_release_plan.py | 3567 --------- ...t_release_recovery_consumer_conformance.py | 360 - ...t_release_recovery_target_qualification.py | 236 - tests/test_release_source_preparation.py | 112 - tests/test_repository_hygiene.py | 66 - tests/test_stable_authorization.py | 536 -- tests/test_waterline_service_conformance.py | 63 - tests/test_waterline_train.py | 328 - tests/verification_fixture.py | 295 - waterline-train/README.md | 22 - .../completion-evidence-schema.json | 237 - waterline-train/contract.json | 31 - waterline-train/schema.json | 54 - 122 files changed, 136 insertions(+), 69692 deletions(-) delete mode 100644 .github/workflows/release-recovery-consumer-conformance.yml delete mode 100644 beta-authorization/README.md delete mode 100644 beta-authorization/evidence-schema.json delete mode 100644 beta-authorization/record-schema.json delete mode 100644 beta-authorization/request-schema.json delete mode 100644 beta-conformance/README.md delete mode 100644 beta-conformance/contract-schema.json delete mode 100644 beta-conformance/contract.json delete mode 100644 beta-conformance/plan-schema.json delete mode 100644 beta-conformance/result-schema.json delete mode 100644 beta-conformance/suite-result-schema.json delete mode 100644 beta-continuity/README.md delete mode 100644 beta-continuity/config.json delete mode 100644 beta-continuity/schema.json delete mode 100644 candidates/README.md delete mode 100644 candidates/main.json delete mode 100644 candidates/schema.json delete mode 100644 candidates/verification-schema.json delete mode 100644 issue-authority/README.md delete mode 100644 issue-authority/backlog-schema.json delete mode 100644 issue-authority/backlog.json delete mode 100644 issue-authority/legacy-cross-repository-targets-schema.json delete mode 100644 issue-authority/legacy-cross-repository-targets.json delete mode 100644 issue-authority/policy-schema.json delete mode 100644 issue-authority/policy.json delete mode 100644 product-train/README.md delete mode 100644 product-train/current.json delete mode 100644 product-train/payload-codec-contract-schema.json delete mode 100644 product-train/payload-codec-contract.json delete mode 100644 product-train/schema.json delete mode 100644 product-train/sdk-server-qualification-schema.json delete mode 100644 product-train/sdk-server-qualification.json delete mode 100644 release-plans/README.md delete mode 100644 release-plans/candidate-schema.json delete mode 100644 release-plans/continuity-resolution-schema.json delete mode 100644 release-plans/continuity-successor-selection.json delete mode 100644 release-plans/current-source-preparation.json delete mode 100644 release-plans/current.json delete mode 100644 release-plans/failure-schema.json delete mode 100644 release-plans/first-release-candidate-source-preparation.json delete mode 100644 release-plans/first-release-candidate.json delete mode 100644 release-plans/preparation-schema.json delete mode 100644 release-plans/schema.json delete mode 100644 release-plans/source-preparation-schema.json delete mode 100644 release-recovery/README.md delete mode 100644 release-recovery/authority.json delete mode 100644 release-recovery/consumer-conformance/README.md delete mode 100644 release-recovery/consumer-conformance/adapter-schema.json delete mode 100644 release-recovery/consumer-conformance/contract-schema.json delete mode 100644 release-recovery/consumer-conformance/contract.json delete mode 100644 release-recovery/protected-source-identities-schema.json delete mode 100644 release-recovery/protected-source-identities.json delete mode 100644 release-recovery/schema.json delete mode 100644 repository-hygiene/inventory-schema.json delete mode 100644 repository-hygiene/inventory.json delete mode 100644 scripts/beta_authorization.py delete mode 100644 scripts/beta_candidate.py delete mode 100644 scripts/beta_conformance.py delete mode 100644 scripts/beta_continuity.py delete mode 100644 scripts/component_release_recovery.py delete mode 100644 scripts/cross_repository_lifecycle.py delete mode 100644 scripts/current_plan_publication.py delete mode 100644 scripts/handoff_recovery.py delete mode 100644 scripts/issue_authority.py delete mode 100644 scripts/packagist_metadata.py delete mode 100644 scripts/product_train.py delete mode 100644 scripts/protected_release_plan_writer.py delete mode 100644 scripts/recovery_authority_reconciliation.py delete mode 100644 scripts/recovery_workflow_authority.py delete mode 100644 scripts/release_asset_mirror.py delete mode 100644 scripts/release_plan.py delete mode 100644 scripts/release_recovery_consumer_conformance.py delete mode 100644 scripts/release_recovery_target_qualification.py delete mode 100644 scripts/stable_authorization.py delete mode 100644 scripts/waterline_service_conformance.py delete mode 100644 scripts/waterline_train.py delete mode 100644 stable-authorization/README.md delete mode 100644 stable-authorization/authorization-schema.json delete mode 100644 stable-authorization/contract-schema.json delete mode 100644 stable-authorization/contract.json delete mode 100644 stable-authorization/readout-schema.json delete mode 100644 stable-authorization/request-schema.json delete mode 100644 tests/fixtures/beta-conformance/activities-cross-language-php-sdk.json delete mode 100644 tests/fixtures/github/cross-referenced-events.json delete mode 100644 tests/fixtures/github/pipeline-completion-comment.json delete mode 100644 tests/test_beta_authorization.py delete mode 100644 tests/test_beta_candidate.py delete mode 100644 tests/test_beta_conformance.py delete mode 100644 tests/test_beta_continuity.py delete mode 100644 tests/test_cli_release_authority.py delete mode 100644 tests/test_component_release_recovery.py delete mode 100644 tests/test_current_plan_publication.py delete mode 100644 tests/test_handoff_recovery.py delete mode 100644 tests/test_issue_authority.py delete mode 100644 tests/test_product_train.py delete mode 100644 tests/test_protected_release_plan_writer.py delete mode 100644 tests/test_recovery_authority_reconciliation.py delete mode 100644 tests/test_recovery_workflow_authority.py delete mode 100644 tests/test_release_asset_mirror.py delete mode 100644 tests/test_release_plan.py delete mode 100644 tests/test_release_recovery_consumer_conformance.py delete mode 100644 tests/test_release_recovery_target_qualification.py delete mode 100644 tests/test_release_source_preparation.py delete mode 100644 tests/test_repository_hygiene.py delete mode 100644 tests/test_stable_authorization.py delete mode 100644 tests/test_waterline_service_conformance.py delete mode 100644 tests/test_waterline_train.py delete mode 100644 tests/verification_fixture.py delete mode 100644 waterline-train/README.md delete mode 100644 waterline-train/completion-evidence-schema.json delete mode 100644 waterline-train/contract.json delete mode 100644 waterline-train/schema.json diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index cdb2da2..9beb788 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Durable Workflow documentation url: https://durable-workflow.com/docs/2.0/ - about: Read the explicit prerelease documentation before filing product work. + about: Read the current documentation before filing product work. - name: Public platform conformance url: https://durable-workflow.com/docs/2.0/platform-conformance/ about: Check public artifact contracts and retained compatibility evidence. diff --git a/.github/ISSUE_TEMPLATE/cross_repository.yml b/.github/ISSUE_TEMPLATE/cross_repository.yml index 358d8a4..756ee13 100644 --- a/.github/ISSUE_TEMPLATE/cross_repository.yml +++ b/.github/ISSUE_TEMPLATE/cross_repository.yml @@ -11,7 +11,7 @@ body: attributes: value: | Open this in the repository that owns the shared decision, usually `durable-workflow/.github` for organization-level - release and recovery contracts. Link focused implementation issues in each affected public repository. + product contracts. Link focused implementation issues in each affected public repository. - type: dropdown id: owner @@ -67,7 +67,7 @@ body: id: contract attributes: label: Shared public contract - description: Define exact API, artifact, protocol, release, or recovery behavior and identify its authority. + description: Define the exact API, artifact, protocol, release, or operational behavior shared by the repositories. validations: required: true diff --git a/.github/ISSUE_TEMPLATE/product_defect.yml b/.github/ISSUE_TEMPLATE/product_defect.yml index 0264a5b..cb199d9 100644 --- a/.github/ISSUE_TEMPLATE/product_defect.yml +++ b/.github/ISSUE_TEMPLATE/product_defect.yml @@ -38,7 +38,7 @@ body: attributes: label: Exact version or source identity description: Published version, image digest, tag, or full public commit SHA where the defect is present. - placeholder: 2.0.0-beta.1 or a 40-character commit SHA + placeholder: 2.0.0 or a 40-character commit SHA validations: required: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 924ddb8..adb19ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,8 +32,10 @@ jobs: - name: Test retained shared contracts run: >- python -m unittest + tests.test_qualification_policy tests.test_regression_corpus tests.test_regression_evidence_schema + tests.test_server_codec_counterfactual_schema tests.test_visual_evidence - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 diff --git a/.github/workflows/release-recovery-consumer-conformance.yml b/.github/workflows/release-recovery-consumer-conformance.yml deleted file mode 100644 index 42e00a9..0000000 --- a/.github/workflows/release-recovery-consumer-conformance.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Release-recovery consumer conformance - -on: - push: - branches: [main] - paths: - - '.github/workflows/release-recovery-consumer-conformance.yml' - - 'release-recovery/consumer-conformance/**' - - 'scripts/release_recovery_consumer_conformance.py' - - 'scripts/release_recovery_target_qualification.py' - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: release-recovery-consumer-conformance - cancel-in-progress: false - -jobs: - audit: - name: Audit seven target consumers - runs-on: ubuntu-latest - timeout-minutes: 5 - - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - - name: Audit public target conformance - run: >- - python scripts/release_recovery_target_qualification.py - --contract release-recovery/consumer-conformance/contract.json - --evidence release-recovery-target-qualification-evidence.json - - - name: Retain target conformance evidence - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: release-recovery-consumer-conformance-${{ github.run_id }}-${{ github.run_attempt }} - path: release-recovery-target-qualification-evidence.json - if-no-files-found: error - retention-days: 90 diff --git a/AGENTS.md b/AGENTS.md index b14c7a2..b901a67 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,7 +83,8 @@ not post lease heartbeats, raw local logs, or vague status text. - `status:blocked` must name the external dependency and the exact action that clears it. "Blocked on maintainer review" is not valid; perform the review. - Close duplicates, completed work, and concerns that apply only to superseded - prereleases. Do not preserve prerelease compatibility cruft before stable 2.0. + prereleases. Released stable versions follow semantic versioning; historical + prereleases do not create compatibility obligations. - Dependabot-authored update text is preapproved for review. Process dependency and security updates promptly, then mark handled notifications done. @@ -113,7 +114,7 @@ not post lease heartbeats, raw local logs, or vague status text. hand-roll a protocol primitive already provided by the ecosystem package. - Service-mode SDK payloads are Avro-only. Do not add JSON compatibility for historical 2.0 prereleases. -- Avoid embedding current prerelease numbers in prose. Put exact versions in one +- Avoid embedding current package versions in prose. Put exact versions in one machine-owned manifest or resolver and render them where needed. - Never commit credentials, customer data, private infrastructure details, local filesystem paths, or operator-specific information to public repos. @@ -152,9 +153,11 @@ not post lease heartbeats, raw local logs, or vague status text. ## Releases -- Never move or reuse a published tag. Advance prerelease versions. +- Never move or reuse a published tag. Release new versions according to + semantic versioning. - Workflow and Waterline develop on `v2`; `master` is only for explicitly - approved 1.x maintenance. + approved 1.x maintenance until their tracked branch migrations make `main` + the stable 2.x branch and `1.x` the maintenance branch. - Server, CLI, AI, PHP SDK, Python SDK, and Rust SDK develop on `main`. - Each package repository owns ordinary semantic-version releases and publication through a small repository-local workflow. Sample App, Cloud, @@ -162,8 +165,6 @@ not post lease heartbeats, raw local logs, or vague status text. - 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7051873..3a2a056 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,9 +3,9 @@ Agents and automated contributors must read the organization-wide [`AGENTS.md`](AGENTS.md) before starting work. -Use the repository's focused tests and public qualification workflow for every -change. Keep public examples and evidence free of credentials, customer data, -and private deployment details. +Use the repository's focused tests and GitHub Actions checks for every change. +Keep public examples and evidence free of credentials, customer data, and +private deployment details. ## Visible work diff --git a/README.md b/README.md index 98dc76a..d90f173 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,14 @@ guidance is in [`CONTRIBUTING.md`](CONTRIBUTING.md). ## Shared contracts -- [`conformance/`](conformance/) describes the product experiments used to - qualify releases and important compatibility changes. +- [`qualification/`](qualification/) defines the reviewed GitHub Actions and + workflow trust policy used by repository CI. +- [`conformance/`](conformance/) describes 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. +Historical tags and GitHub Releases remain available as immutable release +history. They are not active project state. diff --git a/beta-authorization/README.md b/beta-authorization/README.md deleted file mode 100644 index 2099b89..0000000 --- a/beta-authorization/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Protected beta authorization - -The `Beta authorization` workflow is the only repository-owned writer for an -immutable `beta-authorization/*` decision. A product owner dispatches the -workflow with one JSON request conforming to -[`request-schema.json`](request-schema.json), and the `beta-authorization` -environment must approve the job before it can receive write authority. - -The request identifies the proposed beta release-plan name and exact seven -component versions and source commits. Workflow and Waterline must use exact -`2.0.0-beta.N` versions. It also cites immutable Git records for the qualified -artifact candidate, retained passing conformance, and the completed and -scheduled-no-op continuity phases. - -Before dispatch, add a comment to the public beta authority issue containing -this marker, where the digest is SHA-256 over the canonical, indented and -key-sorted `authorization` object including its trailing newline: - -```text - -``` - -The request names that comment ID. The workflow verifies that the comment was -created by the dispatching organization member or collaborator, that the -authority issue is still the open classified beta gate, and that no other open -public issue in the governed repository inventory has a `priority:P0` or -`priority:P1` label. - -For a new authorization, the protected job independently runs the public target -qualification audit pinned to all seven intended source commits. It retains the -complete resulting `target-qualification-evidence.json` in the authorization -record and rechecks the exact branch heads immediately before publication. This -qualification is deliberately independent from the earlier continuity drill's -accepted-phase qualification, because continuity proves release recovery rather -than the current candidate's source identity. - -The writer also verifies every cited record and its live immutable tag. -Candidate artifacts and retained conformance must bind all seven intended -source commits. The configured continuity drill must retain its exact -completion and later scheduled no-op authority. The environment must require -reviewers and allow only the `main` branch, and the record retains both the -dispatching actor and the approving GitHub user. - -The authoritative tag is `beta-authorization/`. Its root -commit contains the minimal `beta-authorization.json` consumed by release -plans, a detailed `beta-authorization-evidence.json`, and the exact -`target-qualification-evidence.json`. A GitHub Release with the same tag mirrors -all three files. Repeating an identical request compares and recovers that -record against the exact qualification policy at the recorded controller -commit, including its complete target and action-runtime contract, even when -the current policy has advanced. New authorizations remain subject to the -current policy. A changed request, corrupted retained evidence, or an occupied -conflicting tag fails closed. The action authorizes only the beta channel. -Stable 2.0 remains a separate decision. diff --git a/beta-authorization/evidence-schema.json b/beta-authorization/evidence-schema.json deleted file mode 100644 index 88080c1..0000000 --- a/beta-authorization/evidence-schema.json +++ /dev/null @@ -1,263 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-authorization/evidence-schema.json", - "title": "Durable Workflow beta authorization evidence", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "authorization_sha256", - "request_sha256", - "decision", - "candidate", - "qualification", - "conformance", - "continuity", - "backlog", - "github_authority" - ], - "properties": { - "schema": { "const": "durable-workflow.beta-authorization-evidence/v1" }, - "authorization_sha256": { "$ref": "#/$defs/sha256" }, - "request_sha256": { "$ref": "#/$defs/sha256" }, - "decision": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "issue", "issue_url", "comment", "comment_url", "author", "body_sha256"], - "properties": { - "repository": { "const": "durable-workflow/.github" }, - "issue": { "const": 3 }, - "issue_url": { "const": "https://github.com/durable-workflow/.github/issues/3" }, - "comment": { "type": "integer", "minimum": 1 }, - "comment_url": { "type": "string", "pattern": "^https://github.com/durable-workflow/\\.github/issues/3#issuecomment-" }, - "author": { "$ref": "#/$defs/user" }, - "body_sha256": { "$ref": "#/$defs/sha256" } - } - }, - "candidate": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "manifest_sha256", "verification_sha256"], - "properties": { - "tag": { "type": "string", "pattern": "^beta-candidate/" }, - "commit": { "$ref": "#/$defs/commit" }, - "manifest_sha256": { "$ref": "#/$defs/sha256" }, - "verification_sha256": { "$ref": "#/$defs/sha256" } - } - }, - "qualification": { - "type": "object", - "additionalProperties": false, - "required": ["path", "sha256"], - "properties": { - "path": { "const": "target-qualification-evidence.json" }, - "sha256": { "$ref": "#/$defs/sha256" } - } - }, - "conformance": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "release", "run"], - "properties": { - "tag": { "type": "string", "pattern": "^beta-conformance/" }, - "commit": { "$ref": "#/$defs/commit" }, - "release": { "type": "string", "pattern": "^https://github.com/durable-workflow/\\.github/releases/tag/" }, - "run": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "run_id", "run_attempt", "evidence_tag"], - "properties": { - "repository": { "const": "durable-workflow/.github" }, - "run_id": { "type": "integer", "minimum": 1 }, - "run_attempt": { "type": "integer", "minimum": 1 }, - "evidence_tag": { "type": "string", "pattern": "^beta-conformance/" } - } - } - } - }, - "continuity": { - "type": "object", - "additionalProperties": false, - "required": ["complete", "no_op", "plan"], - "properties": { - "complete": { "$ref": "#/$defs/gitRecord" }, - "no_op": { "$ref": "#/$defs/gitRecord" }, - "plan": { "$ref": "#/$defs/digestRecord" } - } - }, - "backlog": { - "type": "object", - "additionalProperties": false, - "required": ["repositories", "allowed_authorization_gate", "unresolved_p0_p1"], - "properties": { - "repositories": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "string", "pattern": "^durable-workflow/" } - }, - "allowed_authorization_gate": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "number", "url"], - "properties": { - "repository": { "const": "durable-workflow/.github" }, - "number": { "const": 3 }, - "url": { "const": "https://github.com/durable-workflow/.github/issues/3" } - } - }, - "unresolved_p0_p1": { "type": "array", "maxItems": 0 } - } - }, - "github_authority": { - "type": "object", - "additionalProperties": false, - "required": [ - "actor", - "repository", - "workflow_ref", - "workflow_commit", - "run_id", - "run_attempt", - "run_url", - "environment", - "environment_protection", - "environment_approval" - ], - "properties": { - "actor": { "type": "string", "pattern": "^[A-Za-z0-9-]{1,39}$" }, - "repository": { "const": "durable-workflow/.github" }, - "workflow_ref": { "const": "durable-workflow/.github/.github/workflows/beta-authorization.yml@refs/heads/main" }, - "workflow_commit": { "$ref": "#/$defs/commit" }, - "run_id": { "type": "integer", "minimum": 1 }, - "run_attempt": { "type": "integer", "minimum": 1 }, - "run_url": { "type": "string", "pattern": "^https://github.com/durable-workflow/\\.github/actions/runs/" }, - "environment": { "const": "beta-authorization" }, - "environment_protection": { "$ref": "#/$defs/environmentProtection" }, - "environment_approval": { "$ref": "#/$defs/environmentApproval" } - } - } - }, - "$defs": { - "commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "gitRecord": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit"], - "properties": { - "tag": { "type": "string", "minLength": 1 }, - "commit": { "$ref": "#/$defs/commit" } - } - }, - "digestRecord": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "sha256"], - "properties": { - "tag": { "type": "string", "minLength": 1 }, - "commit": { "$ref": "#/$defs/commit" }, - "sha256": { "$ref": "#/$defs/sha256" } - } - }, - "user": { - "type": "object", - "additionalProperties": false, - "required": ["login", "id", "node_id", "url", "html_url"], - "properties": { - "login": { "type": "string", "pattern": "^[A-Za-z0-9-]{1,39}$" }, - "id": { "type": "integer", "minimum": 1 }, - "node_id": { "type": "string", "minLength": 1 }, - "url": { "type": "string", "pattern": "^https://api.github.com/users/" }, - "html_url": { "type": "string", "pattern": "^https://github.com/" } - } - }, - "environmentProtection": { - "type": "object", - "additionalProperties": false, - "required": [ - "custom_branch_policies", - "deployment_branch_policy", - "environment_id", - "environment_url", - "prevent_self_review", - "required_reviewer_rule_ids", - "required_reviewer_user_ids" - ], - "properties": { - "custom_branch_policies": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["id", "name"], - "properties": { - "id": { "type": "integer", "minimum": 1 }, - "name": { "const": "main" } - } - } - }, - "deployment_branch_policy": { - "const": { "custom_branch_policies": true, "protected_branches": false } - }, - "environment_id": { "type": "integer", "minimum": 1 }, - "environment_url": { - "const": "https://github.com/durable-workflow/.github/deployments/activity_log?environments_filter=beta-authorization" - }, - "prevent_self_review": { "const": false }, - "required_reviewer_rule_ids": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "integer", "minimum": 1 } - }, - "required_reviewer_user_ids": { "const": [1130888] } - } - }, - "environmentApproval": { - "type": "object", - "additionalProperties": false, - "required": ["comment", "environments", "run_attempt", "run_id", "state", "user"], - "properties": { - "comment": { "type": "string" }, - "environments": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["html_url", "id", "name", "node_id", "url"], - "properties": { - "html_url": { - "const": "https://github.com/durable-workflow/.github/deployments/activity_log?environments_filter=beta-authorization" - }, - "id": { "type": "integer", "minimum": 1 }, - "name": { "const": "beta-authorization" }, - "node_id": { "type": "string", "minLength": 1 }, - "url": { - "const": "https://api.github.com/repos/durable-workflow/.github/environments/beta-authorization" - } - } - } - }, - "run_attempt": { "type": "integer", "minimum": 1 }, - "run_id": { "type": "integer", "minimum": 1 }, - "state": { "const": "approved" }, - "user": { - "allOf": [ - { "$ref": "#/$defs/user" }, - { - "type": "object", - "properties": { - "id": { "const": 1130888 } - } - } - ] - } - } - } - } -} diff --git a/beta-authorization/record-schema.json b/beta-authorization/record-schema.json deleted file mode 100644 index d112f8b..0000000 --- a/beta-authorization/record-schema.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-authorization/record-schema.json", - "title": "Durable Workflow beta authorization", - "type": "object", - "additionalProperties": false, - "required": ["schema", "channel", "candidate", "components"], - "properties": { - "schema": { - "const": "durable-workflow.beta-authorization/v1" - }, - "channel": { - "const": "beta" - }, - "candidate": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9._-]{0,55}$" - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": { "$ref": "#/$defs/component" }, - "waterline": { "$ref": "#/$defs/component" }, - "server": { "$ref": "#/$defs/component" }, - "cli": { "$ref": "#/$defs/component" }, - "sdk-php": { "$ref": "#/$defs/component" }, - "sdk-python": { "$ref": "#/$defs/component" }, - "sdk-rust": { "$ref": "#/$defs/component" } - } - } - }, - "$defs": { - "component": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?$" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - } - } -} diff --git a/beta-authorization/request-schema.json b/beta-authorization/request-schema.json deleted file mode 100644 index d1bd369..0000000 --- a/beta-authorization/request-schema.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-authorization/request-schema.json", - "title": "Durable Workflow beta authorization request", - "type": "object", - "additionalProperties": false, - "required": ["schema", "authorization", "evidence"], - "properties": { - "schema": { - "const": "durable-workflow.beta-authorization-request/v1" - }, - "authorization": { - "$ref": "record-schema.json" - }, - "evidence": { - "type": "object", - "additionalProperties": false, - "required": ["candidate", "conformance", "continuity", "decision"], - "properties": { - "candidate": { - "$ref": "#/$defs/gitRecord" - }, - "conformance": { - "allOf": [ - { "$ref": "#/$defs/gitRecord" }, - { - "type": "object", - "properties": { - "tag": { - "type": "string", - "pattern": "^beta-conformance/[a-z0-9][a-z0-9._-]{0,62}/[1-9][0-9]*\\.[1-9][0-9]*$" - } - } - } - ] - }, - "continuity": { - "type": "object", - "additionalProperties": false, - "required": ["complete", "no_op"], - "properties": { - "complete": { "$ref": "#/$defs/gitRecord" }, - "no_op": { "$ref": "#/$defs/gitRecord" } - } - }, - "decision": { - "type": "object", - "additionalProperties": false, - "required": ["issue", "comment"], - "properties": { - "issue": { "const": 3 }, - "comment": { "type": "integer", "minimum": 1 } - } - } - } - } - }, - "$defs": { - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "gitRecord": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit"], - "properties": { - "tag": { "type": "string", "minLength": 1, "maxLength": 180 }, - "commit": { "$ref": "#/$defs/commit" } - } - } - } -} diff --git a/beta-conformance/README.md b/beta-conformance/README.md deleted file mode 100644 index d38ce63..0000000 --- a/beta-conformance/README.md +++ /dev/null @@ -1,134 +0,0 @@ -# Beta conformance - -The `Beta conformance` workflow runs the portable beta experiment set from a -clean GitHub-hosted runner. Its only product input is a canonical manifest that -already exists at the immutable Git tag `beta-candidate/`. -`prepare` rejects a different tuple, invalid verification, an abbreviated -source identity, a distribution without recorded SHA-256 identities, or a -server image without the recorded OCI manifest digest. The plan binds the -digest of the complete candidate verification document as well as normalized -digests for every package, crate, image manifest, and CLI release asset. The -contract also declares the MySQL and Redis image selectors needed by the -standalone runtime. Waterline contributes two distribution identities under -that one component: `waterline` for its embedded Composer package and -`waterline-service` for its service OCI image. `prepare` resolves each selector once and records its OCI -manifest digest in the immutable execution plan. The first workflow attempt -retains that plan under the run identity. A full rerun restores and validates -the retained plan instead of resolving the selectors again; if the plan is not -available or does not match the candidate, contract, and runner revision, the -workflow stops before creating experiment jobs. - -The experiment contract is [`contract.json`](contract.json). It selects replay, -polyglot, worker-heartbeat, and signals/query runners shipped inside the exact -published server image. Those runners install the candidate's PHP SDK from -Packagist, Python SDK from PyPI, Rust SDK from crates.io, CLI release assets, -and Composer packages by exact version. A runner bound to the exact control- -plane revision launches the digest-pinned Waterline service image, confirms -that it contains the published candidate PHP SDK, and exercises its remote -workflow list against the digest-pinned standalone server. Product checkouts -and mutable package selectors are not inputs to this workflow. - -Runner entries declare the distributions they consume and any runtime they need -from the candidate. Component version evidence remains keyed by the seven -product components, while executed identity evidence distinguishes all eight -distributions. The signals/query matrix therefore includes the Waterline -service identity it executes in its assignment while reporting that -distribution's version under the `waterline` component. The union of the runner -distribution sets must equal the experiment's complete required set. The direct -PHP SDK runner declares a standalone server dependency, so the portable wrapper -bootstraps the digest-pinned candidate image and starts its HTTP, queue-worker, -and scheduler processes with digest-pinned MySQL and Redis containers on -isolated Docker state. Matrix jobs never launch either dependency through its -mutable selector. The wrapper waits for the -published readiness endpoint before injecting the loopback URL, namespace, and -ephemeral token into that runner; it removes the containers and database volume -after every attempt. - -## Independent execution - -Dispatch `.github/workflows/beta-conformance.yml` with the complete canonical -candidate JSON. The optional `injected_failure_experiment` proves that a -same-version distribution digest mismatch is recorded, retained, and left red. -The optional `injected_canary_failure_experiment` selects a deterministic -failure containing a fixed, synthetic credential-shaped canary. Its retained -diagnostic contains `[REDACTED]`, preserves the digest of the raw output, and -does not retain the canary itself. Neither injection is passed through the -infrastructure retry classifier. - -The same entry point can be exercised on any clean runner with Git, Python, -and Docker after checking out this repository at a full commit and fetching -the immutable candidate tags: - -```text -python scripts/beta_conformance.py prepare candidate.json plan.json \ - --contract beta-conformance/contract.json \ - --runner-revision -python scripts/beta_conformance.py extract plan.json published-server extraction.json -python scripts/beta_conformance.py run plan.json replay published-server result \ - --contract beta-conformance/contract.json -``` - -`extract` pulls `docker.io/durableworkflow/server@sha256:` from the -candidate verification and copies the conformance orchestration and fixtures -from that container. It never falls back to a server checkout. - -## Evidence and failure behavior - -Every experiment result conforms to -[`result-schema.json`](result-schema.json) and repeats these bindings: - -- the candidate name, manifest digest, immutable Git record, and exact - seven-component version/commit tuple and all eight required distributions; -- the public source commit for every artifact; -- the candidate verification-document digest and expected distribution digests; -- bounded native evidence identifying the package, crate, image manifest, and - release-asset bytes actually executed by the published runner; -- the control-plane runner revision and contract digest; -- the exact server OCI digest used to obtain the product-owned runner; -- the declared MySQL and Redis selectors and the exact OCI manifest digests - launched for the standalone runtime; -- the owning product contract, outcome, retry record, failure fingerprint, - and bounded diagnostic tails and findings. - -Native results at or below the 4 MiB portable evidence limit retain their exact -size and complete SHA-256 identity. An oversized result is never read or hashed -in full: the wrapper retains its observed size and the SHA-256 identity of at -most the first 64 KiB, with the hashed byte count recorded separately. - -The wrapper recognizes only a small allowlist of registry and connection -transients. Those failures may run twice. A native non-passing result, timeout, -missing published runner, or injected distribution identity mismatch is never -retried. -Detached registry snapshots are not accepted as execution evidence. Each runner -must report identities derived from its own executed downloads for exactly the -distributions assigned to that runner. The experiment-wide check then requires -the complete distribution set declared by the experiment and matches every -reported identity to the immutable candidate record. Registry-native version -text is accepted only when it represents the candidate release and the -executed artifact digest matches the immutable plan; retained evidence keeps -the exact version text reported by the runner. Missing native evidence is a -non-retryable infrastructure failure; a different release or digest is a -non-retryable product failure under the experiment's owning contract. -Matching version strings cannot satisfy the identity check. A passing retained -suite covers the complete eight-distribution set, including both Waterline modes. -Experiments run in separate GitHub matrix jobs, have explicit deadlines, use -unique scratch and Docker state, and prune Docker resources on every exit path. - -The execution workflow keeps `contents: read` for every job. After its matrix -finishes, even when one cell is red, the separate `Beta conformance retention` -workflow runs from the trusted default branch with only `actions: read` and -`contents: write`. It validates that the source is a completed, same-repository -`Beta conformance` run on `main`, binds the execution plan to that run's exact -commit, and aggregates its retained JSON artifacts from the trusted default- -branch retention controller without executing artifact content. -The retention workflow can also be dispatched with an existing run ID and -attempt, allowing a completed matrix to be recovered without running it again. -Release creation tolerates a bounded transient API denial; a later recovery -attempt still consumes the same source run and attempt and produces the same -evidence tag. - -Retention creates a GitHub Release tagged -`beta-conformance//.`. Its canonical suite and -experiment JSON assets are durable, immutable-by-comparison mirrors. They can -be queried later through the GitHub Releases API, independently of the Actions -job log or artifact-retention window. diff --git a/beta-conformance/contract-schema.json b/beta-conformance/contract-schema.json deleted file mode 100644 index 33ee24b..0000000 --- a/beta-conformance/contract-schema.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-conformance/contract-schema.json", - "title": "Durable Workflow beta conformance contract", - "type": "object", - "additionalProperties": false, - "required": ["$schema", "schema", "runtime_dependencies", "experiments"], - "properties": { - "$schema": { - "const": "./contract-schema.json" - }, - "schema": { - "const": "durable-workflow.beta-conformance.contract/v2" - }, - "runtime_dependencies": { - "type": "object", - "additionalProperties": false, - "required": ["mysql", "redis"], - "properties": { - "mysql": { "const": "docker.io/library/mysql:8.0" }, - "redis": { "const": "docker.io/library/redis:7-alpine" } - } - }, - "experiments": { - "type": "object", - "additionalProperties": false, - "required": ["replay", "polyglot", "heartbeats", "signals-queries"], - "properties": { - "replay": { "$ref": "#/$defs/experiment" }, - "polyglot": { "$ref": "#/$defs/experiment" }, - "heartbeats": { "$ref": "#/$defs/experiment" }, - "signals-queries": { "$ref": "#/$defs/experiment" } - } - } - }, - "$defs": { - "experiment": { - "type": "object", - "additionalProperties": false, - "required": ["owning_contract", "required_clients", "required_distributions", "runners", "timeout_seconds"], - "properties": { - "owning_contract": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,62}$" }, - "required_clients": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "enum": ["sdk-php", "sdk-python", "sdk-rust"] } - }, - "required_distributions": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": ["workflow", "waterline", "waterline-service", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"] - } - }, - "runners": { - "type": "array", - "minItems": 1, - "maxItems": 3, - "items": { "$ref": "#/$defs/runner" } - }, - "timeout_seconds": { "type": "integer", "minimum": 60, "maximum": 5400 } - } - }, - "runner": { - "type": "object", - "additionalProperties": false, - "required": ["id", "source", "path", "result", "required_distributions"], - "properties": { - "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,62}$" }, - "source": { "enum": ["server-image", "control-plane"] }, - "path": { "type": "string", "pattern": "^(?:scripts/conformance/[a-z0-9-]+\\.(?:sh|mjs|py)|scripts/waterline_service_conformance\\.py)$" }, - "result": { "type": "string", "pattern": "^[a-z0-9][a-z0-9.-]+\\.json$" }, - "required_distributions": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": ["workflow", "waterline", "waterline-service", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"] - } - }, - "result_schema": { "type": "string", "pattern": "^[a-z0-9][a-z0-9.-]{0,126}$" }, - "required_result_fields": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,126}$" } - }, - "required_scenarios": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,126}$" } - }, - "runtime": { "$ref": "#/$defs/runtime" } - } - }, - "runtime": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "database_backend", - "cache_backend", - "queue_backend", - "network_scope", - "namespace_environment", - "server_url_environment", - "token_environment" - ], - "properties": { - "kind": { "const": "standalone-server" }, - "database_backend": { "const": "mysql" }, - "cache_backend": { "const": "redis" }, - "queue_backend": { "const": "redis" }, - "network_scope": { "const": "private" }, - "namespace_environment": { "type": "string", "pattern": "^DW_[A-Z0-9_]{1,95}$" }, - "server_url_environment": { "type": "string", "pattern": "^DW_[A-Z0-9_]{1,95}$" }, - "token_environment": { "type": "string", "pattern": "^DW_[A-Z0-9_]{1,95}$" } - } - } - } -} diff --git a/beta-conformance/contract.json b/beta-conformance/contract.json deleted file mode 100644 index be7b5cf..0000000 --- a/beta-conformance/contract.json +++ /dev/null @@ -1,266 +0,0 @@ -{ - "$schema": "./contract-schema.json", - "runtime_dependencies": { - "mysql": "docker.io/library/mysql:8.0", - "redis": "docker.io/library/redis:7-alpine" - }, - "experiments": { - "heartbeats": { - "owning_contract": "worker-heartbeat-observability", - "required_clients": [ - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "required_distributions": [ - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "runners": [ - { - "id": "php", - "source": "server-image", - "path": "scripts/conformance/heartbeats-published-artifacts.sh", - "result": "php-sdk-heartbeat-loop-evidence.json", - "required_distributions": [ - "server", - "cli", - "sdk-php" - ] - }, - { - "id": "python", - "source": "server-image", - "path": "scripts/conformance/heartbeats-python-published-artifacts.sh", - "result": "python-sdk-heartbeat-loop-evidence.json", - "required_distributions": [ - "server", - "cli", - "sdk-python" - ] - }, - { - "id": "rust", - "source": "server-image", - "path": "scripts/conformance/heartbeats-rust-published-artifacts.sh", - "result": "rust-sdk-heartbeat-loop-evidence.json", - "required_distributions": [ - "server", - "cli", - "sdk-rust" - ] - } - ], - "timeout_seconds": 3600 - }, - "polyglot": { - "owning_contract": "cross-language-worker-protocol", - "required_clients": [ - "sdk-php", - "sdk-python" - ], - "required_distributions": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python" - ], - "runners": [ - { - "id": "python-parity", - "source": "server-image", - "path": "scripts/conformance/python-published-artifacts.sh", - "result": "python-conformance-result.json", - "required_distributions": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-python" - ] - }, - { - "id": "php-sdk", - "source": "server-image", - "path": "scripts/conformance/php-sdk-published-artifacts.sh", - "result": "php-sdk-conformance-result.json", - "required_distributions": [ - "sdk-php" - ], - "runtime": { - "cache_backend": "redis", - "database_backend": "mysql", - "kind": "standalone-server", - "namespace_environment": "DW_PHP_SDK_CONFORMANCE_NAMESPACE", - "network_scope": "private", - "queue_backend": "redis", - "server_url_environment": "DW_PHP_SDK_CONFORMANCE_SERVER_URL", - "token_environment": "DW_PHP_SDK_CONFORMANCE_TOKEN" - } - }, - { - "id": "activities-cross-language", - "source": "server-image", - "path": "scripts/conformance/activities-published-artifacts.sh", - "result": "activities-result.json", - "required_distributions": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python" - ] - } - ], - "timeout_seconds": 3600 - }, - "replay": { - "owning_contract": "deterministic-replay", - "required_clients": [ - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "required_distributions": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "runners": [ - { - "id": "replay-matrix", - "source": "server-image", - "path": "scripts/conformance/replay-published-artifacts.sh", - "result": "replay-conformance-result.json", - "required_distributions": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ] - } - ], - "timeout_seconds": 3600 - }, - "signals-queries": { - "owning_contract": "signals-and-queries", - "required_clients": [ - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "required_distributions": [ - "workflow", - "waterline", - "waterline-service", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "runners": [ - { - "id": "signals-queries-matrix", - "source": "server-image", - "path": "scripts/conformance/signals-queries-published-artifacts.sh", - "result": "signals-queries-result.json", - "required_distributions": [ - "workflow", - "waterline", - "waterline-service", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "result_schema": "durable-workflow.v2.signal-query-runtime.result", - "required_result_fields": [ - "schema", - "started_at", - "finished_at", - "outcome", - "runner_blocked", - "artifactVersions", - "executed_distribution_identities", - "runtime_matrix", - "scenario_results", - "findings", - "finding_links" - ], - "required_scenarios": [ - "published_artifact_install_only", - "python_worker_cli_and_sdk_baseline", - "php_worker_cli_and_sdk_baseline", - "python_worker_php_facing_and_cli_clients", - "php_worker_python_and_cli_clients", - "rust_worker_rust_php_python_clients", - "python_worker_rust_client", - "php_worker_rust_client", - "rust_query_error_and_immutability", - "ordered_signal_delivery", - "dedup_contract_observation", - "signal_during_replay", - "query_during_replay", - "rust_replayed_instance_state_query_after_cold_restart", - "completed_run_signal_and_query", - "unknown_signal_and_query_errors", - "malformed_signal_and_query_payloads", - "waterline_operator_visibility", - "waterline_service_operator_visibility" - ] - }, - { - "id": "waterline-service", - "source": "control-plane", - "path": "scripts/waterline_service_conformance.py", - "result": "waterline-service-conformance-result.json", - "required_distributions": [ - "waterline-service" - ], - "result_schema": "durable-workflow.beta-conformance.waterline-service.v1", - "required_result_fields": [ - "schema", - "started_at", - "finished_at", - "outcome", - "runner_blocked", - "artifact_versions", - "executed_distribution_identities", - "scenario_results", - "findings" - ], - "required_scenarios": [ - "service_image_php_sdk_standalone" - ], - "runtime": { - "cache_backend": "redis", - "database_backend": "mysql", - "kind": "standalone-server", - "namespace_environment": "DW_WATERLINE_SERVICE_NAMESPACE", - "network_scope": "private", - "queue_backend": "redis", - "server_url_environment": "DW_WATERLINE_SERVICE_SERVER_URL", - "token_environment": "DW_WATERLINE_SERVICE_TOKEN" - } - } - ], - "timeout_seconds": 5400 - } - }, - "schema": "durable-workflow.beta-conformance.contract/v2" -} diff --git a/beta-conformance/plan-schema.json b/beta-conformance/plan-schema.json deleted file mode 100644 index 3ca4378..0000000 --- a/beta-conformance/plan-schema.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-conformance/plan-schema.json", - "title": "Durable Workflow beta conformance execution plan", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "candidate", - "artifact_tuple", - "source_identities", - "distribution_identities", - "runtime_dependencies", - "runner", - "server_runner", - "waterline_service_runner", - "experiments" - ], - "properties": { - "schema": { "const": "durable-workflow.beta-conformance.plan/v2" }, - "candidate": { "$ref": "#/$defs/candidate" }, - "artifact_tuple": { "$ref": "#/$defs/components" }, - "source_identities": { "$ref": "#/$defs/sourceIdentities" }, - "distribution_identities": { "$ref": "#/$defs/distributionIdentities" }, - "runtime_dependencies": { "$ref": "#/$defs/runtimeDependencies" }, - "runner": { "$ref": "#/$defs/runner" }, - "server_runner": { "$ref": "#/$defs/serverRunner" }, - "waterline_service_runner": { "$ref": "#/$defs/waterlineServiceRunner" }, - "experiments": { - "type": "array", - "const": ["heartbeats", "polyglot", "replay", "signals-queries"] - } - }, - "$defs": { - "identity": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?$" - }, - "commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" } - } - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"], - "properties": { - "workflow": { "$ref": "#/$defs/identity" }, - "waterline": { "$ref": "#/$defs/identity" }, - "server": { "$ref": "#/$defs/identity" }, - "cli": { "$ref": "#/$defs/identity" }, - "sdk-php": { "$ref": "#/$defs/identity" }, - "sdk-python": { "$ref": "#/$defs/identity" }, - "sdk-rust": { "$ref": "#/$defs/identity" } - } - }, - "sourceIdentities": { - "type": "object", - "additionalProperties": false, - "required": ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"], - "properties": { - "workflow": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "waterline": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "server": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "cli": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "sdk-php": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "sdk-python": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "sdk-rust": { "type": "string", "pattern": "^[0-9a-f]{40}$" } - } - }, - "candidate": { - "type": "object", - "additionalProperties": false, - "required": ["name", "manifest_sha256", "verification_sha256", "record_ref", "record_commit"], - "properties": { - "name": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,62}$" }, - "manifest_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "verification_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "record_ref": { "type": "string", "pattern": "^beta-candidate/" }, - "record_commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" } - } - }, - "runner": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "revision", "contract_sha256"], - "properties": { - "repository": { "const": "durable-workflow/.github" }, - "revision": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, - "contract_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" } - } - }, - "serverRunner": { - "type": "object", - "additionalProperties": false, - "required": ["image", "manifest_digest", "source_commit"], - "properties": { - "image": { "type": "string", "pattern": "^docker\\.io/durableworkflow/server@sha256:[0-9a-f]{64}$" }, - "manifest_digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }, - "source_commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" } - } - }, - "waterlineServiceRunner": { - "type": "object", - "additionalProperties": false, - "required": ["image", "manifest_digest", "source_commit"], - "properties": { - "image": { "type": "string", "pattern": "^docker\\.io/durableworkflow/waterline@sha256:[0-9a-f]{64}$" }, - "manifest_digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }, - "source_commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" } - } - }, - "runtimeDependencies": { - "type": "object", - "additionalProperties": false, - "required": ["mysql", "redis"], - "properties": { - "mysql": { "$ref": "#/$defs/mysqlRuntimeDependency" }, - "redis": { "$ref": "#/$defs/redisRuntimeDependency" } - } - }, - "mysqlRuntimeDependency": { - "type": "object", - "additionalProperties": false, - "required": ["selector", "image", "manifest_digest"], - "properties": { - "selector": { "const": "docker.io/library/mysql:8.0" }, - "image": { "type": "string", "pattern": "^docker\\.io/library/mysql@sha256:[0-9a-f]{64}$" }, - "manifest_digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" } - } - }, - "redisRuntimeDependency": { - "type": "object", - "additionalProperties": false, - "required": ["selector", "image", "manifest_digest"], - "properties": { - "selector": { "const": "docker.io/library/redis:7-alpine" }, - "image": { "type": "string", "pattern": "^docker\\.io/library/redis@sha256:[0-9a-f]{64}$" }, - "manifest_digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" } - } - }, - "distributionArtifact": { - "type": "object", - "additionalProperties": false, - "required": ["name", "sha256"], - "properties": { - "name": { "type": "string", "minLength": 1, "maxLength": 256 }, - "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" } - } - }, - "distributionIdentity": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "locator", "artifacts"], - "properties": { - "kind": { "enum": ["composer", "github-release", "pypi", "crates.io", "oci"] }, - "locator": { "type": "string", "minLength": 1, "maxLength": 256 }, - "artifacts": { - "type": "array", - "minItems": 1, - "maxItems": 128, - "items": { "$ref": "#/$defs/distributionArtifact" } - } - } - }, - "distributionIdentities": { - "type": "object", - "additionalProperties": false, - "required": ["workflow", "waterline", "waterline-service", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"], - "properties": { - "workflow": { "$ref": "#/$defs/distributionIdentity" }, - "waterline": { "$ref": "#/$defs/distributionIdentity" }, - "waterline-service": { "$ref": "#/$defs/distributionIdentity" }, - "server": { "$ref": "#/$defs/distributionIdentity" }, - "cli": { "$ref": "#/$defs/distributionIdentity" }, - "sdk-php": { "$ref": "#/$defs/distributionIdentity" }, - "sdk-python": { "$ref": "#/$defs/distributionIdentity" }, - "sdk-rust": { "$ref": "#/$defs/distributionIdentity" } - } - }, - "partialDistributionIdentities": { - "type": "object", - "additionalProperties": false, - "properties": { - "workflow": { "$ref": "#/$defs/distributionIdentity" }, - "waterline": { "$ref": "#/$defs/distributionIdentity" }, - "waterline-service": { "$ref": "#/$defs/distributionIdentity" }, - "server": { "$ref": "#/$defs/distributionIdentity" }, - "cli": { "$ref": "#/$defs/distributionIdentity" }, - "sdk-php": { "$ref": "#/$defs/distributionIdentity" }, - "sdk-python": { "$ref": "#/$defs/distributionIdentity" }, - "sdk-rust": { "$ref": "#/$defs/distributionIdentity" } - } - } - } -} diff --git a/beta-conformance/result-schema.json b/beta-conformance/result-schema.json deleted file mode 100644 index 16b4ff3..0000000 --- a/beta-conformance/result-schema.json +++ /dev/null @@ -1,271 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-conformance/result-schema.json", - "title": "Durable Workflow beta conformance experiment result", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "experiment", - "candidate", - "artifact_tuple", - "source_identities", - "distribution_identities", - "runtime_dependencies", - "runner", - "server_runner", - "waterline_service_runner", - "owning_contract", - "required_clients", - "required_distributions", - "source_policy", - "started_at", - "finished_at", - "outcome", - "classification", - "failure_fingerprint", - "retry", - "diagnostics" - ], - "properties": { - "schema": { "const": "durable-workflow.beta-conformance.experiment-result/v2" }, - "experiment": { "enum": ["replay", "polyglot", "heartbeats", "signals-queries"] }, - "candidate": { "$ref": "plan-schema.json#/$defs/candidate" }, - "artifact_tuple": { "$ref": "plan-schema.json#/$defs/components" }, - "source_identities": { "$ref": "plan-schema.json#/$defs/sourceIdentities" }, - "distribution_identities": { "$ref": "plan-schema.json#/$defs/distributionIdentities" }, - "runtime_dependencies": { "$ref": "plan-schema.json#/$defs/runtimeDependencies" }, - "runner": { "$ref": "plan-schema.json#/$defs/runner" }, - "server_runner": { "$ref": "plan-schema.json#/$defs/serverRunner" }, - "waterline_service_runner": { "$ref": "plan-schema.json#/$defs/waterlineServiceRunner" }, - "owning_contract": { "type": "string" }, - "required_clients": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "enum": ["sdk-php", "sdk-python", "sdk-rust"] } - }, - "required_distributions": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": ["workflow", "waterline", "waterline-service", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"] - } - }, - "source_policy": { "$ref": "#/$defs/sourcePolicy" }, - "started_at": { "type": "string", "format": "date-time" }, - "finished_at": { "type": "string", "format": "date-time" }, - "outcome": { "enum": ["pass", "fail"] }, - "classification": { "enum": ["passed", "product_failure", "infrastructure_failure"] }, - "failure_fingerprint": { "type": ["string", "null"], "pattern": "^[0-9a-f]{64}$" }, - "retry": { - "type": "object", - "additionalProperties": false, - "required": ["attempts", "maximum_infrastructure_attempts", "semantic_failures_retryable"], - "properties": { - "attempts": { "type": "integer", "minimum": 1, "maximum": 2 }, - "maximum_infrastructure_attempts": { "type": "integer", "minimum": 1, "maximum": 2 }, - "semantic_failures_retryable": { "const": false } - } - }, - "diagnostics": { - "type": "array", - "minItems": 1, - "maxItems": 7, - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "runner", - "attempt", - "exit_code", - "timed_out", - "native_outcome", - "runner_blocked", - "stdout_tail", - "stdout_sha256", - "stderr_tail", - "stderr_sha256", - "native_result_size_bytes", - "native_result_sha256", - "native_result_prefix_sha256", - "native_result_prefix_bytes", - "native_summary", - "findings" - ], - "properties": { - "runner": { "type": "string", "maxLength": 63 }, - "attempt": { "type": "integer", "minimum": 1, "maximum": 2 }, - "exit_code": { "type": "integer" }, - "timed_out": { "type": "boolean" }, - "native_outcome": { "type": ["string", "null"], "maxLength": 128 }, - "runner_blocked": { "type": "boolean" }, - "stdout_tail": { "type": "string", "maxLength": 8192 }, - "stderr_tail": { "type": "string", "maxLength": 8192 }, - "stdout_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "stderr_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "native_result_size_bytes": { "type": ["integer", "null"], "minimum": 0 }, - "native_result_sha256": { "type": ["string", "null"], "pattern": "^[0-9a-f]{64}$" }, - "native_result_prefix_sha256": { "type": ["string", "null"], "pattern": "^[0-9a-f]{64}$" }, - "native_result_prefix_bytes": { "type": ["integer", "null"], "minimum": 0, "maximum": 65536 }, - "native_summary": { - "oneOf": [ - { "type": "null" }, - { "$ref": "#/$defs/nativeSummary" } - ] - }, - "findings": { - "type": "array", - "maxItems": 20, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["type", "owning_contract", "summary"], - "properties": { - "type": { "type": "string", "maxLength": 128 }, - "owning_contract": { "type": "string", "maxLength": 128 }, - "summary": { "type": "string", "maxLength": 2048 } - } - } - } - }, - "oneOf": [ - { - "properties": { - "native_result_size_bytes": { "type": "null" }, - "native_result_sha256": { "type": "null" }, - "native_result_prefix_sha256": { "type": "null" }, - "native_result_prefix_bytes": { "type": "null" }, - "native_summary": { "type": "null" } - } - }, - { - "properties": { - "runner_blocked": { "const": true }, - "native_result_size_bytes": { "type": "integer", "minimum": 0 }, - "native_result_sha256": { "type": "null" }, - "native_result_prefix_sha256": { "type": "null" }, - "native_result_prefix_bytes": { "type": "null" }, - "native_summary": { "type": "null" } - } - }, - { - "properties": { - "native_result_size_bytes": { "type": "integer", "minimum": 0, "maximum": 4194304 }, - "native_result_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "native_result_prefix_sha256": { "type": "null" }, - "native_result_prefix_bytes": { "type": "null" } - } - }, - { - "properties": { - "native_result_size_bytes": { "type": "integer", "minimum": 4194305 }, - "native_result_sha256": { "type": "null" }, - "native_result_prefix_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "native_result_prefix_bytes": { "type": "integer", "minimum": 1, "maximum": 65536 }, - "native_summary": { "type": "null" } - } - } - ] - } - } - }, - "$defs": { - "sourcePolicy": { - "type": "object", - "additionalProperties": false, - "required": ["product_artifacts", "orchestration_source", "local_product_source_checkout_used"], - "properties": { - "product_artifacts": { "const": "published_only" }, - "orchestration_source": { "const": "bound_control_plane_and_exact_candidate_images" }, - "local_product_source_checkout_used": { "const": false } - } - }, - "nativeSummary": { - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "artifact_versions", - "executed_distribution_identities", - "scenario_statuses", - "failure_projection", - "local_product_source_checkout_used" - ], - "properties": { - "schema": { "type": ["string", "null"], "maxLength": 256 }, - "local_product_source_checkout_used": { "type": ["boolean", "null"] }, - "artifact_versions": { - "type": "object", - "additionalProperties": false, - "maxProperties": 8, - "properties": { - "workflow": { "type": "string", "maxLength": 128 }, - "waterline": { "type": "string", "maxLength": 128 }, - "waterline-service": { "type": "string", "maxLength": 128 }, - "server": { "type": "string", "maxLength": 128 }, - "cli": { "type": "string", "maxLength": 128 }, - "sdk-php": { "type": "string", "maxLength": 128 }, - "sdk-python": { "type": "string", "maxLength": 128 }, - "sdk-rust": { "type": "string", "maxLength": 128 } - } - }, - "executed_distribution_identities": { - "$ref": "plan-schema.json#/$defs/partialDistributionIdentities" - }, - "scenario_statuses": { - "type": "array", - "maxItems": 128, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["id", "status"], - "properties": { - "id": { "type": "string", "maxLength": 128 }, - "status": { "type": "string", "maxLength": 64 } - } - } - }, - "failure_projection": { - "type": "object", - "additionalProperties": false, - "required": ["max_bytes", "component_max_bytes", "truncated", "scenarios"], - "properties": { - "max_bytes": { "const": 24576 }, - "component_max_bytes": { "const": 6144 }, - "truncated": { "type": "boolean" }, - "scenarios": { - "type": "array", - "maxItems": 128, - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "status", - "failure_stage", - "failure_classification", - "failure_owner", - "worker_evidence", - "server_evidence", - "linked_findings" - ], - "properties": { - "id": { "type": "string", "minLength": 1, "maxLength": 128 }, - "status": { "type": "string", "minLength": 1, "maxLength": 64 }, - "failure_stage": { "type": ["string", "null"], "maxLength": 128 }, - "failure_classification": { "type": ["string", "null"], "maxLength": 128 }, - "failure_owner": { "type": ["string", "null"], "maxLength": 128 }, - "worker_evidence": {}, - "server_evidence": {}, - "linked_findings": {} - } - } - } - } - } - } - } - } -} diff --git a/beta-conformance/suite-result-schema.json b/beta-conformance/suite-result-schema.json deleted file mode 100644 index e38506c..0000000 --- a/beta-conformance/suite-result-schema.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-conformance/suite-result-schema.json", - "title": "Durable Workflow beta conformance suite result", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "candidate", - "artifact_tuple", - "source_identities", - "distribution_identities", - "executed_distribution_identities", - "runtime_dependencies", - "runner", - "server_runner", - "waterline_service_runner", - "source_policy", - "github_run", - "generated_at", - "outcome", - "experiments" - ], - "properties": { - "schema": { "const": "durable-workflow.beta-conformance.suite-result/v2" }, - "candidate": { "$ref": "plan-schema.json#/$defs/candidate" }, - "artifact_tuple": { "$ref": "plan-schema.json#/$defs/components" }, - "source_identities": { "$ref": "plan-schema.json#/$defs/sourceIdentities" }, - "distribution_identities": { "$ref": "plan-schema.json#/$defs/distributionIdentities" }, - "executed_distribution_identities": { "$ref": "plan-schema.json#/$defs/partialDistributionIdentities" }, - "runtime_dependencies": { "$ref": "plan-schema.json#/$defs/runtimeDependencies" }, - "runner": { "$ref": "plan-schema.json#/$defs/runner" }, - "server_runner": { "$ref": "plan-schema.json#/$defs/serverRunner" }, - "waterline_service_runner": { "$ref": "plan-schema.json#/$defs/waterlineServiceRunner" }, - "source_policy": { "$ref": "result-schema.json#/$defs/sourcePolicy" }, - "github_run": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "run_id", "run_attempt", "evidence_tag"], - "properties": { - "repository": { "const": "durable-workflow/.github" }, - "run_id": { "type": "integer", "minimum": 1 }, - "run_attempt": { "type": "integer", "minimum": 1 }, - "evidence_tag": { "type": "string", "pattern": "^beta-conformance/" } - } - }, - "generated_at": { "type": "string", "format": "date-time" }, - "outcome": { "enum": ["pass", "fail"] }, - "experiments": { - "type": "object", - "additionalProperties": false, - "required": ["replay", "polyglot", "heartbeats", "signals-queries"], - "properties": { - "replay": { "$ref": "#/$defs/experiment" }, - "polyglot": { "$ref": "#/$defs/experiment" }, - "heartbeats": { "$ref": "#/$defs/experiment" }, - "signals-queries": { "$ref": "#/$defs/experiment" } - } - } - }, - "$defs": { - "experiment": { - "type": "object", - "additionalProperties": false, - "required": [ - "outcome", - "classification", - "owning_contract", - "required_clients", - "required_distributions", - "result_sha256", - "failure_fingerprint" - ], - "properties": { - "outcome": { "enum": ["pass", "fail"] }, - "classification": { "enum": ["passed", "product_failure", "infrastructure_failure"] }, - "owning_contract": { "type": "string" }, - "required_clients": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "enum": ["sdk-php", "sdk-python", "sdk-rust"] } - }, - "required_distributions": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": ["workflow", "waterline", "waterline-service", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"] - } - }, - "result_sha256": { "type": ["string", "null"], "pattern": "^[0-9a-f]{64}$" }, - "failure_fingerprint": { "type": ["string", "null"], "pattern": "^[0-9a-f]{64}$" } - } - } - } -} diff --git a/beta-continuity/README.md b/beta-continuity/README.md deleted file mode 100644 index b220396..0000000 --- a/beta-continuity/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Workspace-unavailable beta continuity - -The `Beta continuity` workflow is the GitHub-owned driver for the final remote -continuity drill. It reads the authoritative public issue and qualified target -branches, allocates seven component versions from public release tags, and -stores that selection in an immutable Git tag before routing any source-version -blocker. A fresh drill never inherits version allocations from blockers created -by an earlier drill. When a selected version publishes while its blocker is -being repaired, later runs for the same drill bind the release plan to that -tag's source commit instead of allocating a successor. - -Acceptance records which exact-plan components are already public and which are -still pending. The controller deliberately ends one run only after a component -that was pending at acceptance becomes public through its repository-owned -recovery workflow for the exact immutable plan. Baseline artifacts remain valid -members of the final tuple, but cannot trigger the interruption. A later -scheduled run reads the append-only interruption record, dispatches the -identical plan to all seven repository-owned recovery workflows, and continues -from public GitHub and registry state. Component repositories retain their own -scheduled and manual recovery entry points and their own publication -environments; this workflow coordinates those paths but does not replace them. - -Each durable phase is an immutable Git tag: - -```text -beta-continuity-selection/ -beta-continuity//accepted -beta-continuity//interrupted -beta-continuity//resumed -beta-continuity//conformance-requested -beta-continuity//complete -beta-continuity//no-op-confirmed -``` - -The selection tag contains `continuity-selection.json`. Every phase contains -`continuity-evidence.json` and the canonical -`release-plan.json`. The accepted phase also retains exact target qualification -evidence. The authoritative issue receives links to these records. Completion -requires the immutable release candidate record, seven public component -releases, and a passing clean-runner conformance Release for the same tuple. The -controller dispatches conformance only when no matching execution exists. Once -a matching run is complete, it requests the retention-only workflow with that -exact run ID and attempt; a failed publication step therefore cannot cause the -experiment matrix to run again. The repository-scoped `GITHUB_TOKEN` performs -these same-repository dispatches, while cross-repository product authority -remains confined to the protected environment. -Automatic completion-triggered and manual recovery runs are deduplicated by -that source identity. After one failed retention retry, the controller reports -terminal publication failure in its retained observation instead of looping. - -The authority issue remains open after completion until a later scheduled run -retains a successful no-op phase. At that terminal boundary, the controller -revalidates the exact plan, source releases, public verification, protected -qualification, conformance, and no-op records before changing issue state. It -then comments and closes trusted component blockers, followed by the configured -evidence work items, and only then comments and closes the parent authority. -Each closure comment links the same immutable evidence, and retries reuse the -exact existing report instead of duplicating it. - -Earlier interruptions and their routed blockers remain available as immutable -diagnostic evidence. The corrected drill uses a new selection and plan identity, -allocates from current public release tags, and links the superseded diagnostic -phase from its accepted evidence; existing tags are never rewritten. - -Planning pauses after the immutable version selection when a component cannot -accept its selected public version. The protected GitHub issue authority then -files an idempotent, repository-owned release blocker with the exact source and -version mismatch. The drill remains open until that focused dependency lands. diff --git a/beta-continuity/config.json b/beta-continuity/config.json deleted file mode 100644 index 5442138..0000000 --- a/beta-continuity/config.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "./schema.json", - "authority_issue": { - "number": 2, - "repository": "durable-workflow/.github", - "work_id": "github-only-beta-continuity-drill" - }, - "channel": "alpha", - "drill": "workspace-unavailable-beta-continuity-release-pages", - "evidence_work_items": [ - { - "number": 10, - "repository": "durable-workflow/.github", - "required_labels": [ - "authority:github", - "beta:blocker", - "completion:evidence-required", - "kind:release-blocker", - "status:ready" - ], - "work_id": "beta-continuity-post-acceptance-publication" - }, - { - "number": 11, - "repository": "durable-workflow/.github", - "required_labels": [ - "authority:github", - "beta:blocker", - "completion:evidence-required", - "kind:release-blocker", - "status:ready" - ], - "work_id": "continuity-converges-routed-release-blockers" - } - ], - "first_component": "workflow", - "plan_prefix": "workspace-unavailable-release-pages", - "public_issue_budget": { - "max_open_actionable": 1, - "stale_age_seconds": 604800 - }, - "required_issue_labels": [ - "authority:github", - "beta:blocker", - "completion:evidence-required", - "kind:cross-repository", - "status:ready" - ], - "superseded_interruption": { - "reason": "missing-post-acceptance-publication-trigger", - "tag": "beta-continuity/workspace-unavailable-0b191da0d140/interrupted" - } -} diff --git a/beta-continuity/schema.json b/beta-continuity/schema.json deleted file mode 100644 index 223f66f..0000000 --- a/beta-continuity/schema.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/beta-continuity/schema.json", - "title": "Durable Workflow workspace-unavailable beta continuity drill", - "type": "object", - "additionalProperties": false, - "required": [ - "$schema", - "authority_issue", - "channel", - "drill", - "evidence_work_items", - "first_component", - "plan_prefix", - "public_issue_budget", - "required_issue_labels", - "superseded_interruption" - ], - "properties": { - "$schema": { "const": "./schema.json" }, - "authority_issue": { - "type": "object", - "additionalProperties": false, - "required": ["number", "repository", "work_id"], - "properties": { - "number": { "type": "integer", "minimum": 1 }, - "repository": { "const": "durable-workflow/.github" }, - "work_id": { "const": "github-only-beta-continuity-drill" } - } - }, - "channel": { "const": "alpha" }, - "drill": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9._-]{0,79}$" - }, - "evidence_work_items": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["number", "repository", "required_labels", "work_id"], - "properties": { - "number": { "type": "integer", "minimum": 1 }, - "repository": { - "type": "string", - "pattern": "^durable-workflow/[A-Za-z0-9._-]+$" - }, - "required_labels": { - "type": "array", - "minItems": 4, - "uniqueItems": true, - "items": { "type": "string", "minLength": 1 }, - "allOf": [ - { "contains": { "const": "authority:github" } }, - { "contains": { "const": "beta:blocker" } }, - { "contains": { "const": "completion:evidence-required" } }, - { "contains": { "const": "status:ready" } } - ] - }, - "work_id": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9._-]{0,79}$" - } - } - } - }, - "first_component": { - "enum": ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"] - }, - "plan_prefix": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9-]{0,35}$" - }, - "public_issue_budget": { - "type": "object", - "additionalProperties": false, - "required": ["max_open_actionable", "stale_age_seconds"], - "properties": { - "max_open_actionable": { "const": 1 }, - "stale_age_seconds": { "const": 604800 } - } - }, - "required_issue_labels": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "string", "minLength": 1 } - }, - "superseded_interruption": { - "type": "object", - "additionalProperties": false, - "required": ["reason", "tag"], - "properties": { - "reason": { - "const": "missing-post-acceptance-publication-trigger" - }, - "tag": { - "const": "beta-continuity/workspace-unavailable-0b191da0d140/interrupted" - } - } - } - } -} diff --git a/candidates/README.md b/candidates/README.md deleted file mode 100644 index b09bf3a..0000000 --- a/candidates/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Beta candidate records - -The `Beta candidate` GitHub workflow verifies and records a coherent public -release tuple. A dispatch supplies one JSON value conforming to -[`schema.json`](schema.json). The fixed [`main.json`](main.json) tuple exercises -the same path whenever this repository's main branch changes. It is the next -candidate to qualify, not the mutable supported-train pointer; current-train -verification derives its candidate input from the current release plan. - -Every component version must be an exact release and every commit must be its -full 40-character source identity. Repository and registry locations are owned -by the verifier rather than supplied by the request: - -| Component | Public artifact surface | Source identity check | -| --- | --- | --- | -| Workflow | Packagist `durable-workflow/workflow` | Packagist source/dist reference and GitHub release tag | -| Waterline | Packagist `durable-workflow/waterline` and Docker Hub `durableworkflow/waterline` | Packagist source/dist reference, OCI manifest/platform/config-label evidence, and one GitHub release tag | -| server | Docker Hub `durableworkflow/server` | OCI config labels and GitHub release tag | -| CLI | GitHub release assets | release checksums, build-attestation source commit/ref, and GitHub release tag | -| PHP SDK | Packagist `durable-workflow/sdk` | Packagist source/dist reference and GitHub release tag | -| Python SDK | PyPI `durable-workflow` | registry digests, repository metadata, and GitHub release tag | -| Rust SDK | crates.io `durable-workflow` | registry digest, packaged VCS identity, and GitHub release tag | - -## Immutability and recovery - -The authoritative record is the Git tag `beta-candidate/`. Its -root commit contains only canonical `candidate.json` and `verification.json`. -The workflow never force-pushes that tag. A repeated request must have the same -canonical manifest; otherwise it fails before any record can change. - -A GitHub Release with the same tag mirrors both files for convenient API and -browser queries. Reruns recreate a missing release or missing asset, but reject -an existing asset whose bytes differ from the tagged authority. Actions logs -and temporary workflow artifacts are not part of the record. - -For example, after replacing the repository and candidate names as needed: - -```text -GET /repos/durable-workflow/.github/releases/tags/beta-candidate%2F -GET /repos/durable-workflow/.github/contents/candidate.json?ref=beta-candidate%2F -GET /repos/durable-workflow/.github/contents/verification.json?ref=beta-candidate%2F -``` - -The manifest contains public versions and source commits only. Registry -credentials, publishing environments, conformance results, and product secrets -are deliberately outside this format. - -Waterline remains one manifest component. Its verification result has a -required `distributions` object with `embedded` Composer and `service` OCI -records. Both records are checked against the component's one version, source -tag, and full commit; either missing record makes the candidate unverifiable. - -Candidate manifests using `durable-workflow.beta-candidate/v2` require that -dual-distribution verification contract. The verifier recognizes v1 evidence -only for the exact immutable candidate manifests recorded before the service -image became a required Waterline distribution. diff --git a/candidates/main.json b/candidates/main.json deleted file mode 100644 index 46c8321..0000000 --- a/candidates/main.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "candidate": "rc-current-2-0-20260801", - "components": { - "cli": { - "commit": "e4413464908e5d42ae13071e2c6e6c280da06a1a", - "version": "2.0.0-rc.12" - }, - "sdk-php": { - "commit": "31698e4b97fd36e56f05517a0bd56ec7e16a8c05", - "version": "2.0.0-rc.6" - }, - "sdk-python": { - "commit": "6c5ad457d98834f90799db093c8dff515bee710e", - "version": "2.0.0-rc.8" - }, - "sdk-rust": { - "commit": "527dc8581131a40f127d1d8144a3b55d87829ac8", - "version": "2.0.0-rc.7" - }, - "server": { - "commit": "0f0ce78e8d4eadb91a34a0d09f67e04f5335cdea", - "version": "2.0.0-rc.13" - }, - "waterline": { - "commit": "747ba7712a5e1c6ebee196640d76fc11f77fa67d", - "version": "2.0.0-rc.9" - }, - "workflow": { - "commit": "9416c2a4d3fe71d85f77c2465ed8337c833a79ee", - "version": "2.0.0-rc.12" - } - }, - "schema": "durable-workflow.beta-candidate/v2" -} diff --git a/candidates/schema.json b/candidates/schema.json deleted file mode 100644 index 1f4371e..0000000 --- a/candidates/schema.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/candidates/schema.json", - "title": "Durable Workflow beta candidate", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "candidate", - "components" - ], - "properties": { - "schema": { - "const": "durable-workflow.beta-candidate/v2" - }, - "candidate": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9._-]{0,62}$" - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": { "$ref": "#/$defs/component" }, - "waterline": { "$ref": "#/$defs/component" }, - "server": { "$ref": "#/$defs/component" }, - "cli": { "$ref": "#/$defs/component" }, - "sdk-php": { "$ref": "#/$defs/component" }, - "sdk-python": { "$ref": "#/$defs/component" }, - "sdk-rust": { "$ref": "#/$defs/component" } - } - } - }, - "$defs": { - "component": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?$" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - } - } -} diff --git a/candidates/verification-schema.json b/candidates/verification-schema.json deleted file mode 100644 index 5ca2816..0000000 --- a/candidates/verification-schema.json +++ /dev/null @@ -1,411 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/candidates/verification-schema.json", - "title": "Durable Workflow beta candidate verification", - "type": "object", - "additionalProperties": false, - "required": ["schema", "candidate", "manifest_sha256", "verified_at", "outcome", "components"], - "properties": { - "schema": {"const": "durable-workflow.beta-candidate-verification/v2"}, - "candidate": {"type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,62}$"}, - "manifest_sha256": {"$ref": "#/$defs/sha256"}, - "verified_at": { - "type": "string", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" - }, - "outcome": {"const": "verified"}, - "components": { - "type": "object", - "additionalProperties": false, - "required": ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"], - "properties": { - "workflow": {"$ref": "#/$defs/composerResult"}, - "waterline": {"$ref": "#/$defs/waterlineResult"}, - "server": {"$ref": "#/$defs/ociResult"}, - "cli": {"$ref": "#/$defs/githubReleaseResult"}, - "sdk-php": {"$ref": "#/$defs/composerResult"}, - "sdk-python": {"$ref": "#/$defs/pypiResult"}, - "sdk-rust": {"$ref": "#/$defs/crateResult"} - } - } - }, - "$defs": { - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, - "commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, - "ociDigest": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}, - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?$" - }, - "httpsUrl": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "download": { - "type": "object", - "additionalProperties": false, - "required": ["url", "size", "sha256"], - "properties": { - "url": {"$ref": "#/$defs/httpsUrl"}, - "size": {"type": "integer", "minimum": 1, "maximum": 9223372036854775807}, - "sha256": {"$ref": "#/$defs/sha256"} - } - }, - "source": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "tag", "tag_object", "commit", "url"], - "properties": { - "repository": {"type": "string", "pattern": "^durable-workflow/[a-z0-9-]{1,64}$"}, - "tag": {"$ref": "#/$defs/version"}, - "tag_object": {"$ref": "#/$defs/commit"}, - "commit": {"$ref": "#/$defs/commit"}, - "url": {"$ref": "#/$defs/httpsUrl"} - } - }, - "composerDistribution": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "package", "registry", "source_reference", "dist_reference", "dist"], - "properties": { - "kind": {"const": "composer"}, - "package": {"type": "string", "minLength": 1, "maxLength": 128}, - "registry": {"$ref": "#/$defs/httpsUrl"}, - "source_reference": {"$ref": "#/$defs/commit"}, - "dist_reference": {"$ref": "#/$defs/commit"}, - "dist": {"$ref": "#/$defs/download"} - } - }, - "attestationAuthority": { - "oneOf": [ - { - "type": "object", - "additionalProperties": false, - "required": ["mode", "ref", "commit"], - "properties": { - "mode": {"const": "exact-tag"}, - "ref": {"type": "string", "pattern": "^refs/tags/", "maxLength": 256}, - "commit": {"$ref": "#/$defs/commit"} - } - }, - { - "type": "object", - "additionalProperties": false, - "required": ["mode", "ref", "workflow"], - "properties": { - "mode": {"const": "qualified-main-workflow"}, - "ref": {"const": "refs/heads/main"}, - "workflow": { - "type": "string", - "pattern": "^durable-workflow/cli/\\.github/workflows/release\\.yml$" - } - } - } - ] - }, - "releaseAsset": { - "type": "object", - "additionalProperties": false, - "required": ["name", "asset_id", "url", "size", "sha256"], - "properties": { - "name": { - "enum": [ - "SHA256SUMS", - "dw-linux-aarch64", - "dw-linux-x86_64", - "dw-macos-aarch64", - "dw-windows-x86_64.exe", - "dw.phar", - "dw.rb", - "install.ps1", - "install.sh", - "verify-release.sh" - ] - }, - "asset_id": {"type": "integer", "minimum": 1, "maximum": 9223372036854775807}, - "url": {"$ref": "#/$defs/httpsUrl"}, - "size": {"type": "integer", "minimum": 1, "maximum": 9223372036854775807}, - "sha256": {"$ref": "#/$defs/sha256"} - } - }, - "githubReleaseDistribution": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "repository", - "release_id", - "release_url", - "build_attestations_verified", - "build_attestation_authority", - "package_source", - "assets" - ], - "properties": { - "kind": {"const": "github-release"}, - "repository": {"const": "durable-workflow/cli"}, - "release_id": {"type": "integer", "minimum": 1, "maximum": 9223372036854775807}, - "release_url": {"$ref": "#/$defs/httpsUrl"}, - "build_attestations_verified": {"const": true}, - "build_attestation_authority": {"$ref": "#/$defs/attestationAuthority"}, - "package_source": { - "type": "object", - "additionalProperties": false, - "required": ["commit", "embedded_phar_identity"], - "properties": { - "commit": {"$ref": "#/$defs/commit"}, - "embedded_phar_identity": { - "type": "string", - "pattern": "^dw [0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)? \\(commit [0-9a-f]{12}\\)$", - "maxLength": 512 - } - } - }, - "assets": { - "type": "array", - "minItems": 10, - "maxItems": 10, - "uniqueItems": true, - "items": {"$ref": "#/$defs/releaseAsset"} - } - } - }, - "pypiFile": { - "type": "object", - "additionalProperties": false, - "required": ["filename", "package_type", "url", "size", "sha256"], - "properties": { - "filename": {"type": "string", "minLength": 1, "maxLength": 255, "pattern": "^[^/\\\\]+$"}, - "package_type": {"enum": ["bdist_wheel", "sdist"]}, - "url": {"$ref": "#/$defs/httpsUrl"}, - "size": {"type": "integer", "minimum": 1, "maximum": 9223372036854775807}, - "sha256": {"$ref": "#/$defs/sha256"} - } - }, - "pypiDistribution": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "package", "registry", "source_identity", "files"], - "properties": { - "kind": {"const": "pypi"}, - "package": {"const": "durable-workflow"}, - "registry": {"$ref": "#/$defs/httpsUrl"}, - "source_identity": { - "type": "object", - "additionalProperties": false, - "required": ["source_archive", "source_files_compared", "wheel_files_compared", "source_commit"], - "properties": { - "source_archive": {"$ref": "#/$defs/download"}, - "source_files_compared": {"type": "integer", "minimum": 1, "maximum": 9223372036854775807}, - "wheel_files_compared": {"type": "integer", "minimum": 1, "maximum": 9223372036854775807}, - "source_commit": {"$ref": "#/$defs/commit"} - } - }, - "files": { - "type": "array", - "minItems": 2, - "maxItems": 32, - "uniqueItems": true, - "items": {"$ref": "#/$defs/pypiFile"} - } - } - }, - "crateDistribution": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "package", "registry", "archive_vcs_commit", "archive_vcs_dirty", "archive"], - "properties": { - "kind": {"const": "crates.io"}, - "package": {"const": "durable-workflow"}, - "registry": {"$ref": "#/$defs/httpsUrl"}, - "archive_vcs_commit": {"$ref": "#/$defs/commit"}, - "archive_vcs_dirty": {"const": false}, - "archive": {"$ref": "#/$defs/download"} - } - }, - "ociConfig": { - "type": "object", - "additionalProperties": false, - "required": ["digest", "labels"], - "properties": { - "digest": {"$ref": "#/$defs/ociDigest"}, - "labels": { - "type": "object", - "additionalProperties": false, - "required": ["org.opencontainers.image.revision", "dev.durable-workflow.release.tag"], - "properties": { - "org.opencontainers.image.revision": {"$ref": "#/$defs/commit"}, - "dev.durable-workflow.release.tag": {"$ref": "#/$defs/version"} - } - } - } - }, - "ociDistribution": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "image", "manifest_digest", "platforms", "configs"], - "properties": { - "kind": {"const": "oci"}, - "image": {"type": "string", "pattern": "^docker\\.io/durableworkflow/(?:server|waterline):"}, - "manifest_digest": {"$ref": "#/$defs/ociDigest"}, - "platforms": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": {"enum": ["linux/amd64", "linux/arm64"]} - }, - "configs": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": {"$ref": "#/$defs/ociConfig"} - } - } - }, - "serverOciDistribution": { - "allOf": [ - {"$ref": "#/$defs/ociDistribution"}, - { - "type": "object", - "properties": { - "image": {"type": "string", "pattern": "^docker\\.io/durableworkflow/server:"} - } - } - ] - }, - "waterlineOciDistribution": { - "allOf": [ - {"$ref": "#/$defs/ociDistribution"}, - { - "type": "object", - "properties": { - "image": {"type": "string", "pattern": "^docker\\.io/durableworkflow/waterline:"} - } - } - ] - }, - "resultBase": { - "type": "object", - "required": ["version", "commit", "source", "distribution", "outcome"], - "properties": { - "version": {"$ref": "#/$defs/version"}, - "commit": {"$ref": "#/$defs/commit"}, - "source": {"$ref": "#/$defs/source"}, - "outcome": {"const": "verified"} - } - }, - "composerResult": { - "allOf": [ - {"$ref": "#/$defs/resultBase"}, - { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit", "source", "distribution", "outcome"], - "properties": { - "version": {"$ref": "#/$defs/version"}, - "commit": {"$ref": "#/$defs/commit"}, - "source": {"$ref": "#/$defs/source"}, - "distribution": {"$ref": "#/$defs/composerDistribution"}, - "outcome": {"const": "verified"} - } - } - ] - }, - "waterlineResult": { - "allOf": [ - { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit", "source", "distributions", "outcome"], - "properties": { - "version": {"$ref": "#/$defs/version"}, - "commit": {"$ref": "#/$defs/commit"}, - "source": {"$ref": "#/$defs/source"}, - "distributions": { - "type": "object", - "additionalProperties": false, - "required": ["embedded", "service"], - "properties": { - "embedded": {"$ref": "#/$defs/composerDistribution"}, - "service": {"$ref": "#/$defs/waterlineOciDistribution"} - } - }, - "outcome": {"const": "verified"} - } - } - ] - }, - "githubReleaseResult": { - "allOf": [ - {"$ref": "#/$defs/resultBase"}, - { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit", "source", "distribution", "outcome"], - "properties": { - "version": {"$ref": "#/$defs/version"}, - "commit": {"$ref": "#/$defs/commit"}, - "source": {"$ref": "#/$defs/source"}, - "distribution": {"$ref": "#/$defs/githubReleaseDistribution"}, - "outcome": {"const": "verified"} - } - } - ] - }, - "pypiResult": { - "allOf": [ - {"$ref": "#/$defs/resultBase"}, - { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit", "source", "distribution", "outcome"], - "properties": { - "version": {"$ref": "#/$defs/version"}, - "commit": {"$ref": "#/$defs/commit"}, - "source": {"$ref": "#/$defs/source"}, - "distribution": {"$ref": "#/$defs/pypiDistribution"}, - "outcome": {"const": "verified"} - } - } - ] - }, - "crateResult": { - "allOf": [ - {"$ref": "#/$defs/resultBase"}, - { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit", "source", "distribution", "outcome"], - "properties": { - "version": {"$ref": "#/$defs/version"}, - "commit": {"$ref": "#/$defs/commit"}, - "source": {"$ref": "#/$defs/source"}, - "distribution": {"$ref": "#/$defs/crateDistribution"}, - "outcome": {"const": "verified"} - } - } - ] - }, - "ociResult": { - "allOf": [ - {"$ref": "#/$defs/resultBase"}, - { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit", "source", "distribution", "outcome"], - "properties": { - "version": {"$ref": "#/$defs/version"}, - "commit": {"$ref": "#/$defs/commit"}, - "source": {"$ref": "#/$defs/source"}, - "distribution": {"$ref": "#/$defs/serverOciDistribution"}, - "outcome": {"const": "verified"} - } - } - ] - } - } -} diff --git a/issue-authority/README.md b/issue-authority/README.md deleted file mode 100644 index 1bea0ad..0000000 --- a/issue-authority/README.md +++ /dev/null @@ -1,242 +0,0 @@ -# GitHub issue authority - -GitHub Issues is the durable authority for new public product defects, features, -release blockers, and cross-repository work entering the 2.0 beta line. The -organization issue forms capture source evidence, acceptance criteria, -dependencies, and public-safe context at intake. - -## Vetted revisions - -Issue text is inert until its current revision has trusted intake authority. -Issues created by `rmcdaniel` or `durable-workflow-ops` are trusted at creation. -Every other issue must carry `intake:approved`, and the most recent transition -of that label must have been performed by one of those maintainers after the -most recent title or body edit. An edit invalidates an earlier approval. Label -removal also invalidates approval, while a later trusted reapplication binds a -new SHA-256 digest of the complete title and body. - -Before any environment-backed job is considered, a metadata-only, read-only -pass reconstructs these decisions from the issue author, last edit time, and -approval-label timeline. It fetches title and body only after that pass accepts -the revision, then binds the content digest. The resulting manifest contains -issue coordinates, approval evidence, revision digests, and the structured -completion-hold decision, but no issue text. -The lifecycle job directly refetches every manifest-selected issue and requires -its identity, approval actor, approval time, approval mode, and revision digest -to remain exact before processing the vetted issue bodies. A selected issue's -edit, approval change, or disappearance between those jobs therefore fails -closed before mutation. A newly created or newly visible issue that was absent -from the manifest remains inert until a later discovery run. -Lifecycle evidence retains the matched approval records and revision digests -for review, but later runs reconstruct authority from GitHub instead of -consuming an earlier artifact. - -Only the issue title, body revision, and explicit completion-hold label -participate in intake. Headings such as `Completion`, `Delete when`, or -`Acceptance` do not create a hold. Comments, pull-request text, workflow logs, -artifacts, and attachments are not queried or interpreted as issue -instructions. - -The cross-repository issue form records every required source target as an -exact `organization/repository@branch` selection in its required-targets -section. Intake binds those selections to the same vetted revision and resolves -their required checks from `qualification/policy.json`. The lifecycle audit -reads closing-reference authority from GitHub's GraphQL -`CrossReferencedEvent`, then evaluates the latest trusted linked implementation -pull request for each target. Same-repository execution must bind an authorized -pull-request author and closing-reference actor. A fork or other external -attempt participates only when an authorized maintainer's latest approval names -its exact current head commit and was submitted strictly after the event's -current `referencedAt` value. Removing, reintroducing, or retargeting that -reference therefore requires another approval. Every admitted attempt also -binds its exact pull-request identity, -head repository, head ref and commit, base repository, ref and commit, reference -event identity and time, and any required external approval identity and time -before latest-attempt selection. A target is complete only when that attempt -merged into the declared branch, the merge commit remains on the branch, and -every required repository check succeeded. A newer trusted open, rejected, or -rebuilt attempt supersedes earlier evidence for the same target; untrusted -public references remain inert. - -The protected merge gates may also land an implementation directly on each -target branch without creating public implementation pull requests. In that -case the lifecycle writer's aggregate completion record is the provenance -boundary. The record must contain the complete declared target set, each exact -`organization/repository@branch` identity, a full immutable landing commit, and -the immutable GitHub Actions run identity that qualified it. The primary target -is bound by the completion line and public qualification run; every peer uses -the merge gate's exact `repository:branch` landing-and-qualification entry, and -the record also carries one full-SHA completion-source marker matching the -primary landing. The audit accepts this path only when there is no trusted -linked pull-request attempt to supersede, the record belongs to the lifecycle -credential's immutable user identity, every recorded commit remains reachable -from its declared protected branch, each cited workflow run is green for that -exact repository and commit, and every policy-required check is independently -green on the commit. Retained evidence names both the cited run and the actual -run carrying each required check; they need not be the same workflow. Other -comments, mutable commit or run references, partial records, mismatched branches -or commits, and qualification claims that differ from the vetted target -contract remain non-completing. - -After pull-request, approval, landing, and check evaluation, the audit rereads -the target-relevant closing-reference records and requires the complete snapshot -to remain exact. It retries a changed snapshot only within a fixed bound and -otherwise publishes pending evidence. A second reread after lifecycle writes -immediately replaces raced completion evidence, reopens the parent, and restores -its prior open status when the snapshot changed during mutation. - -The same contract applies to issues created through the API or edited outside -the issue form. A cross-repository revision with a missing, empty, repeated, -unqualified, or single-target section is not admitted to the intake manifest. -The selective public backlog stores the same qualified target identities as -structured data, and its API issue renderer emits the required section in every -new cross-repository body. -Earlier trusted-created revisions are covered by a bounded, checked-in migration -catalog. Each catalog entry names a stable body marker and at least two exact -qualified targets; revisions created by the original issue form may instead -bind the exact repository identities in its legacy affected-repositories -section. The migration applies only to unchanged revisions created before the -target-set contract took effect. Editing, reapproving, or newly applying the -cross-repository label after that cutoff requires the current target section, -so a catalog entry cannot authorize a later revision. Active migrated authority -uses the normal aggregate landing lifecycle. A separate issue-and-revision-bound -record covers each archived completion and names its exact target set. Every -archived protected-branch landing stores the exact required-check identities -that qualified that commit. Before preserving closed/done state, the audit -revalidates the catalog's immutable landing commit against its protected branch -and frozen checks without resolving check names from the current qualification -policy. Changing historical evidence requires a reviewed migration of this -versioned catalog. Archived issues are not reopened, relabeled, or commented on -merely to satisfy the new body syntax. - -To migrate any revision outside that bounded catalog, edit its body to add at -least two exact qualified targets, then have a trusted maintainer remove and -reapply `intake:approved` after that edit. If the work has only one source -target, replace its cross-repository kind with the appropriate -single-repository kind instead. Until that migration is reviewed, intake and -lifecycle reconciliation fail closed. - -The audit maintains one generated issue comment containing the complete target -set, latest pull requests, landed commits, qualification results, and aggregate -state. This comment is public lifecycle evidence, not an instruction source. -The parent remains open until the aggregate is complete, while each repository -pull request may merge independently. If a later correction supersedes a -completed attempt, the audit reopens the parent. Once all latest attempts -qualify, it closes the parent and derives `status:done`. - -`policy.json` owns the public repository inventory and the shared label and -milestone vocabulary. `backlog.json` records the deliberate review of the -unresolved alpha queue and contains only the work selected for migration. A -review disposition does not copy the excluded issue or its operational history -to GitHub. - -## State direction - -GitHub owns both lifecycle state and completion approval. Ordinary authoritative -issues close when their source has landed and required repository qualification -has passed. A product owner may exceptionally add -`completion:evidence-required` during approved intake when completion also -requires publication, installable-artifact verification, an operational drill, -a live workflow, or another later observation. After that evidence is publicly -demonstrated, a maintainer records `completion:evidence-verified`; for a defect, -the public report must name the fixed version or source identity. - -Status labels remain derived triage aids. The audit changes stale labels to -`status:done` when an ordinary issue closes or an explicitly held issue has -satisfied its completion gate. A prematurely closed held issue or incomplete -cross-repository parent is reopened on GitHub and remains in its previous open -status (or returns to triage when that status is unavailable). Removing a -default or obsolete hold remains effective; target aggregation is normal -lifecycle completion and does not add an evidence hold. The audit restores an -evidence label only when the approved intake manifest explicitly declared it. -External automation may read or mirror GitHub state; it must not send lifecycle -state back to this workflow. - -An immutable prerelease artifact mismatch or an unreconstructable historical -direct-push landing may make a release blocker's original acceptance criteria -impossible. `policy.json` may name that issue and either one explicit successor -issue or one digest-bound immutable supported product train. Issue successors -remain evidence-required beta blockers in the same milestone while active. -Product-train successors name the exact public commit, file digest, train, and -release-plan identity, so retirement does not require manufacturing another -per-release issue. Discovery verifies the immutable public bytes before the -lifecycle job can act. - -The lifecycle job records one successful commit status on the policy's exact -`activation_commit`; its digest binds the policy mapping, reason, retired issue -revision, and successor authority. GitHub commit statuses are append-only, so -later discovery can distinguish that activation from mutable issue state. -Successor issue chains are allowed only when each retired identity is explicit -and the earlier activation is already retained. - -Already-retired authorities can be bootstrapped with the protected -`workflow_dispatch` `activate` mode. That job reconstructs the exact current -trusted title/body revisions and immutable product-train bytes directly from -the checked-in policy, rejects any conflicting GitHub Actions status authority, -and creates only missing statuses. Its token cannot write issue content or -lifecycle state, and dispatch accepts no commit, context, or digest input. - -The retired issue receives `status:superseded`, links to the successor in -generated lifecycle evidence, and closes as not planned without `status:done`, -`completion:evidence-required`, or `completion:evidence-verified`. Retirement -scrubs both stale completion labels because the original contract was neither -satisfied nor left awaiting evidence. Later audits preserve that terminal state -and do not evaluate its source landings. Once the -append-only activation matches, the exact successor issue revision may reach -verified completion and close without reopening its predecessor. Comments, -pull requests, current labels alone, and unapproved issue edits cannot create or -redirect this relationship. Every issue outside the reviewed mapping retains -the ordinary fail-closed completion and cross-repository behavior. - -Every migrated issue contains exactly one stable `beta-work-id` marker, and each -work ID identifies exactly one issue. Migration first searches open and closed -issues across the complete public inventory. One match preserves its current -title, maintainer-authored body, labels, and lifecycle state. The only -source-owned body field reconciled on replay is the bounded unblock-condition -section for a blocked migration. When review advances a dependency-free item -from blocked to ready, migration consumes that bounded section as transition -proof, removes it, and replaces `status:blocked` with `status:ready` on the same -open issue. Once the section is gone, later replays preserve GitHub lifecycle -changes. A repeated identity across issues receives `authority:conflict` and -makes the workflow fail. Multiple distinct identities on one issue fail a -read-only preflight before shared labels, milestones, issue labels, issue bodies, -or issue creation can change. Replaying migration therefore cannot alias -lifecycle or blocker state, duplicate an issue, or make completed GitHub work -pending again. - -A blocked migration must name either an earlier migrated dependency or an -explicit public-safe unblock condition. The renderer links migrated dependencies -and publishes external decision gates under an `Unblock condition` heading, so a -consumer can tell what must change before the work becomes ready. - -The scheduled audit also fails when a selected item is missing, when a marker -appears in the wrong repository, when an open authoritative issue has ambiguous -status labels, or when migrated ownership, beta classification, or milestone -metadata becomes incomplete. Bounded evidence is retained as a GitHub Actions -artifact on both successful and failed runs. - -## Credential boundary - -The workflow's repository-scoped `GITHUB_TOKEN` performs discovery with only -Contents read and Issues read permissions. GitHub's public repository graph is -available to that token across the explicit inventory in `policy.json`; no -operator-provisioned cross-repository discovery credential is required. The -token runs outside the protected environment and cannot change issue or -authority state. - -Cross-repository metadata and issues are written separately with -`BETA_PRODUCT_WORK_TOKEN` in the protected `beta-product-work` GitHub -environment. That lifecycle credential needs repository metadata read and -Issues read/write access only for the same public repositories. It does not -need source, package, release, environment, or private repository access, and -it is never exposed to an event whose current issue revision failed intake. -The repository-scoped job token performs the read-only pull-request, commit, -and check-run lookups used by target aggregation; the writer token is not used -for those reads. Public comment discovery also uses the job token. Before -updating a generated lifecycle comment, the workflow resolves the authenticated -writer identity from the lifecycle credential and matches both its immutable -user ID and login. Marker copies owned by any other user remain inert. - -Private Cloud implementation work is outside this inventory. If public -components need a Cloud-facing contract, the public issue describes only that -contract and links no private implementation detail. diff --git a/issue-authority/backlog-schema.json b/issue-authority/backlog-schema.json deleted file mode 100644 index 062f57c..0000000 --- a/issue-authority/backlog-schema.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/issue-authority/backlog-schema.json", - "title": "Durable Workflow selectively migrated beta backlog", - "type": "object", - "additionalProperties": false, - "required": ["schema", "reviewed", "milestone", "review", "items"], - "properties": { - "$schema": { - "type": "string" - }, - "schema": { - "const": "durable-workflow.github-beta-backlog/v1" - }, - "reviewed": { - "type": "string", - "format": "date" - }, - "milestone": { - "type": "string", - "minLength": 1 - }, - "review": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["review_id", "repository", "title", "disposition", "reason"], - "properties": { - "review_id": { - "$ref": "#/$defs/workId" - }, - "repository": { - "type": "string" - }, - "title": { - "type": "string", - "minLength": 1 - }, - "disposition": { - "enum": ["migrate", "superseded", "evidence-only", "experiment-only"] - }, - "reason": { - "type": "string", - "minLength": 1 - } - } - } - }, - "items": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { - "kind": { - "const": "cross-repository" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "required": [ - "required_source_targets" - ] - }, - "else": { - "not": { - "required": [ - "required_source_targets" - ] - } - } - } - ], - "required": [ - "id", - "repository", - "title", - "kind", - "priority", - "classification", - "status", - "body", - "depends_on" - ], - "properties": { - "id": { - "$ref": "#/$defs/workId" - }, - "repository": { - "type": "string" - }, - "title": { - "type": "string", - "minLength": 1, - "maxLength": 256 - }, - "kind": { - "enum": ["defect", "feature", "release-blocker", "cross-repository"] - }, - "priority": { - "enum": ["P0", "P1", "P2", "P3"] - }, - "classification": { - "enum": ["blocker", "compatible", "post-2.0"] - }, - "status": { - "enum": ["ready", "blocked"] - }, - "body": { - "type": "string", - "minLength": 1 - }, - "depends_on": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/workId" - } - }, - "required_source_targets": { - "type": "array", - "minItems": 2, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^durable-workflow/[a-z0-9_.-]+@(main|v2)$" - } - }, - "unblock_condition": { - "type": "string", - "minLength": 1, - "pattern": "\\S" - } - } - } - } - }, - "$defs": { - "workId": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9-]{2,79}$" - } - } -} diff --git a/issue-authority/backlog.json b/issue-authority/backlog.json deleted file mode 100644 index 634c45c..0000000 --- a/issue-authority/backlog.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "$schema": "./backlog-schema.json", - "items": [ - { - "body": "## Classification\n\nBeta-compatible release preparation owned by `durable-workflow/.github`.\n\n## Problem\n\nA component publisher cannot require an exact, dated version heading in a source commit when the release plan chooses that version only after the commit lands. Guessing the next version in ordinary product work can deadlock automatic releases or bind notes to the wrong tag. The public [release-plan contract](https://github.com/durable-workflow/.github/blob/main/release-plans/README.md) is the source boundary for the correction.\n\n## Acceptance criteria\n\n- GitHub release preparation durably binds the exact planned component version and source identity before publication.\n- The tagged source contains or immutably binds exact dated release notes without requiring a product contributor to select the version.\n- Preparation is idempotent and cannot recursively create another release.\n- Recovery resumes the same prepared source and version after interruption.\n- PHP, Python, Rust, CLI, and server release surfaces can fail closed on missing or mismatched versioned notes.\n\n## Completion\n\nOne automatic patch release retains exact planned-version changelog evidence at its source tag, recovers after interruption, and does not create a follow-on release from preparation metadata alone.", - "classification": "compatible", - "depends_on": [], - "id": "release-plan-versioned-changelogs", - "kind": "cross-repository", - "priority": "P2", - "repository": ".github", - "required_source_targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main" - ], - "status": "ready", - "title": "Prepare versioned changelogs without guessing release tags" - }, - { - "body": "## Classification\n\nBeta-compatible public-contract defect owned by `durable-workflow/durable-workflow.github.io`.\n\n## Evidence\n\nThe live [PHP SDK conformance contract](https://durable-workflow.com/platform-conformance/php-sdk-conformance.json) exposes repository-relative runner paths, an image-local path, and internal evidence filenames. The [source contract](https://github.com/durable-workflow/durable-workflow.github.io/blob/main/static/platform-conformance/php-sdk-conformance.json) is also not registered by the public suite discovery surface, so external consumers cannot resolve its authority relationship.\n\n## Acceptance criteria\n\n- If normative, the contract is registered by public URL and schema identity from the platform-conformance suite and discovery pages.\n- Runner and evidence requirements use public identifiers, schemas, or resolvable URLs. Validation-only paths and filenames are not published.\n- The public-boundary validator rejects absolute filesystem references, local URLs, repository paths, and internal harness artifacts while accepting public API routes and public URLs.\n- Validation considers every path candidate in mixed text independently, so an allowed API route cannot hide a later filesystem reference.\n\n## Completion\n\nThe live contract is consumer-resolvable or is removed from public publication, and GitHub qualification rejects reintroduction of validation-only paths.", - "classification": "compatible", - "depends_on": [], - "id": "docs-php-conformance-public-authority", - "kind": "defect", - "priority": "P2", - "repository": "durable-workflow.github.io", - "status": "ready", - "title": "Make the PHP SDK conformance contract consumer-resolvable" - }, - { - "body": "## Classification\n\nFinal beta continuity acceptance drill owned by `durable-workflow/.github`.\n\n## Required outcome\n\n- Accept and triage a representative public product issue entirely on GitHub.\n- Qualify a landed target commit through protected GitHub checks and retain the exact candidate identity remotely.\n- Initiate a coherent seven-artifact prerelease wave from GitHub, interrupt it after partial publication, and resume the identical plan without duplicate tags, versions, or artifacts.\n- Verify the public artifacts and run representative exact-tuple conformance from a clean GitHub runner.\n- Retain queryable GitHub evidence for issue state, qualification, release phases, verification, and conformance.\n- Demonstrate repository-owned recovery and protected credential boundaries with the bootstrap environment unavailable.\n- File every dependency discovered during the drill as focused GitHub-owned product work.\n\n## Scope\n\nThis issue proves the assembled public system. Product defects discovered by the drill belong in their owning public repositories and are linked here.\n\n## Completion\n\nThe complete flow succeeds using GitHub and public registries as its durable authorities, and no required beta decision exists only in an operator-local system.", - "classification": "blocker", - "depends_on": [], - "id": "github-only-beta-continuity-drill", - "kind": "cross-repository", - "priority": "P2", - "repository": ".github", - "required_source_targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sample-app@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ], - "status": "ready", - "title": "Prove beta continuity from GitHub and public registries" - }, - { - "body": "## Classification\n\nFinal 2.0 beta authorization gate owned by `durable-workflow/.github`.\n\n## Required outcome\n\n- Review the public API, wire protocol, durable history, storage schema, worker registration/versioning, and PHP/Python/Rust SDK contracts. No unresolved P0 or P1 item may require an intentional breaking redesign.\n- Workflow and Waterline pass their authoritative GitHub matrices on the exact candidate commits, and every other public target passes its required GitHub qualification.\n- One coherent seven-artifact prerelease tuple is publicly installable, immutable, and covered by retained exact-artifact conformance.\n- GitHub owns release initiation and recovery, public issue intake and the selective beta backlog, and portable conformance.\n- The complete GitHub-only continuity drill passes, including recovery after partial publication.\n- Every remaining open issue is classified as a beta-compatible defect, post-2.0 work, or a beta blocker.\n\n## Beta contract\n\nAuthorizing `2.0.0-beta.1` ends routine alpha-style breaking changes. Later changes preserve beta compatibility unless a correctness or security defect requires an explicitly recorded exception. Stable `2.0.0` remains separately authorized.\n\n## Completion\n\nThe product owner records the exact candidate, qualification, verification, conformance, backlog review, and continuity-drill evidence in GitHub, then authorizes the first beta release wave.", - "classification": "blocker", - "depends_on": [ - "github-only-beta-continuity-drill" - ], - "id": "authorize-2-0-beta", - "kind": "release-blocker", - "priority": "P0", - "repository": ".github", - "status": "blocked", - "title": "Authorize 2.0 beta after contract freeze and remote continuity" - } - ], - "milestone": "2.0 beta", - "review": [ - { - "disposition": "evidence-only", - "reason": "The public source correction has landed and current public-file leak evidence passes; only published-artifact observation remained.", - "repository": "workflow", - "review_id": "workflow-ci-host-neutrality", - "title": "Remove internal CI-host terminology from the public build contract" - }, - { - "disposition": "superseded", - "reason": "The focused continuity drill and beta authorization gate preserve the executable acceptance criteria without duplicating an umbrella milestone.", - "repository": "server", - "review_id": "beta-continuity-umbrella", - "title": "Beta exit criterion: prove GitHub-only product continuity" - }, - { - "disposition": "evidence-only", - "reason": "The public source correction has landed and current public-file leak evidence passes; only published-artifact observation remained.", - "repository": "sdk-python", - "review_id": "python-ci-host-neutrality", - "title": "Make public CI portability fixtures host-neutral" - }, - { - "disposition": "migrate", - "reason": "Version-aware release preparation remains unresolved and affects recoverable beta patch publication across public repositories.", - "repository": ".github", - "review_id": "release-plan-versioned-changelogs", - "title": "Prepare versioned changelogs without guessing release tags" - }, - { - "disposition": "migrate", - "reason": "The GitHub-only continuity drill is a required beta gate and has no substitute in completed alpha history.", - "repository": ".github", - "review_id": "github-only-beta-continuity-drill", - "title": "Prove beta continuity from GitHub and public registries" - }, - { - "disposition": "migrate", - "reason": "The final beta authorization decision and its evidence must remain available without any operator-local database.", - "repository": ".github", - "review_id": "authorize-2-0-beta", - "title": "Authorize 2.0 beta after contract freeze and remote continuity" - }, - { - "disposition": "evidence-only", - "reason": "The tuple refresh is already deployed; only a post-deployment execution record remained.", - "repository": "durable-workflow.github.io", - "review_id": "docs-public-tuple-refresh", - "title": "Refresh the public 2.0 artifact tuple" - }, - { - "disposition": "experiment-only", - "reason": "This record exists only to retain a passing runtime experiment and does not describe unresolved product implementation.", - "repository": "durable-workflow.github.io", - "review_id": "php-sdk-published-conformance-evidence", - "title": "Record passing published PHP SDK conformance" - }, - { - "disposition": "migrate", - "reason": "The live public contract still exposes validation-only paths and lacks a consumer-resolvable authority relationship.", - "repository": "durable-workflow.github.io", - "review_id": "docs-php-conformance-public-authority", - "title": "Make the PHP SDK conformance contract consumer-resolvable" - }, - { - "disposition": "evidence-only", - "reason": "The onboarding source and live execution contract have landed; the remaining acceptance is a published quickstart execution record.", - "repository": "durable-workflow.github.io", - "review_id": "docs-first-success-quickstart", - "title": "Make the 2.0 quickstart a true first-success guide" - } - ], - "reviewed": "2026-07-19", - "schema": "durable-workflow.github-beta-backlog/v1" -} diff --git a/issue-authority/legacy-cross-repository-targets-schema.json b/issue-authority/legacy-cross-repository-targets-schema.json deleted file mode 100644 index d2afdf0..0000000 --- a/issue-authority/legacy-cross-repository-targets-schema.json +++ /dev/null @@ -1,332 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/issue-authority/legacy-cross-repository-targets-schema.json", - "additionalProperties": false, - "properties": { - "$schema": { - "type": "string" - }, - "authorities": { - "items": { - "additionalProperties": false, - "properties": { - "id": { - "pattern": "^[a-z0-9][a-z0-9-]{2,79}$", - "type": "string" - }, - "marker": { - "enum": [ - "beta-work-id", - "durable-workflow-work-id" - ] - }, - "targets": { - "items": { - "pattern": "^durable-workflow/[a-z0-9_.-]+@(main|v2)$", - "type": "string" - }, - "minItems": 2, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "id", - "marker", - "targets" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "created_before": { - "format": "date-time", - "type": "string" - }, - "frozen_lifecycle_migrations": { - "items": { - "additionalProperties": false, - "properties": { - "approval_actor_sha256": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - }, - "approval_at": { - "format": "date-time", - "type": "string" - }, - "approval_mode": { - "enum": [ - "trusted-creation", - "trusted-label" - ] - }, - "authority_snapshot_sha256": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - }, - "completion_source": { - "pattern": "^[0-9a-f]{40}$", - "type": [ - "string", - "null" - ] - }, - "declared_targets": { - "items": { - "pattern": "^durable-workflow/[a-z0-9_.-]+@(main|v2)$", - "type": "string" - }, - "minItems": 2, - "type": "array", - "uniqueItems": true - }, - "landings": { - "items": { - "additionalProperties": false, - "properties": { - "branch": { - "enum": [ - "main", - "v2" - ] - }, - "commit": { - "pattern": "^[0-9a-f]{40}$", - "type": "string" - }, - "qualification": { - "additionalProperties": false, - "properties": { - "checks": { - "items": { - "additionalProperties": false, - "properties": { - "job": { - "minimum": 1, - "type": "integer" - }, - "name": { - "minLength": 1, - "type": "string" - }, - "run": { - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "job", - "name", - "run" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "run": { - "minimum": 1, - "type": "integer" - }, - "workflow_name": { - "minLength": 1, - "type": "string" - }, - "workflow_path": { - "pattern": "^[A-Za-z0-9_.-]+\\.ya?ml$", - "type": "string" - } - }, - "required": [ - "run", - "workflow_name", - "workflow_path", - "checks" - ], - "type": "object" - }, - "repository": { - "pattern": "^[a-z0-9_.-]+$", - "type": "string" - } - }, - "required": [ - "repository", - "branch", - "commit", - "qualification" - ], - "type": "object" - }, - "type": "array" - }, - "missing_evidence": { - "type": [ - "string", - "null" - ] - }, - "number": { - "minimum": 1, - "type": "integer" - }, - "outcome": { - "enum": [ - "complete", - "missing-evidence" - ] - }, - "repository": { - "pattern": "^[a-z0-9_.-]+$", - "type": "string" - }, - "approved_issue_revision_sha256": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - } - }, - "required": [ - "repository", - "number", - "authority_snapshot_sha256", - "approved_issue_revision_sha256", - "approval_actor_sha256", - "approval_at", - "approval_mode", - "outcome", - "declared_targets", - "completion_source", - "landings", - "missing_evidence" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "historical_completions": { - "items": { - "additionalProperties": false, - "properties": { - "number": { - "minimum": 1, - "type": "integer" - }, - "repository": { - "pattern": "^[a-z0-9_.-]+$", - "type": "string" - }, - "revision": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - }, - "targets": { - "items": { - "pattern": "^durable-workflow/[a-z0-9_.-]+@(main|v2)$", - "type": "string" - }, - "minItems": 2, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "repository", - "number", - "revision", - "targets" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "immutable_issue_targets": { - "description": "Reviewed source-target bindings keyed by exact public issue identity. Issue text may explain but cannot override these targets.", - "items": { - "additionalProperties": false, - "properties": { - "number": { - "minimum": 1, - "type": "integer" - }, - "repository": { - "pattern": "^[a-z0-9_.-]+$", - "type": "string" - }, - "targets": { - "items": { - "pattern": "^durable-workflow/[a-z0-9_.-]+@(main|v2)$", - "type": "string" - }, - "minItems": 2, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "repository", - "number", - "targets" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "protected_branch_landings": { - "items": { - "additionalProperties": false, - "properties": { - "branch": { - "enum": [ - "main", - "v2" - ] - }, - "commit": { - "pattern": "^[0-9a-f]{40}$", - "type": "string" - }, - "repository": { - "pattern": "^[a-z0-9_.-]+$", - "type": "string" - }, - "required_checks": { - "items": { - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "repository", - "branch", - "commit", - "required_checks" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "schema": { - "const": "durable-workflow.legacy-cross-repository-targets/v5" - } - }, - "required": [ - "schema", - "created_before", - "authorities", - "immutable_issue_targets", - "historical_completions", - "frozen_lifecycle_migrations", - "protected_branch_landings" - ], - "type": "object" -} diff --git a/issue-authority/legacy-cross-repository-targets.json b/issue-authority/legacy-cross-repository-targets.json deleted file mode 100644 index 9c33baf..0000000 --- a/issue-authority/legacy-cross-repository-targets.json +++ /dev/null @@ -1,557 +0,0 @@ -{ - "$schema": "./legacy-cross-repository-targets-schema.json", - "authorities": [ - { - "id": "release-plan-versioned-changelogs", - "marker": "beta-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main" - ] - }, - { - "id": "github-only-beta-continuity-drill", - "marker": "beta-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sample-app@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "github-actions-trust-boundary", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sample-app@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "qualification-bind-privileged-artifact-digests", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/waterline@v2" - ] - }, - { - "id": "coherent-2-0-beta-4-train", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sample-app@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2" - ] - }, - { - "id": "coherent-component-release-recovery-authority", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "qualified-immutable-recovery-workflow-authority", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "verify-release-plan-supersession-approval-against-github-authority", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "release-plan-discovery-reject-ambiguous-continuity-successors", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "waterline-dual-distribution-release-authority", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/waterline@v2" - ] - }, - { - "id": "release-plan-discovery-stable-authority-snapshot", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "release-plan-explicit-recovery-terminal-lifecycle-gate", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "release-plan-discovery-ignore-mutable-release-order", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "release-plan-strict-semver-authority-validation", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "release-plan-semver-successor-transition-safety", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "version-component-release-recovery-consumer-conformance", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "id": "remove-hosted-runtime-target-contract", - "marker": "durable-workflow-work-id", - "targets": [ - "durable-workflow/server@main", - "durable-workflow/workflow@v2" - ] - } - ], - "historical_completions": [ - { - "number": 2, - "repository": ".github", - "revision": "ba44faf30e41168b5919c38cb49dc88af9e128dbf50d3a00c6322cf4209f897e", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sample-app@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 26, - "repository": ".github", - "revision": "54435f4fb585f28d392c77bf233a17cbbf9d8fe2ce69585c0a4e274ef3ca41d4", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sample-app@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 32, - "repository": ".github", - "revision": "15e93174de92fb96a85f964e5db289cd52b015f7d8fb3210c5dde4df71b9ac50", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 33, - "repository": ".github", - "revision": "6e74a1aae6fbd622e8cdc5e67a8ed43038d1dea29462a83a90c58e750be777be", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 35, - "repository": ".github", - "revision": "02f0fefc7daf5861293e2a5a43ddadcc88c0a65c0fa191fbc22287829105e574", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 36, - "repository": ".github", - "revision": "f2a51b8acb24f8933a03cfa61d6056f952571047bb57c04d586a314bd65e8ffc", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 37, - "repository": ".github", - "revision": "93edd6788d835ffa4418f2aedb3a0fcdf47842c000ee4c9d12640fc2df812349", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 38, - "repository": ".github", - "revision": "ffa12ab58f0fc2f4a563180dbb2815743daa5bcad3848c0cbe57c1fe750e2694", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 39, - "repository": ".github", - "revision": "04d0feb26f9db860b209e1921d37d435906393fad404184d03ccf873bcb70de9", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 41, - "repository": ".github", - "revision": "10747417ad575473547430515ebd1f351a7eca2f4d504a362028709d1fd17f11", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main" - ] - }, - { - "number": 46, - "repository": ".github", - "revision": "3344ef7259e8da37dd0486c9806f978268ba1c5c658c2377f1ad6e548ec33643", - "targets": [ - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/workflow@v2" - ] - }, - { - "number": 48, - "repository": ".github", - "revision": "d9b0cbfe9d1b5f3dba5fee2841696bcf3de4061d09f194059e3fbde671484bd8", - "targets": [ - "durable-workflow/.github@main", - "durable-workflow/cli@main", - "durable-workflow/sdk-php@main", - "durable-workflow/sdk-python@main", - "durable-workflow/sdk-rust@main", - "durable-workflow/server@main", - "durable-workflow/waterline@v2", - "durable-workflow/workflow@v2" - ] - } - ], - "immutable_issue_targets": [ - { - "number": 32, - "repository": "sdk-python", - "targets": [ - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sdk-python@main" - ] - } - ], - "frozen_lifecycle_migrations": [ - { - "approval_actor_sha256": "c1e557a26d086d65ed41bfcf3dbc8bbd2526544117a56aa2c08b3115f9ca802e", - "approval_at": "2026-07-30T20:37:14Z", - "approval_mode": "trusted-creation", - "authority_snapshot_sha256": "5ffcae7ed560f99c217788fb62df36cfc0df75f149ae349db661862e45ccc55b", - "completion_source": "6542094b6ae27711186a17ba107a6718fa3a1c83", - "declared_targets": [ - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/workflow@v2" - ], - "approved_issue_revision_sha256": "4da46c504298af75643744bea5e793671f4318007dac5ba9608a68dc2d160909", - "landings": [ - { - "branch": "main", - "commit": "6542094b6ae27711186a17ba107a6718fa3a1c83", - "qualification": { - "checks": [ - { - "job": 91047691168, - "name": "Executable documentation contracts", - "run": 30595766880 - } - ], - "run": 30595766880, - "workflow_name": "Documentation Qualification", - "workflow_path": "qualification.yml" - }, - "repository": "durable-workflow.github.io" - }, - { - "branch": "v2", - "commit": "7eb21763078fd1cf3ec00fbccebf48eb85547110", - "qualification": { - "checks": [ - { - "job": 91030116510, - "name": "build", - "run": 30589737606 - } - ], - "run": 30589737606, - "workflow_name": "build", - "workflow_path": "php.yml" - }, - "repository": "workflow" - } - ], - "missing_evidence": null, - "number": 63, - "outcome": "complete", - "repository": "durable-workflow.github.io" - }, - { - "approval_actor_sha256": "c1e557a26d086d65ed41bfcf3dbc8bbd2526544117a56aa2c08b3115f9ca802e", - "approval_at": "2026-08-10T00:33:42Z", - "approval_mode": "trusted-creation", - "authority_snapshot_sha256": "591adaf96dfbd9f9b0f8b31e3a213f73660dee14de0507d24b7779529794928d", - "completion_source": null, - "declared_targets": [ - "durable-workflow/sample-app@main", - "durable-workflow/waterline@v2" - ], - "approved_issue_revision_sha256": "c8f2b27b33e562350b31cd781f95270abd83d37cbd5b8f926d24d8ac60357d77", - "landings": [], - "missing_evidence": "The protected Waterline landing fa28853fe0e12743d0159eb29497d95b4e7d2d57 is installable as 2.0.0-rc.14, but no exact successful post-correction Sample App run records that released package completing the required responsive opened-dialog qualification on the protected target branch.", - "number": 79, - "outcome": "missing-evidence", - "repository": "waterline" - } - ], - "protected_branch_landings": [ - { - "branch": "main", - "commit": "88128029486735ee9be54d1ce6442b6b23956bbe", - "repository": ".github", - "required_checks": [ - "Control-plane source qualification" - ] - }, - { - "branch": "main", - "commit": "c1e6ea3b7045312f7699062bffcdc8addefdb2c3", - "repository": "cli", - "required_checks": [ - "Target branch qualification" - ] - }, - { - "branch": "main", - "commit": "23e90eb5f28d2cc79095ff582b7c20be4d6e5657", - "repository": "durable-workflow.github.io", - "required_checks": [ - "Executable documentation contracts" - ] - }, - { - "branch": "main", - "commit": "dc710a239ad558974148cc24b67243eac42136c7", - "repository": "sample-app", - "required_checks": [ - "Target branch qualification", - "docker compose sample workflows", - "polyglot smoke (PHP/Python/Rust)" - ] - }, - { - "branch": "main", - "commit": "fa1a07947236a5b4b314aedbddceecdf195ae268", - "repository": "sdk-php", - "required_checks": [ - "Target branch qualification" - ] - }, - { - "branch": "main", - "commit": "9ec515842af0ba1db4bb865ef5927e73df0da19a", - "repository": "sdk-python", - "required_checks": [ - "Target branch qualification" - ] - }, - { - "branch": "main", - "commit": "c8f5e7dac692c8e7b9cd0af980036c10c94ae258", - "repository": "sdk-rust", - "required_checks": [ - "Target branch qualification" - ] - }, - { - "branch": "main", - "commit": "6abbe41c12c16daf9715578e7a7f7ce79a91f04e", - "repository": "server", - "required_checks": [ - "PHPUnit feature suite" - ] - }, - { - "branch": "v2", - "commit": "ab25539cfad2e119ac132be4ef156fad36ec13f2", - "repository": "waterline", - "required_checks": [ - "Target branch qualification" - ] - }, - { - "branch": "v2", - "commit": "11afa4eeef8cd7ce05bb6834159d80be0348e48d", - "repository": "workflow", - "required_checks": [ - "build" - ] - } - ], - "created_before": "2026-07-25T00:01:08Z", - "schema": "durable-workflow.legacy-cross-repository-targets/v5" -} diff --git a/issue-authority/policy-schema.json b/issue-authority/policy-schema.json deleted file mode 100644 index 5e251dc..0000000 --- a/issue-authority/policy-schema.json +++ /dev/null @@ -1,272 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/issue-authority/policy-schema.json", - "title": "Durable Workflow GitHub issue authority policy", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "organization", - "authority_repository", - "state_direction", - "intake", - "lifecycle", - "repositories", - "labels", - "milestones", - "prerelease_supersessions" - ], - "properties": { - "$schema": { - "type": "string" - }, - "schema": { - "const": "durable-workflow.github-issue-authority/v1" - }, - "organization": { - "const": "durable-workflow" - }, - "authority_repository": { - "const": ".github" - }, - "state_direction": { - "const": "github-to-mirrors" - }, - "intake": { - "type": "object", - "additionalProperties": false, - "required": ["approval_label", "trusted_actors"], - "properties": { - "approval_label": { - "const": "intake:approved" - }, - "trusted_actors": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[A-Za-z0-9-]+$" - }, - "allOf": [ - { - "contains": { - "const": "rmcdaniel" - } - }, - { - "contains": { - "const": "durable-workflow-ops" - } - } - ] - } - } - }, - "lifecycle": { - "type": "object", - "additionalProperties": false, - "required": [ - "audit_interval_seconds", - "creation_stale_age_seconds", - "completed_issue_closure_seconds", - "execution_state_direction", - "max_open_actionable_per_repository", - "maintainer_vetting_seconds", - "priority_escalation_seconds", - "approved_state_seconds", - "product_owner_alert_seconds", - "projection_actors", - "projection_event", - "stale_approved_transition_seconds", - "unattended_placeholder_seconds", - "state_labels" - ], - "properties": { - "audit_interval_seconds": {"const": 600}, - "creation_stale_age_seconds": {"const": 604800}, - "completed_issue_closure_seconds": {"const": 900}, - "execution_state_direction": {"const": "trusted-execution-to-github"}, - "max_open_actionable_per_repository": {"const": 1}, - "maintainer_vetting_seconds": {"const": 86400}, - "priority_escalation_seconds": {"const": 259200}, - "approved_state_seconds": {"const": 86400}, - "product_owner_alert_seconds": {"const": 604800}, - "projection_actors": {"const": ["durable-workflow-ops"]}, - "projection_event": {"const": "public-lifecycle"}, - "stale_approved_transition_seconds": {"const": 259200}, - "unattended_placeholder_seconds": {"const": 1209600}, - "state_labels": { - "type": "object", - "additionalProperties": false, - "required": [ - "awaiting-maintainer-vetting", - "approved-queued", - "in-progress", - "blocked", - "completed" - ], - "properties": { - "awaiting-maintainer-vetting": {"const": "status:triage"}, - "approved-queued": {"const": "status:ready"}, - "in-progress": {"const": "status:in-progress"}, - "blocked": {"const": "status:blocked"}, - "completed": {"const": "status:done"} - } - } - } - }, - "repositories": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[A-Za-z0-9._-]+$" - } - }, - "labels": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["name", "color", "description"], - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 50 - }, - "color": { - "type": "string", - "pattern": "^[0-9a-f]{6}$" - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 100 - } - } - } - }, - "milestones": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["title", "description", "state", "repositories"], - "properties": { - "title": { - "type": "string", - "minLength": 1, - "maxLength": 100 - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 500 - }, - "state": { - "enum": ["open", "closed"] - }, - "repositories": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[A-Za-z0-9._-]+$" - } - } - } - } - }, - "prerelease_supersessions": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "required": ["activation_commit", "retired", "successor", "reason"], - "properties": { - "activation_commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "retired": {"$ref": "#/$defs/issueIdentity"}, - "successor": { - "oneOf": [ - {"$ref": "#/$defs/issueIdentity"}, - {"$ref": "#/$defs/immutableProductTrainIdentity"} - ] - }, - "reason": { - "type": "string", - "minLength": 1, - "maxLength": 500 - } - } - } - } - }, - "$defs": { - "issueIdentity": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "number"], - "properties": { - "repository": { - "type": "string", - "pattern": "^[A-Za-z0-9._-]+$" - }, - "number": { - "type": "integer", - "minimum": 1 - } - } - }, - "immutableProductTrainIdentity": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "commit", "path", "sha256", "train", "release_plan"], - "properties": { - "repository": { - "type": "string", - "pattern": "^[A-Za-z0-9._-]+$" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "path": { - "const": "product-train/current.json" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "train": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+-(?:alpha|beta|rc)\\.[0-9]+$" - }, - "release_plan": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-plan/[a-z0-9][a-z0-9-]{2,79}$" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - } - } - } - } - } - } -} diff --git a/issue-authority/policy.json b/issue-authority/policy.json deleted file mode 100644 index 17544a8..0000000 --- a/issue-authority/policy.json +++ /dev/null @@ -1,300 +0,0 @@ -{ - "$schema": "./policy-schema.json", - "authority_repository": ".github", - "intake": { - "approval_label": "intake:approved", - "trusted_actors": [ - "rmcdaniel", - "durable-workflow-ops" - ] - }, - "lifecycle": { - "audit_interval_seconds": 600, - "creation_stale_age_seconds": 604800, - "completed_issue_closure_seconds": 900, - "execution_state_direction": "trusted-execution-to-github", - "max_open_actionable_per_repository": 1, - "maintainer_vetting_seconds": 86400, - "priority_escalation_seconds": 259200, - "projection_actors": [ - "durable-workflow-ops" - ], - "projection_event": "public-lifecycle", - "approved_state_seconds": 86400, - "product_owner_alert_seconds": 604800, - "stale_approved_transition_seconds": 259200, - "unattended_placeholder_seconds": 1209600, - "state_labels": { - "awaiting-maintainer-vetting": "status:triage", - "approved-queued": "status:ready", - "in-progress": "status:in-progress", - "blocked": "status:blocked", - "completed": "status:done" - } - }, - "labels": [ - { - "color": "0969da", - "description": "GitHub is the authoritative lifecycle record for this work", - "name": "authority:github" - }, - { - "color": "b60205", - "description": "Issue authority has a duplicate or state conflict requiring review", - "name": "authority:conflict" - }, - { - "color": "0e8a16", - "description": "Current issue title and body revision is approved for authority intake", - "name": "intake:approved" - }, - { - "color": "d73a4a", - "description": "A public product behavior is incorrect", - "name": "kind:defect" - }, - { - "color": "a2eeef", - "description": "A public product capability or experience is requested", - "name": "kind:feature" - }, - { - "color": "b60205", - "description": "A release cannot proceed until this issue is resolved", - "name": "kind:release-blocker" - }, - { - "color": "5319e7", - "description": "Work spans more than one public repository", - "name": "kind:cross-repository" - }, - { - "color": "1d76db", - "description": "One exact-tuple conformance experiment result", - "name": "kind:conformance-run" - }, - { - "color": "b60205", - "description": "Immediate release or data-integrity risk", - "name": "priority:P0" - }, - { - "color": "d93f0b", - "description": "High-priority product or release risk", - "name": "priority:P1" - }, - { - "color": "fbca04", - "description": "Normal-priority product work", - "name": "priority:P2" - }, - { - "color": "0e8a16", - "description": "Lower-priority product improvement", - "name": "priority:P3" - }, - { - "color": "cfd3d7", - "description": "Maintainers have not assigned a priority", - "name": "priority:untriaged" - }, - { - "color": "ededed", - "description": "Awaiting maintainer classification", - "name": "status:triage" - }, - { - "color": "0e8a16", - "description": "Ready for implementation", - "name": "status:ready" - }, - { - "color": "1d76db", - "description": "Approved implementation is actively in progress", - "name": "status:in-progress" - }, - { - "color": "d93f0b", - "description": "Waiting on a named dependency or decision", - "name": "status:blocked" - }, - { - "color": "6f42c1", - "description": "Derived from the authoritative closed issue state", - "name": "status:done" - }, - { - "color": "8250df", - "description": "Retired in favor of an active prerelease successor without completion", - "name": "status:superseded" - }, - { - "color": "d93f0b", - "description": "Close only after all explicit acceptance and operational evidence is public", - "name": "completion:evidence-required" - }, - { - "color": "0e8a16", - "description": "Acceptance, fixed version, and required operational evidence are publicly verified", - "name": "completion:evidence-verified" - }, - { - "color": "b60205", - "description": "Must be resolved before the first 2.0 beta", - "name": "beta:blocker" - }, - { - "color": "0e8a16", - "description": "May be completed without changing the frozen beta contract", - "name": "beta:compatible" - }, - { - "color": "c5def5", - "description": "Explicitly deferred until after 2.0", - "name": "post-2.0" - }, - { - "color": "bfdadc", - "description": "Owned by the public organization control plane", - "name": "repo:github-control-plane" - }, - { - "color": "bfdadc", - "description": "Owned by the Workflow engine repository", - "name": "repo:workflow" - }, - { - "color": "bfdadc", - "description": "Owned by the Waterline operator repository", - "name": "repo:waterline" - }, - { - "color": "bfdadc", - "description": "Owned by the standalone server repository", - "name": "repo:server" - }, - { - "color": "bfdadc", - "description": "Owned by the CLI repository", - "name": "repo:cli" - }, - { - "color": "bfdadc", - "description": "Owned by the AI repository", - "name": "repo:ai" - }, - { - "color": "bfdadc", - "description": "Owned by the sample application repository", - "name": "repo:sample-app" - }, - { - "color": "bfdadc", - "description": "Owned by the PHP SDK repository", - "name": "repo:sdk-php" - }, - { - "color": "bfdadc", - "description": "Owned by the Python SDK repository", - "name": "repo:sdk-python" - }, - { - "color": "bfdadc", - "description": "Owned by the Rust SDK repository", - "name": "repo:sdk-rust" - }, - { - "color": "bfdadc", - "description": "Owned by the public documentation repository", - "name": "repo:documentation" - } - ], - "milestones": [ - { - "description": "Public product work that must be resolved or explicitly classified before 2.0 beta authorization.", - "repositories": [ - ".github", - "workflow", - "waterline", - "server", - "cli", - "ai", - "sample-app", - "sdk-php", - "sdk-python", - "sdk-rust", - "durable-workflow.github.io" - ], - "state": "open", - "title": "2.0 beta" - } - ], - "organization": "durable-workflow", - "prerelease_supersessions": [ - { - "activation_commit": "93fab46b8eb028d0302b0438591a8c67bd4b0d9f", - "reason": "The immutable Server 2.0.0-beta.13 image contains Workflow 2.0.0-beta.10 and cannot represent a coherent synchronized beta.13 train.", - "retired": { - "number": 59, - "repository": ".github" - }, - "successor": { - "number": 61, - "repository": ".github" - } - }, - { - "activation_commit": "bf822f3ba0ca4f05a00649112fad9848a2557140", - "reason": "The coherent 2.0.0-beta.14 train is superseded by the later immutable coherent 2.0.0-beta.17 public product train, while its historical direct target-branch landings cannot be reconstructed as linked pull requests.", - "retired": { - "number": 61, - "repository": ".github" - }, - "successor": { - "commit": "bf822f3ba0ca4f05a00649112fad9848a2557140", - "path": "product-train/current.json", - "release_plan": { - "sha256": "8084152882a3c7751db1315a4251b93c7f44b9dd83f01c25e5a097c59bdcf3f5", - "tag": "release-plan/coherent-2-0-beta-17" - }, - "repository": ".github", - "sha256": "12893e1d2cfe637d49631f639e53aa85445ff0292567442e98dc9aad91649417", - "train": "2.0.0-beta.17" - } - }, - { - "activation_commit": "bf822f3ba0ca4f05a00649112fad9848a2557140", - "reason": "The prerelease-specific 2.0.0-beta.16 authority is retired in favor of its later immutable coherent 2.0.0-beta.17 public product-train record, while its historical direct target-branch landings cannot be reconstructed as linked pull requests.", - "retired": { - "number": 65, - "repository": ".github" - }, - "successor": { - "commit": "bf822f3ba0ca4f05a00649112fad9848a2557140", - "path": "product-train/current.json", - "release_plan": { - "sha256": "8084152882a3c7751db1315a4251b93c7f44b9dd83f01c25e5a097c59bdcf3f5", - "tag": "release-plan/coherent-2-0-beta-17" - }, - "repository": ".github", - "sha256": "12893e1d2cfe637d49631f639e53aa85445ff0292567442e98dc9aad91649417", - "train": "2.0.0-beta.17" - } - } - ], - "repositories": [ - ".github", - "workflow", - "waterline", - "server", - "cli", - "ai", - "sample-app", - "sdk-php", - "sdk-python", - "sdk-rust", - "durable-workflow.github.io" - ], - "schema": "durable-workflow.github-issue-authority/v1", - "state_direction": "github-to-mirrors" -} diff --git a/product-train/README.md b/product-train/README.md deleted file mode 100644 index d8fa5ca..0000000 --- a/product-train/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Durable Workflow product train - -[`current.json`](current.json) is the machine-readable authority for the one -supported Durable Workflow 2.0 product train. Selecting its `current` record -selects exact server, CLI, Workflow, Waterline operator, PHP SDK, Python SDK, -and Rust SDK artifacts together. The record identifier names the aggregate -authority; each component keeps its own prerelease sequence. Python registry -versions use the PEP 440 spelling of the selected Python SDK version. -Waterline remains one component. Its `install.waterline` record publishes -separate `embedded` Composer and `service` OCI commands at that component's -single train version. - -[`payload-codec-contract.json`](payload-codec-contract.json) is the synchronized -prerelease release gate for durable values. It distinguishes JSON HTTP -transport from the sole public payload codec, Avro, and requires a blocking, -non-destructive persisted-history preflight plus exact published-artifact -qualification before a train can be declared complete. - -The supported train also binds its exact immutable `release-plan/*` tag and -canonical plan digest. A train is not complete authority until that plan and -its matching terminal completion record are public. - -The same train binds -[`sdk-server-qualification.json`](sdk-server-qualification.json) by SHA-256. -That record carries the exact source identity, outcome, and published-artifact -conformance source for every PHP, Python, and Rust SDK binding to Server. A -missing, failed, stale, or tuple-mismatched qualification is not a supported -train and cannot become public compatibility guidance. Validation downloads the -pinned conformance suite, verifies its bytes, and requires passing heartbeat, -replay, and signal/query results that exercised all three SDK clients. - -New aggregate prerelease plans must use every exact version in the current -train. Component prerelease sequences advance independently, while one -immutable candidate, release plan, and retained conformance suite bind the -installable tuple as a unit. After stable 2.0, compatible capabilities follow -ordinary semantic-version progression: fixes use patches, additive -capabilities use minors, and breaking changes use a new major. - -Earlier 2.0 alphas, betas, release candidates, and mixed-version tuples remain -immutable historical artifacts, but they are unsupported and omitted from -install guidance. They may be yanked where a registry supports yanking without -deleting release history. No compatibility adapter between those prereleases -and the current train is part of the product contract. diff --git a/product-train/current.json b/product-train/current.json deleted file mode 100644 index b93eafc..0000000 --- a/product-train/current.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema": "./schema.json", - "schema": "durable-workflow.product-train/v2", - "current": "2.0.0-rc.13", - "components": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "trains": { - "2.0.0-rc.13": { - "channel": "rc", - "status": "supported", - "versions": { - "workflow": "2.0.0-rc.12", - "sdk-php": "2.0.0-rc.6", - "waterline": "2.0.0-rc.9", - "server": "2.0.0-rc.13", - "cli": "2.0.0-rc.12", - "sdk-python": "2.0.0-rc.8", - "sdk-rust": "2.0.0-rc.7" - }, - "registry_versions": { - "workflow": "2.0.0-rc.12", - "sdk-php": "2.0.0-rc.6", - "waterline": "2.0.0-rc.9", - "server": "2.0.0-rc.13", - "cli": "2.0.0-rc.12", - "sdk-python": "2.0.0rc8", - "sdk-rust": "2.0.0-rc.7" - }, - "install": { - "workflow": "composer require durable-workflow/workflow:2.0.0-rc.12@RC", - "sdk-php": "composer require durable-workflow/sdk:2.0.0-rc.6@RC", - "waterline": { - "embedded": "composer require durable-workflow/waterline:2.0.0-rc.9@RC durable-workflow/workflow:2.0.0-rc.12@RC durable-workflow/sdk:2.0.0-rc.6@RC", - "service": "docker pull durableworkflow/waterline:2.0.0-rc.9" - }, - "server": "docker pull durableworkflow/server:2.0.0-rc.13", - "cli": "curl -fsSL https://durable-workflow.com/install.sh | VERSION=2.0.0-rc.12 sh", - "sdk-python": "pip install durable-workflow==2.0.0rc8", - "sdk-rust": "cargo add durable-workflow@=2.0.0-rc.7" - }, - "release_plan": { - "tag": "release-plan/current-2-0-20260801", - "sha256": "d4f976b427939b50cd3ac590df3664d76d2b9a0018a86ce2fc16dcd528945ea8" - }, - "sdk_server_qualification": { - "schema": "durable-workflow.sdk-server-qualification/v1", - "source_url": "https://raw.githubusercontent.com/durable-workflow/.github/main/product-train/sdk-server-qualification.json", - "sha256": "edb337e0bb27c800e4688c18ec1b8a50a6b933c0d890166bf065a23a539e51bb" - } - } - }, - "progression": { - "prerelease": "independent_prerelease_components", - "stable": "semantic_versioning", - "compatibility_shims": "forbidden_between_2_0_prereleases" - }, - "historical_prereleases": { - "support": "unsupported", - "guidance": "de_emphasize", - "registry_action": "yank_where_supported" - } -} diff --git a/product-train/payload-codec-contract-schema.json b/product-train/payload-codec-contract-schema.json deleted file mode 100644 index 722c577..0000000 --- a/product-train/payload-codec-contract-schema.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/durable-workflow/.github/main/product-train/payload-codec-contract-schema.json", - "type": "object", - "required": ["schema", "version", "product_train", "http_transport", "public_payload_codecs", "avro_value", "rejection", "deployment_preflight", "release_qualification"], - "additionalProperties": false, - "properties": { - "$schema": {"type": "string"}, - "schema": {"const": "durable-workflow.payload-codec-contract/v1"}, - "version": {"const": 1}, - "product_train": {"const": "2.0.0-rc.30"}, - "http_transport": {"const": "json"}, - "public_payload_codecs": {"const": ["avro"]}, - "avro_value": { - "type": "object", - "required": ["schema", "fingerprint", "framing", "blob_encoding"], - "additionalProperties": false, - "properties": { - "schema": {"const": "durable_workflow.protocol.Value"}, - "fingerprint": {"const": "crc64-avro:e2a33dff55802237"}, - "framing": {"const": "avro-single-object"}, - "blob_encoding": {"const": "base64"} - } - }, - "rejection": { - "type": "object", - "required": ["reason", "rejects"], - "additionalProperties": false, - "properties": { - "reason": {"const": "unsupported_payload_codec"}, - "rejects": {"const": ["json", "unknown", "untagged"]} - } - }, - "deployment_preflight": { - "type": "object", - "required": ["required", "inventory", "frame_validation", "on_failure", "delete_history"], - "additionalProperties": false, - "properties": { - "required": {"const": true}, - "inventory": {"const": "all-active-and-replay-relevant-payload-codec-fields-and-nested-history-envelopes"}, - "frame_validation": {"const": "single-object-magic-and-fixed-schema-fingerprint"}, - "on_failure": {"const": "block-with-drain-export-and-verified-avro-re-encode-remediation"}, - "delete_history": {"const": false} - } - }, - "release_qualification": { - "type": "object", - "required": ["channel", "published_artifacts", "evidence"], - "additionalProperties": false, - "properties": { - "channel": {"const": "prerelease"}, - "published_artifacts": {"const": ["server-image", "composer-sdk", "pypi-sdk", "crates-sdk"]}, - "evidence": {"const": ["cluster-advertises-only-avro", "json-request-rejected", "three-sdk-avro-corpus-roundtrip", "three-sdk-json-envelope-rejection", "managed-history-readable", "no-source-tree-substitutions"]} - } - } - } -} diff --git a/product-train/payload-codec-contract.json b/product-train/payload-codec-contract.json deleted file mode 100644 index b98b733..0000000 --- a/product-train/payload-codec-contract.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "./payload-codec-contract-schema.json", - "schema": "durable-workflow.payload-codec-contract/v1", - "version": 1, - "product_train": "2.0.0-rc.30", - "http_transport": "json", - "public_payload_codecs": ["avro"], - "avro_value": { - "schema": "durable_workflow.protocol.Value", - "fingerprint": "crc64-avro:e2a33dff55802237", - "framing": "avro-single-object", - "blob_encoding": "base64" - }, - "rejection": { - "reason": "unsupported_payload_codec", - "rejects": ["json", "unknown", "untagged"] - }, - "deployment_preflight": { - "required": true, - "inventory": "all-active-and-replay-relevant-payload-codec-fields-and-nested-history-envelopes", - "frame_validation": "single-object-magic-and-fixed-schema-fingerprint", - "on_failure": "block-with-drain-export-and-verified-avro-re-encode-remediation", - "delete_history": false - }, - "release_qualification": { - "channel": "prerelease", - "published_artifacts": ["server-image", "composer-sdk", "pypi-sdk", "crates-sdk"], - "evidence": ["cluster-advertises-only-avro", "json-request-rejected", "three-sdk-avro-corpus-roundtrip", "three-sdk-json-envelope-rejection", "managed-history-readable", "no-source-tree-substitutions"] - } -} diff --git a/product-train/schema.json b/product-train/schema.json deleted file mode 100644 index ae8d491..0000000 --- a/product-train/schema.json +++ /dev/null @@ -1,181 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/product-train/schema.json", - "title": "Durable Workflow supported product train", - "type": "object", - "additionalProperties": false, - "required": [ - "$schema", - "schema", - "current", - "components", - "trains", - "progression", - "historical_prereleases" - ], - "properties": { - "$schema": { - "const": "./schema.json" - }, - "schema": { - "const": "durable-workflow.product-train/v2" - }, - "current": { - "type": "string", - "pattern": "^2\\.0\\.0-(?:beta|rc)\\.[1-9][0-9]*$" - }, - "components": { - "const": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ] - }, - "trains": { - "type": "object", - "minProperties": 1, - "additionalProperties": { - "$ref": "#/$defs/train" - } - }, - "progression": { - "type": "object", - "additionalProperties": false, - "required": ["prerelease", "stable", "compatibility_shims"], - "properties": { - "prerelease": { - "enum": [ - "synchronized_beta_increment", - "synchronized_prerelease_increment", - "independent_prerelease_components" - ] - }, - "stable": {"const": "semantic_versioning"}, - "compatibility_shims": {"const": "forbidden_between_2_0_prereleases"} - } - }, - "historical_prereleases": { - "type": "object", - "additionalProperties": false, - "required": ["support", "guidance", "registry_action"], - "properties": { - "support": {"const": "unsupported"}, - "guidance": {"const": "de_emphasize"}, - "registry_action": {"enum": ["yank_where_supported", "retain_immutable_history"]} - } - } - }, - "$defs": { - "componentVersions": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "sdk-php", - "waterline", - "server", - "cli", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": {"type": "string"}, - "sdk-php": {"type": "string"}, - "waterline": {"type": "string"}, - "server": {"type": "string"}, - "cli": {"type": "string"}, - "sdk-python": {"type": "string"}, - "sdk-rust": {"type": "string"} - } - }, - "installCommands": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "sdk-php", - "waterline", - "server", - "cli", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": {"type": "string", "minLength": 1}, - "sdk-php": {"type": "string", "minLength": 1}, - "waterline": {"$ref": "#/$defs/waterlineInstallCommands"}, - "server": {"type": "string", "minLength": 1}, - "cli": {"type": "string", "minLength": 1}, - "sdk-python": {"type": "string", "minLength": 1}, - "sdk-rust": {"type": "string", "minLength": 1} - } - }, - "waterlineInstallCommands": { - "type": "object", - "additionalProperties": false, - "required": ["embedded", "service"], - "properties": { - "embedded": {"type": "string", "minLength": 1}, - "service": {"type": "string", "minLength": 1} - } - }, - "train": { - "type": "object", - "additionalProperties": false, - "required": [ - "channel", - "status", - "versions", - "registry_versions", - "install", - "release_plan", - "sdk_server_qualification" - ], - "properties": { - "channel": {"enum": ["beta", "rc"]}, - "status": {"enum": ["supported", "historical"]}, - "versions": {"$ref": "#/$defs/componentVersions"}, - "registry_versions": {"$ref": "#/$defs/componentVersions"}, - "install": {"$ref": "#/$defs/installCommands"}, - "release_plan": {"$ref": "#/$defs/releasePlan"}, - "sdk_server_qualification": {"$ref": "#/$defs/sdkServerQualification"} - } - }, - "releasePlan": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-plan/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - } - } - }, - "sdkServerQualification": { - "type": "object", - "additionalProperties": false, - "required": ["schema", "source_url", "sha256"], - "properties": { - "schema": { - "const": "durable-workflow.sdk-server-qualification/v1" - }, - "source_url": { - "const": "https://raw.githubusercontent.com/durable-workflow/.github/main/product-train/sdk-server-qualification.json" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - } - } - } - } -} diff --git a/product-train/sdk-server-qualification-schema.json b/product-train/sdk-server-qualification-schema.json deleted file mode 100644 index 4814852..0000000 --- a/product-train/sdk-server-qualification-schema.json +++ /dev/null @@ -1,208 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/product-train/sdk-server-qualification-schema.json", - "title": "Durable Workflow SDK-to-Server qualification", - "type": "object", - "additionalProperties": false, - "required": [ - "$schema", - "schema", - "release_plan", - "outcome", - "evidence", - "bindings" - ], - "properties": { - "$schema": { - "const": "./sdk-server-qualification-schema.json" - }, - "schema": { - "const": "durable-workflow.sdk-server-qualification/v1" - }, - "release_plan": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-plan/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "sha256": { - "$ref": "#/$defs/sha256" - } - } - }, - "outcome": { - "enum": ["pass", "fail"] - }, - "evidence": { - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "tag", - "source_url", - "sha256", - "outcome", - "github_run" - ], - "properties": { - "schema": { - "const": "durable-workflow.beta-conformance.suite-result/v2" - }, - "tag": { - "type": "string", - "pattern": "^beta-conformance/(?:beta|rc)-[a-z0-9._-]+/[1-9][0-9]*\\.[1-9][0-9]*$" - }, - "source_url": { - "type": "string", - "pattern": "^https://github\\.com/durable-workflow/\\.github/releases/download/beta-conformance/" - }, - "sha256": { - "$ref": "#/$defs/sha256" - }, - "outcome": { - "enum": ["pass", "fail"] - }, - "github_run": { - "$ref": "#/$defs/githubRun" - } - } - }, - "bindings": { - "type": "object", - "additionalProperties": false, - "required": ["sdk-php", "sdk-python", "sdk-rust"], - "properties": { - "sdk-php": { - "$ref": "#/$defs/binding" - }, - "sdk-python": { - "$ref": "#/$defs/binding" - }, - "sdk-rust": { - "$ref": "#/$defs/binding" - } - } - } - }, - "$defs": { - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "artifact": { - "type": "object", - "additionalProperties": false, - "required": ["source", "distribution"], - "properties": { - "source": { - "$ref": "#/$defs/sourceIdentity" - }, - "distribution": { - "$ref": "#/$defs/distributionIdentity" - } - } - }, - "sourceIdentity": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string", - "minLength": 1 - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - }, - "distributionIdentity": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "locator", "artifacts"], - "properties": { - "kind": { - "enum": ["composer", "pypi", "crates.io", "oci"] - }, - "locator": { - "type": "string", - "minLength": 1 - }, - "artifacts": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["name", "sha256"], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "sha256": { - "$ref": "#/$defs/sha256" - } - } - } - } - } - }, - "githubRun": { - "type": "object", - "additionalProperties": false, - "required": ["repository", "run_id", "run_attempt", "evidence_tag"], - "properties": { - "repository": { - "const": "durable-workflow/.github" - }, - "run_id": { - "type": "integer", - "minimum": 1 - }, - "run_attempt": { - "type": "integer", - "minimum": 1 - }, - "evidence_tag": { - "type": "string", - "pattern": "^beta-conformance/" - } - } - }, - "binding": { - "type": "object", - "additionalProperties": false, - "required": [ - "sdk", - "server", - "supported_server_versions", - "outcome", - "experiments" - ], - "properties": { - "sdk": { - "$ref": "#/$defs/artifact" - }, - "server": { - "$ref": "#/$defs/artifact" - }, - "supported_server_versions": { - "type": "string", - "minLength": 1 - }, - "outcome": { - "enum": ["pass", "fail"] - }, - "experiments": { - "type": "array", - "const": ["heartbeats", "replay", "signals-queries"] - } - } - } - } -} diff --git a/product-train/sdk-server-qualification.json b/product-train/sdk-server-qualification.json deleted file mode 100644 index a9f08e6..0000000 --- a/product-train/sdk-server-qualification.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "$schema": "./sdk-server-qualification-schema.json", - "schema": "durable-workflow.sdk-server-qualification/v1", - "release_plan": { - "tag": "release-plan/current-2-0-20260801", - "sha256": "d4f976b427939b50cd3ac590df3664d76d2b9a0018a86ce2fc16dcd528945ea8" - }, - "outcome": "pass", - "evidence": { - "schema": "durable-workflow.beta-conformance.suite-result/v2", - "tag": "beta-conformance/rc-current-2-0-20260801/30708509976.1", - "source_url": "https://github.com/durable-workflow/.github/releases/download/beta-conformance/rc-current-2-0-20260801/30708509976.1/suite-result.json", - "sha256": "ac78fbd51be97afdd1e307b7409d9c23f3d9eefdf7cae7890cae5eff5ba542c5", - "outcome": "pass", - "github_run": { - "repository": "durable-workflow/.github", - "run_id": 30708509976, - "run_attempt": 1, - "evidence_tag": "beta-conformance/rc-current-2-0-20260801/30708509976.1" - } - }, - "bindings": { - "sdk-php": { - "sdk": { - "source": { - "version": "2.0.0-rc.6", - "commit": "31698e4b97fd36e56f05517a0bd56ec7e16a8c05" - }, - "distribution": { - "kind": "composer", - "locator": "composer:durable-workflow/sdk@2.0.0-rc.6", - "artifacts": [ - { - "name": "durable-workflow/sdk", - "sha256": "6aa7cae401fc30f5b81d4fcfbce7f3539c16afea3450fbef6a944d844fccdd81" - } - ] - } - }, - "server": { - "source": { - "version": "2.0.0-rc.13", - "commit": "0f0ce78e8d4eadb91a34a0d09f67e04f5335cdea" - }, - "distribution": { - "kind": "oci", - "locator": "oci:docker.io/durableworkflow/server@2.0.0-rc.13", - "artifacts": [ - { - "name": "manifest", - "sha256": "e7d0402c74af6b918303d519b5034ad404f2cfd8f87c6a30e382039ed960dc4d" - } - ] - } - }, - "supported_server_versions": "2.0.0-rc.13", - "outcome": "pass", - "experiments": [ - "heartbeats", - "replay", - "signals-queries" - ] - }, - "sdk-python": { - "sdk": { - "source": { - "version": "2.0.0-rc.8", - "commit": "6c5ad457d98834f90799db093c8dff515bee710e" - }, - "distribution": { - "kind": "pypi", - "locator": "pypi:durable-workflow@2.0.0rc8", - "artifacts": [ - { - "name": "durable_workflow-2.0.0rc8-py3-none-any.whl", - "sha256": "fd6df596ad15f9ce1376d9475263f56edc66523fbd532c247e22162f6c1aa4b7" - } - ] - } - }, - "server": { - "source": { - "version": "2.0.0-rc.13", - "commit": "0f0ce78e8d4eadb91a34a0d09f67e04f5335cdea" - }, - "distribution": { - "kind": "oci", - "locator": "oci:docker.io/durableworkflow/server@2.0.0-rc.13", - "artifacts": [ - { - "name": "manifest", - "sha256": "e7d0402c74af6b918303d519b5034ad404f2cfd8f87c6a30e382039ed960dc4d" - } - ] - } - }, - "supported_server_versions": "2.0.0-rc.13", - "outcome": "pass", - "experiments": [ - "heartbeats", - "replay", - "signals-queries" - ] - }, - "sdk-rust": { - "sdk": { - "source": { - "version": "2.0.0-rc.7", - "commit": "527dc8581131a40f127d1d8144a3b55d87829ac8" - }, - "distribution": { - "kind": "crates.io", - "locator": "crates.io:durable-workflow@2.0.0-rc.7", - "artifacts": [ - { - "name": "durable-workflow-2.0.0-rc.7.crate", - "sha256": "6c11fb91b4fe939471807188e62bc59c73ebae0fb51fddee73ea0022dd4a5e7e" - } - ] - } - }, - "server": { - "source": { - "version": "2.0.0-rc.13", - "commit": "0f0ce78e8d4eadb91a34a0d09f67e04f5335cdea" - }, - "distribution": { - "kind": "oci", - "locator": "oci:docker.io/durableworkflow/server@2.0.0-rc.13", - "artifacts": [ - { - "name": "manifest", - "sha256": "e7d0402c74af6b918303d519b5034ad404f2cfd8f87c6a30e382039ed960dc4d" - } - ] - } - }, - "supported_server_versions": "2.0.0-rc.13", - "outcome": "pass", - "experiments": [ - "heartbeats", - "replay", - "signals-queries" - ] - } - } -} diff --git a/qualification/README.md b/qualification/README.md index 80435d5..88c50d2 100644 --- a/qualification/README.md +++ b/qualification/README.md @@ -1,25 +1,15 @@ -# Public target qualification +# GitHub Actions policy -`policy.json` is the machine-readable authority for source qualification before -a beta candidate can be initiated. Every listed workflow runs from its product -repository, on its public target branch, and can be rerun directly in GitHub. +`policy.json` is the reviewed organization policy for GitHub Actions references +and workflow trust boundaries. Product repositories load it during pull-request +checks and validate their own checked-out workflow files. -The scheduled qualification audit resolves each target branch to an exact -commit, requires successful check runs for that commit, and queries GitHub's -active branch rules. A target is eligible only when its required check contexts -are enforced with strict status checks. The audit does not use a workspace, -Forgejo, private databases, or repository mutation credentials. +The policy binds allowed Action releases to immutable commits and records the +supported JavaScript runtimes. It also defines the restrictions applied to +privileged workflows, artifact handoffs, token permissions, caches, containers, +and pull-request execution. -The policy also owns the immutable commits and readable release labels for -actions used by public workflows. The audit reads every workflow at the -resolved target commit, rejects mutable references, and checks action manifests -against the supported JavaScript runtime set. Container actions use approved -OCI manifest digests. Action commits, release labels, runtimes, consuming -workflow paths, permissions, and trust-boundary findings are retained in the -audit evidence. - -The same scanner can qualify any checked-out public repository without GitHub -credentials: +Validate any checked-out public repository without GitHub credentials: ```sh python scripts/qualification_policy.py validate \ @@ -27,34 +17,15 @@ python scripts/qualification_policy.py validate \ --workflow-directory ../server/.github/workflows ``` -Every governed product target runs that validator from its protected aggregate -pull-request check. The preflight checks out the candidate without credentials, -loads the current policy from the protected control-plane branch, and makes the -aggregate fail closed when any workflow uses an unknown or mutable Action -reference. Policy validation verifies this wiring as part of the machine-owned -qualification contract, and the target audit rechecks every publicly auditable -target, so adding a dependency pin and adding its policy authority cannot drift -into separate landings. +Every governed product repository runs this validator in GitHub Actions. It +fails when a workflow uses an unknown or mutable Action reference or crosses a +trust boundary that the policy does not allow. -Every workflow must declare read-only or empty top-level token permissions. -Write access is job-local, pull-request jobs cannot reference environments or -secrets, and pull-request caches have separate event namespaces and narrow -dependency paths rather than workspace or home-directory roots. Any job-level -secrets declaration, including reusable-workflow inheritance and explicit -maps, is privileged. Privileged manual-dispatch jobs must fail closed outside -the repository's protected target ref. The scanner rejects -`pull_request_target`, mutable container images, unreviewed `workflow_run` -consumers, and privileged artifact consumers without an exact producer and -digest provenance. Reviewed source-identity and artifact -digest validators must be the first shell execution after their exact -policy-declared sequence of immutable setup or download Action steps, including -each step's complete input map. Each validator's complete shell command and -arguments are policy-declared and matched exactly. Validator jobs must use the -policy-declared GitHub-hosted runner without job containers or services, and the -validators run in the default shell and working directory with only their -policy-declared environment names effective. Product -repositories continue to own and run their qualification, documentation deployment, -publication, and recovery workflows directly. +Workflows must declare read-only or empty top-level token permissions. Write +access is job-local, and pull-request jobs cannot use environments, secrets, or +privileged caches. The scanner rejects `pull_request_target`, mutable container +images, unreviewed `workflow_run` consumers, and privileged artifact consumers +without exact producer and digest provenance. -The documentation check covers builds, links, version routing, and generated -retrieval surfaces. Editorial wording is deliberately outside this policy. +Product repositories own their tests, documentation deployment, publication, +and deployment workflows. Editorial wording is outside this policy. diff --git a/qualification/policy.json b/qualification/policy.json index ee0a07f..b32a894 100644 --- a/qualification/policy.json +++ b/qualification/policy.json @@ -116,119 +116,7 @@ ], "validator_runner": "ubuntu-latest" }, - "privileged_workflow_run_consumers": { - "github-control-plane/beta-continuity-resolution.yml": { - "artifact_digest_validator_command": "python scripts/beta_continuity.py record-resolution \\\n release-plans/continuity-successor-selection.json \\\n --source-sha \"${{ needs.bind.outputs.source_head_sha }}\" \\\n --run-id \"${{ needs.bind.outputs.source_run_id }}\" \\\n --run-attempt \"${{ needs.bind.outputs.source_run_attempt }}\"\n", - "artifact_digest_validator_environment": [ - "GITHUB_TOKEN" - ], - "artifact_digest_validator_preceding_steps": [ - { - "uses": "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803", - "with": { - "fetch-depth": "0", - "ref": "${{ needs.bind.outputs.source_head_sha }}" - } - }, - { - "uses": "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1", - "with": { - "python-version": "3.13" - } - } - ], - "event": "push", - "identity_validator_command": "python scripts/beta_continuity.py resolution-source \\\n --expected-run-id \"$REQUESTED_RUN_ID\" \\\n --expected-run-attempt \"$REQUESTED_RUN_ATTEMPT\" \\\n --github-output \"$GITHUB_OUTPUT\"\n", - "identity_validator_environment": [ - "GH_TOKEN", - "REQUESTED_RUN_ATTEMPT", - "REQUESTED_RUN_ID" - ], - "identity_validator_preceding_steps": [ - { - "uses": "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803", - "with": { - "persist-credentials": "false" - } - }, - { - "uses": "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1", - "with": { - "python-version": "3.13" - } - } - ], - "privileged_job_condition": "github.ref == 'refs/heads/main' && github.sha == needs.bind.outputs.source_head_sha", - "protected_ref": "main", - "repository": "durable-workflow/.github", - "validator_runner": "ubuntu-latest", - "workflow": "Beta candidate" - }, - "github-control-plane/beta-conformance-retention.yml": { - "artifact_digest_validator_command": "python scripts/beta_conformance.py aggregate \\\n aggregate-input/execution-plan.json \\\n evidence \\\n suite-result.json \\\n release-assets \\\n --contract beta-conformance/contract.json \\\n --run-id \"${{ needs.bind.outputs.source_run_id }}\" \\\n --run-attempt \"${{ needs.bind.outputs.source_run_attempt }}\" \\\n --generated-at \"${{ needs.bind.outputs.source_completed_at }}\" \\\n --source-candidate \"${{ needs.bind.outputs.source_candidate }}\" \\\n --source-head-sha \"${{ needs.bind.outputs.source_head_sha }}\" \\\n --github-output \"$GITHUB_OUTPUT\"\n", - "artifact_digest_validator_environment": [], - "artifact_digest_validator_preceding_steps": [ - { - "uses": "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803", - "with": { - "persist-credentials": "false" - } - }, - { - "uses": "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1", - "with": { - "python-version": "3.13" - } - }, - { - "uses": "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c", - "with": { - "github-token": "${{ github.token }}", - "name": "beta-conformance-plan-${{ needs.bind.outputs.source_run_id }}", - "path": "aggregate-input", - "run-id": "${{ needs.bind.outputs.source_run_id }}", - "digest-mismatch": "error" - } - }, - { - "uses": "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c", - "with": { - "github-token": "${{ github.token }}", - "path": "evidence", - "pattern": "beta-conformance-*-${{ needs.bind.outputs.source_run_id }}-${{ needs.bind.outputs.source_run_attempt }}", - "run-id": "${{ needs.bind.outputs.source_run_id }}", - "digest-mismatch": "error" - } - } - ], - "event": "workflow_dispatch", - "identity_validator_command": "python scripts/beta_conformance.py retention-source \\\n --expected-run-id \"$REQUESTED_RUN_ID\" \\\n --expected-run-attempt \"$REQUESTED_RUN_ATTEMPT\" \\\n --github-output \"$GITHUB_OUTPUT\"\n", - "identity_validator_environment": [ - "GH_TOKEN", - "REQUESTED_RUN_ATTEMPT", - "REQUESTED_RUN_ID" - ], - "identity_validator_preceding_steps": [ - { - "uses": "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803", - "with": { - "persist-credentials": "false" - } - }, - { - "uses": "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1", - "with": { - "python-version": "3.13" - } - } - ], - "privileged_job_condition": "github.ref == 'refs/heads/main'", - "protected_ref": "main", - "repository": "durable-workflow/.github", - "validator_runner": "ubuntu-latest", - "workflow": "Beta conformance" - } - }, + "privileged_workflow_run_consumers": {}, "pull_request_target_exceptions": [] }, "targets": { @@ -287,8 +175,8 @@ "workflows": [ { "matrix_independent": false, - "path": "source-qualification.yml", - "required_check": "Control-plane source qualification" + "path": "ci.yml", + "required_check": "Shared contract tests" } ] }, diff --git a/release-plans/README.md b/release-plans/README.md deleted file mode 100644 index 5f916e5..0000000 --- a/release-plans/README.md +++ /dev/null @@ -1,161 +0,0 @@ -# Release plans - -A release plan is the immutable, pre-publication identity for one coherent -seven-component release. The `Release plan` workflow validates a supplied JSON -document against [`schema.json`](schema.json), proves its public branch and tag -preconditions, and records it at `release-plan/` in Git and as a GitHub -Release asset. - -[`current.json`](current.json) binds the supported already-published train to -the same plan contract. The protected `Current release plan` workflow verifies -all seven public source and distribution identities before it records that -plan and its terminal completion record. A release-candidate plan binds the -exact immutable candidate record that verified its public artifacts. Repeating -the workflow reads and validates an existing immutable completion instead of -regenerating evidence; it does not move component tags or republish packages, -crates, archives, or images. - -Before recording the plan, the workflow also prepares exact dated release -notes for every component. The resulting record, validated by -[`preparation-schema.json`](preparation-schema.json), is stored in the same -immutable Git record and mirrored on the same GitHub Release. Each entry binds -the planned version, the original source commit, the note text and digest, and -the immutable public source from which the text was derived. Repositories with -a maintained `CHANGELOG.md` use its `Unreleased` section; other release -surfaces use the planned source commit message. Product contributors therefore -keep describing unreleased changes without selecting the automatically planned -patch version. - -The preparation record is structured release-note authority; it does not -create a component commit or update a component default branch. Replaying the -same plan recovers the first immutable preparation record even if the retry is -on a later date. Component recovery and publication can compare the plan tag, -plan digest, component version, source commit, note digest, and source evidence -before creating a version tag or publishing. Preparation consequently cannot -look like new component source, recursively allocate another patch, or resume -with notes prepared for another version. - -Plans contain only public source identities, intended versions, their release -channel, and immutable authorization references. They never contain registry -credentials. Each component repository discovers these records on its own -schedule and uses its own GitHub token and publication environment to resume -its release. - -The `durable-workflow.release-plan/v2` contract selects dual-distribution -candidate verification and conformance for Waterline. New plans must use v2. -The readers retain v1 support only for the exact canonical plan documents -recorded before the Waterline service image became a required distribution. - -The current aggregate prerelease source selection is prepared in -[`current-source-preparation.json`](current-source-preparation.json). It binds -the exact seven source commits and the digests of their Unreleased changelogs -or source commit messages before protected aggregate publication. A current -plan must match that source tuple and note authority exactly. The release-plan -action then re-reads the public sources and creates the dated, immutable -preparation record; source preparation does not bypass or impersonate the -protected publication decision. - -The first release-candidate source tuple is prepared separately in -[`first-release-candidate-source-preparation.json`](first-release-candidate-source-preparation.json), -and [`first-release-candidate.json`](first-release-candidate.json) retains the -immutable qualification of the beta train from which that historical plan -advanced. Landing source authorities triggers the protected release-plan -workflow. A release-candidate plan becomes the recoverable publication -identity only after every artifact and completion record verifies. -Release-candidate authorization does not authorize a stable 2.0 release or -alter the stable 1.x documentation default. - -For the workspace-unavailable continuity drill, scheduled component recovery -recognizes the public `beta-continuity//accepted` record and waits until -the matching `resumed` record exists. An explicit recovery dispatch naming the -exact plan remains available in every component repository and bypasses that -scheduled pause. The controller can therefore publish one component, retain a -provably partial interruption, and later resume the same plan, while each -repository keeps an independent exact-plan recovery path. - -The fixed dependency order is enforced through public artifact verification: - -| Tier | Components | Required public predecessors | -| --- | --- | --- | -| 0 | Workflow, PHP SDK | None | -| 1 | Waterline, server | Workflow; Waterline also requires the PHP SDK | -| 2 | CLI, Python SDK, Rust SDK | server | - -Historical alpha plans retain their recorded component identities. Every new -beta plan must use the exact seven-component tuple selected by the -[product-train authority](../product-train/README.md); a user-facing beta -increment advances Workflow, Waterline, server, CLI, and all three SDKs -together. A beta plan additionally names an immutable `beta-authorization/*` -record whose candidate and seven-component tuple match the plan exactly. The protected -[`Beta authorization`](../beta-authorization/README.md) action is the -repository-owned producer and recovery path for that record. An alpha plan -cannot name or satisfy that gate. - -Before a plan is recorded, GitHub must report `v2` as the effective default -branch for Workflow and Waterline and `main` for the other five repositories. -This is what makes their scheduled recovery entry points authoritative. - -Only one plan may be recoverable and incomplete at a time. Recording a -different plan fails closed until every earlier `release-plan/*` Git tag has -either its matching immutable `release-candidate//*` completion record -or a protected `release-plan-failure/*` terminal record. Ordinary interrupted -plans remain blocking and continue through their repository recovery actions. -The only continuity-drill exception is an exact successor whose immutable -`beta-continuity//accepted` record identifies the prior diagnostic -interruption by tag, commit, evidence digest, and plan digest. Preflight reads -both immutable records and verifies the prior plan-record commit before it -treats that invalid interruption as superseded; unrelated incomplete plans -remain blocking. - -The `Release plan supersession` action is the narrow exception for allocations -that cannot be published without mutating public history. This includes a -version already public from a different source commit and an intended source -whose authoritative package manifest declares a different version. Python -`pyproject.toml` project metadata and Rust `Cargo.toml` package metadata are -verified before a plan is recorded and again during supersession. The action -runs through the `release-plan-supersession` environment and requires that the -live environment allow only a custom `main` branch policy. It verifies public -release and distribution identities for existing-version conflicts and -immutable source manifest identities for manifest conflicts. It also verifies -the dispatched workflow run and its approved environment review through GitHub, -retaining both the dispatching actor and approving user identities in the -terminal record. All mutable conflict, successor-version, environment-policy, -and approval evidence is rechecked immediately before the immutable record is -published. Once published, the Git record is the durable authority, so later -policy or reviewer changes cannot invalidate the bound successor. - -The immutable record retains every independently proven conflict and the exact -successor document as `successor-release-plan.json`. The successor must keep -every unaffected component unchanged and resolve every affected allocation. An -existing-version conflict retains the intended source commit and allocates the -immediate next version. A source-manifest conflict retains the intended version, -replaces the incompatible source commit, and proves the successor manifest -declares that version. When the incompatible planned source already occupies its -immutable version tag but has no GitHub Release or public distribution, the -successor allocates the immediate next version, replaces the incompatible -commit, and proves its manifest declares the new version. Repeating the action -compares the existing record; it cannot replace its conflicts, approval -evidence, or successor. - -The `Release plan observer` workflow derives progress from the real public -surfaces and retains `release-state.json` on the plan's GitHub Release. Once all -seven components are public, it invokes the existing candidate verifier and -records `release-candidate//` with the channel in the immutable -record itself. This prevents an alpha recovery proof from becoming beta -authorization. A -rerun never needs an Actions artifact or a local checkout from an earlier run. -When an observer encounters a terminal record, `release-state.json` identifies -all conflicting components and their evidence and directs recovery to the exact -stored successor plan rather than retrying an unrecoverable publication. - -Scheduled recovery discovers the complete `release-plan/*` Git tag registry, -not the mutable ordering of GitHub Releases. It validates every plan together -with its completion, terminal-failure, and continuity lifecycle records, then -orders plans by the committer time embedded in each immutable plan-record -commit. Every plan older than the selected record must be completed or -superseded. The newest record may be completed (a verification-only scheduled no-op), -interrupted, or currently actionable. Duplicate record times, malformed or -conflicting lifecycle records, an older nonterminal plan, and a superseded -record without its exact successor all fail closed. An explicit manual -recovery tag continues to select only that exact immutable plan and its -matching Release mirrors. diff --git a/release-plans/candidate-schema.json b/release-plans/candidate-schema.json deleted file mode 100644 index 26f34ea..0000000 --- a/release-plans/candidate-schema.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/release-plans/candidate-schema.json", - "title": "Durable Workflow completed release candidate", - "type": "object", - "additionalProperties": false, - "required": ["schema", "candidate", "channel", "release_plan", "components"], - "properties": { - "schema": { - "const": "durable-workflow.release-candidate/v1" - }, - "candidate": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9._-]{0,55}$" - }, - "channel": { - "enum": ["alpha", "beta", "rc"] - }, - "release_plan": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-plan/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - } - } - }, - "release_preparation_sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"], - "properties": { - "workflow": { "$ref": "#/$defs/component" }, - "waterline": { "$ref": "#/$defs/component" }, - "server": { "$ref": "#/$defs/component" }, - "cli": { "$ref": "#/$defs/component" }, - "sdk-php": { "$ref": "#/$defs/component" }, - "sdk-python": { "$ref": "#/$defs/component" }, - "sdk-rust": { "$ref": "#/$defs/component" } - } - } - }, - "$defs": { - "component": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string", - "pattern": "^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?![\\s\\S])" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - } - } -} diff --git a/release-plans/continuity-resolution-schema.json b/release-plans/continuity-resolution-schema.json deleted file mode 100644 index 7a56f05..0000000 --- a/release-plans/continuity-resolution-schema.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "$id": "https://github.com/durable-workflow/.github/blob/main/release-plans/continuity-resolution-schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "acceptance_identity": { - "$ref": "#/$defs/identity" - }, - "identity": { - "additionalProperties": false, - "properties": { - "commit": { - "pattern": "^[0-9a-f]{40}$", - "type": "string" - }, - "sha256": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - }, - "tag": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "commit", - "sha256", - "tag" - ], - "type": "object" - }, - "qualification": { - "additionalProperties": false, - "properties": { - "conclusion": { - "const": "success" - }, - "event": { - "const": "push" - }, - "head_branch": { - "const": "main" - }, - "head_sha": { - "pattern": "^[0-9a-f]{40}$", - "type": "string" - }, - "repository": { - "const": "durable-workflow/.github" - }, - "run_attempt": { - "minimum": 1, - "type": "integer" - }, - "run_id": { - "minimum": 1, - "type": "integer" - }, - "status": { - "const": "completed" - }, - "workflow": { - "const": ".github/workflows/beta-candidate.yml" - } - }, - "required": [ - "conclusion", - "event", - "head_branch", - "head_sha", - "repository", - "run_attempt", - "run_id", - "status", - "workflow" - ], - "type": "object" - }, - "successor_claim": { - "additionalProperties": false, - "properties": { - "acceptance": { - "$ref": "#/$defs/acceptance_identity" - }, - "plan": { - "$ref": "#/$defs/identity" - } - }, - "required": [ - "acceptance", - "plan" - ], - "type": "object" - } - }, - "additionalProperties": false, - "properties": { - "interruption": { - "additionalProperties": false, - "properties": { - "evidence": { - "$ref": "#/$defs/identity" - }, - "plan": { - "$ref": "#/$defs/identity" - } - }, - "required": [ - "evidence", - "plan" - ], - "type": "object" - }, - "schema": { - "const": "durable-workflow.release-plan-continuity-resolution/v2" - }, - "qualification": { - "$ref": "#/$defs/qualification" - }, - "selected_successor": { - "$ref": "#/$defs/identity" - }, - "successor_claims": { - "items": { - "$ref": "#/$defs/successor_claim" - }, - "minItems": 2, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "interruption", - "qualification", - "schema", - "selected_successor", - "successor_claims" - ], - "title": "Durable Workflow release-plan continuity successor resolution", - "type": "object" -} diff --git a/release-plans/continuity-successor-selection.json b/release-plans/continuity-successor-selection.json deleted file mode 100644 index 18f5ef9..0000000 --- a/release-plans/continuity-successor-selection.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "interruption": { - "evidence": { - "commit": "d327e15fa3732aebae9a584c61f1541ec53d3693", - "sha256": "435c2456c36f534b1755d00bfb280c9ec07e20ee2d2a7f8be9748877b8b99132", - "tag": "beta-continuity/workspace-unavailable-0b191da0d140/interrupted" - }, - "plan": { - "commit": "9ba2f315431dae56a52ec1a5ba5dd29de898f5e4", - "sha256": "80e88698fa37b6d738d111dd2be3e3c145607973f8147c54cc25e5d91d415b17", - "tag": "release-plan/workspace-unavailable-0b191da0d140" - } - }, - "schema": "durable-workflow.release-plan-continuity-resolution-selection/v1", - "selected_successor": { - "commit": "fa40b58d3ead510b9e730a008ac0b9a1bf59c381", - "sha256": "498804a2c7fd5b0e34f93ef080bea3073bc98e420e8bf84a98ca4cdb94729973", - "tag": "release-plan/workspace-unavailable-release-pages-24ea45b0acb8" - }, - "successor_claims": [ - { - "acceptance": { - "commit": "f488d18850d46bc15f63224b759166be9faed57b", - "sha256": "1682541c56582c651fbcec520c4f9f901be54462547782aa427e758a73d5c2f7", - "tag": "beta-continuity/workspace-unavailable-recovery-f46818553161/accepted" - }, - "plan": { - "commit": "820d0e0f96e8ef930ceb5c1e727bfc60eb4a291d", - "sha256": "295a3f654716ea8cd8dc693c1cd15a4b487737e5f01184bad7363fbde6717c40", - "tag": "release-plan/workspace-unavailable-recovery-f46818553161" - } - }, - { - "acceptance": { - "commit": "b466de7c88a98b93335b2aec326a742e193e2a92", - "sha256": "d666b5a82611f75f00fcbf2190abd4b57137e77a72c5c7f4f26e4068acb19b84", - "tag": "beta-continuity/workspace-unavailable-release-pages-24ea45b0acb8/accepted" - }, - "plan": { - "commit": "fa40b58d3ead510b9e730a008ac0b9a1bf59c381", - "sha256": "498804a2c7fd5b0e34f93ef080bea3073bc98e420e8bf84a98ca4cdb94729973", - "tag": "release-plan/workspace-unavailable-release-pages-24ea45b0acb8" - } - } - ] -} diff --git a/release-plans/current-source-preparation.json b/release-plans/current-source-preparation.json deleted file mode 100644 index a7684e5..0000000 --- a/release-plans/current-source-preparation.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "./source-preparation-schema.json", - "schema": "durable-workflow.release-source-preparation/v1", - "plan": "current-2-0-20260801", - "channel": "rc", - "train": "2.0.0-rc.13", - "status": "source-prepared", - "components": { - "workflow": { - "version": "2.0.0-rc.12", - "commit": "9416c2a4d3fe71d85f77c2465ed8337c833a79ee", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "41f156a13f9483a312179fc57422a25302227e44eaac22da7011f77be558d0c5" - } - }, - "waterline": { - "version": "2.0.0-rc.9", - "commit": "747ba7712a5e1c6ebee196640d76fc11f77fa67d", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "a977ca7a24386c66dec39377d0004f562573fc27f04801e609450f78c7d4117d" - } - }, - "server": { - "version": "2.0.0-rc.13", - "commit": "0f0ce78e8d4eadb91a34a0d09f67e04f5335cdea", - "release_notes": { - "kind": "source-commit-message", - "sha256": "f5d066dfcd29a1cbea01efc403a426a9ec969b193d5ec3f07f1a3dcb731fdff7" - } - }, - "cli": { - "version": "2.0.0-rc.12", - "commit": "e4413464908e5d42ae13071e2c6e6c280da06a1a", - "release_notes": { - "kind": "source-commit-message", - "sha256": "760b2b13ac40e2d80cf9f2054e1a5b26f05e47d42066046146d9ac496ec9364b" - } - }, - "sdk-php": { - "version": "2.0.0-rc.6", - "commit": "31698e4b97fd36e56f05517a0bd56ec7e16a8c05", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "6b8ce586eaa0129b135d4c836b5bbfec82a5177bebc8106d8d136c1064d27401" - } - }, - "sdk-python": { - "version": "2.0.0-rc.8", - "commit": "6c5ad457d98834f90799db093c8dff515bee710e", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "4689b2f61c08ab9ac30ef7d2aed192bfd5f6ba998ca3aa86b4beb982ad15fb65" - } - }, - "sdk-rust": { - "version": "2.0.0-rc.7", - "commit": "527dc8581131a40f127d1d8144a3b55d87829ac8", - "release_notes": { - "kind": "source-commit-message", - "sha256": "4c154824f6a01ded7867276cfadc7ba987f790f25e81cf9fcb11fc954476a3bf" - } - } - }, - "authorization": { - "state": "required-after-source-landing", - "producer": "protected-release-candidate-authorization" - } -} diff --git a/release-plans/current.json b/release-plans/current.json deleted file mode 100644 index 4d69c11..0000000 --- a/release-plans/current.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "beta_authorization": null, - "channel": "rc", - "components": { - "cli": { - "commit": "e4413464908e5d42ae13071e2c6e6c280da06a1a", - "version": "2.0.0-rc.12" - }, - "sdk-php": { - "commit": "31698e4b97fd36e56f05517a0bd56ec7e16a8c05", - "version": "2.0.0-rc.6" - }, - "sdk-python": { - "commit": "6c5ad457d98834f90799db093c8dff515bee710e", - "version": "2.0.0-rc.8" - }, - "sdk-rust": { - "commit": "527dc8581131a40f127d1d8144a3b55d87829ac8", - "version": "2.0.0-rc.7" - }, - "server": { - "commit": "0f0ce78e8d4eadb91a34a0d09f67e04f5335cdea", - "version": "2.0.0-rc.13" - }, - "waterline": { - "commit": "747ba7712a5e1c6ebee196640d76fc11f77fa67d", - "version": "2.0.0-rc.9" - }, - "workflow": { - "commit": "9416c2a4d3fe71d85f77c2465ed8337c833a79ee", - "version": "2.0.0-rc.12" - } - }, - "foundation": { - "commit": "40d39e180e914406dcca2d64892180a0483ee2a2", - "tag": "beta-candidate/rc-current-2-0-20260801" - }, - "plan": "current-2-0-20260801", - "schema": "durable-workflow.release-plan/v2" -} diff --git a/release-plans/failure-schema.json b/release-plans/failure-schema.json deleted file mode 100644 index 7e09f4d..0000000 --- a/release-plans/failure-schema.json +++ /dev/null @@ -1,323 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/release-plans/failure-schema.json", - "title": "Durable Workflow terminal release plan failure", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "outcome", - "failed_plan", - "conflicts", - "successor_plan", - "authorization" - ], - "properties": { - "schema": { - "const": "durable-workflow.release-plan-failure/v1" - }, - "outcome": { - "const": "terminal-failure" - }, - "failed_plan": { - "$ref": "#/$defs/planRecord" - }, - "conflicts": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "oneOf": [ - {"$ref": "#/$defs/publishedConflict"}, - {"$ref": "#/$defs/sourceManifestConflict"}, - {"$ref": "#/$defs/occupiedSourceManifestConflict"} - ] - } - }, - "successor_plan": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-plan/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "sha256": { - "$ref": "#/$defs/sha256" - } - } - }, - "authorization": { - "type": "object", - "additionalProperties": false, - "required": [ - "actor", - "environment", - "environment_approval", - "environment_protection", - "repository", - "run_attempt", - "run_id", - "run_url", - "workflow_commit", - "workflow_ref" - ], - "properties": { - "actor": {"type": "string", "pattern": "^[A-Za-z0-9-]{1,39}$"}, - "environment": {"const": "release-plan-supersession"}, - "environment_approval": { - "type": "object", - "additionalProperties": false, - "required": ["comment", "environments", "run_attempt", "run_id", "state", "user"], - "properties": { - "comment": {"type": "string"}, - "environments": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["html_url", "id", "name", "node_id", "url"], - "properties": { - "html_url": {"type": "string", "format": "uri"}, - "id": {"type": "integer", "minimum": 1}, - "name": {"const": "release-plan-supersession"}, - "node_id": {"type": "string", "minLength": 1}, - "url": {"type": "string", "format": "uri"} - } - } - }, - "run_attempt": {"type": "integer", "minimum": 1}, - "run_id": {"type": "integer", "minimum": 1}, - "state": {"const": "approved"}, - "user": { - "type": "object", - "additionalProperties": false, - "required": ["html_url", "id", "login", "node_id", "url"], - "properties": { - "html_url": {"type": "string", "format": "uri"}, - "id": {"type": "integer", "minimum": 1}, - "login": {"type": "string", "pattern": "^[A-Za-z0-9-]{1,39}$"}, - "node_id": {"type": "string", "minLength": 1}, - "url": {"type": "string", "format": "uri"} - } - } - } - }, - "environment_protection": { - "type": "object", - "additionalProperties": false, - "required": [ - "custom_branch_policies", - "deployment_branch_policy", - "environment_id", - "environment_url", - "required_reviewer_rule_ids" - ], - "properties": { - "custom_branch_policies": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["id", "name"], - "properties": { - "id": {"type": "integer", "minimum": 1}, - "name": {"const": "main"} - } - } - }, - "deployment_branch_policy": { - "type": "object", - "additionalProperties": false, - "required": ["custom_branch_policies", "protected_branches"], - "properties": { - "custom_branch_policies": {"const": true}, - "protected_branches": {"const": false} - } - }, - "environment_id": {"type": "integer", "minimum": 1}, - "environment_url": {"type": "string", "format": "uri"}, - "required_reviewer_rule_ids": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": {"type": "integer", "minimum": 1} - } - } - }, - "repository": {"const": "durable-workflow/.github"}, - "run_attempt": {"type": "integer", "minimum": 1}, - "run_id": {"type": "integer", "minimum": 1}, - "run_url": {"type": "string", "format": "uri"}, - "workflow_commit": {"$ref": "#/$defs/commit"}, - "workflow_ref": { - "const": "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - } - } - } - }, - "$defs": { - "component": { - "enum": ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"] - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "distribution": { - "type": "object", - "required": ["kind"], - "properties": { - "kind": {"enum": ["composer", "oci", "github-release", "pypi", "crates.io"]} - } - }, - "publishedConflict": { - "type": "object", - "additionalProperties": false, - "required": [ - "component", - "version", - "planned_commit", - "observed_commit", - "reason", - "github_release", - "distribution" - ], - "properties": { - "component": {"$ref": "#/$defs/component"}, - "version": {"$ref": "#/$defs/version"}, - "planned_commit": {"$ref": "#/$defs/commit"}, - "observed_commit": {"$ref": "#/$defs/commit"}, - "reason": {"const": "published-version-source-conflict"}, - "github_release": { - "type": "object", - "additionalProperties": false, - "required": ["id", "url"], - "properties": { - "id": {"type": "integer", "minimum": 1}, - "url": {"type": "string", "format": "uri"} - } - }, - "distribution": {"$ref": "#/$defs/distribution"} - } - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "sourceManifest": { - "type": "object", - "additionalProperties": false, - "required": ["declared_version", "package", "path", "sha256", "source_commit", "url"], - "properties": { - "declared_version": {"$ref": "#/$defs/version"}, - "package": {"type": "string", "minLength": 1}, - "path": {"type": "string", "minLength": 1}, - "sha256": {"$ref": "#/$defs/sha256"}, - "source_commit": {"$ref": "#/$defs/commit"}, - "url": {"type": "string", "format": "uri"} - } - }, - "sourceManifestConflict": { - "type": "object", - "additionalProperties": false, - "required": [ - "component", - "version", - "planned_commit", - "reason", - "source_manifest", - "successor_source_manifest" - ], - "properties": { - "component": {"$ref": "#/$defs/component"}, - "version": {"$ref": "#/$defs/version"}, - "planned_commit": {"$ref": "#/$defs/commit"}, - "reason": {"const": "source-manifest-version-conflict"}, - "source_manifest": {"$ref": "#/$defs/sourceManifest"}, - "successor_source_manifest": {"$ref": "#/$defs/sourceManifest"} - } - }, - "sourceTag": { - "type": "object", - "additionalProperties": false, - "required": ["commit", "repository", "tag", "tag_object", "url"], - "properties": { - "commit": {"$ref": "#/$defs/commit"}, - "repository": {"type": "string", "minLength": 1}, - "tag": {"$ref": "#/$defs/version"}, - "tag_object": {"$ref": "#/$defs/commit"}, - "url": {"type": "string", "format": "uri"} - } - }, - "absentGitHubRelease": { - "type": "object", - "additionalProperties": false, - "required": ["api_url", "status", "url"], - "properties": { - "api_url": {"type": "string", "format": "uri"}, - "status": {"const": "absent"}, - "url": {"type": "string", "format": "uri"} - } - }, - "absentDistribution": { - "type": "object", - "additionalProperties": false, - "required": ["api_url", "kind", "status", "url"], - "properties": { - "api_url": {"type": "string", "format": "uri"}, - "kind": {"enum": ["pypi", "crates.io"]}, - "status": {"const": "absent"}, - "url": {"type": "string", "format": "uri"} - } - }, - "occupiedSourceManifestConflict": { - "type": "object", - "additionalProperties": false, - "required": [ - "component", - "version", - "planned_commit", - "reason", - "source_manifest", - "source_tag", - "github_release", - "distribution", - "successor_source_manifest" - ], - "properties": { - "component": {"$ref": "#/$defs/component"}, - "version": {"$ref": "#/$defs/version"}, - "planned_commit": {"$ref": "#/$defs/commit"}, - "reason": {"const": "occupied-source-manifest-version-conflict"}, - "source_manifest": {"$ref": "#/$defs/sourceManifest"}, - "source_tag": {"$ref": "#/$defs/sourceTag"}, - "github_release": {"$ref": "#/$defs/absentGitHubRelease"}, - "distribution": {"$ref": "#/$defs/absentDistribution"}, - "successor_source_manifest": {"$ref": "#/$defs/sourceManifest"} - } - }, - "version": { - "type": "string", - "pattern": "^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?![\\s\\S])" - }, - "planRecord": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-plan/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "commit": {"$ref": "#/$defs/commit"}, - "sha256": {"$ref": "#/$defs/sha256"} - } - } - } -} diff --git a/release-plans/first-release-candidate-source-preparation.json b/release-plans/first-release-candidate-source-preparation.json deleted file mode 100644 index 839b5fb..0000000 --- a/release-plans/first-release-candidate-source-preparation.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "./source-preparation-schema.json", - "schema": "durable-workflow.release-source-preparation/v1", - "plan": "coherent-2-0-rc-1", - "channel": "rc", - "train": "2.0.0-rc.1", - "status": "source-prepared", - "components": { - "workflow": { - "version": "2.0.0-rc.1", - "commit": "864cd6f2e11a60ddbd221548019df8ef0cd8f812", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "70b2d667e532b144094bb12d75e073b449d4319df3e75de9bda028d1230eb222" - } - }, - "waterline": { - "version": "2.0.0-rc.1", - "commit": "16c2fde277c19199ac3465fa79555aab97cd4115", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "e9a945668317baeaddc95e0d2f4d19b286a680be219af2ad14aa8a2e13e01431" - } - }, - "server": { - "version": "2.0.0-rc.2", - "commit": "2627e13c0a9c52572b6732f9696f5f78681eca7b", - "release_notes": { - "kind": "source-commit-message", - "sha256": "307fc912774b10675de920f944bf23107ebe12fd70465fd74615a45ebd13e647" - } - }, - "cli": { - "version": "2.0.0-rc.1", - "commit": "f06ed54d3adefa686d8b5575c989268fc15f8529", - "release_notes": { - "kind": "source-commit-message", - "sha256": "2ce32b002fa9ec00cf6712c6b76936969be340a981e77abcc636fabee88ef4df" - } - }, - "sdk-php": { - "version": "2.0.0-rc.1", - "commit": "5136659df919f80bcac788f7148135fdb373ebbd", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "6de8f3f9e53bed94475aa17a524ccedd9c77870a97e34ba6ea6b72e28c37bf68" - } - }, - "sdk-python": { - "version": "2.0.0-rc.1", - "commit": "e8f21a0a886d055f662a1c893467d1edd0b50b1c", - "release_notes": { - "kind": "changelog-unreleased", - "path": "CHANGELOG.md", - "sha256": "f853d32e66cb4bebc08a39c7b8c44848a33f757c42f571301c3de03ef092abda" - } - }, - "sdk-rust": { - "version": "2.0.0-rc.1", - "commit": "0c69fed9a17e50c041e27d427d6dd043c014df84", - "release_notes": { - "kind": "source-commit-message", - "sha256": "2ce32b002fa9ec00cf6712c6b76936969be340a981e77abcc636fabee88ef4df" - } - } - }, - "authorization": { - "state": "required-after-source-landing", - "producer": "protected-release-candidate-authorization" - } -} diff --git a/release-plans/first-release-candidate.json b/release-plans/first-release-candidate.json deleted file mode 100644 index c2ccec8..0000000 --- a/release-plans/first-release-candidate.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "beta_authorization": { - "commit": "73985d0c906d96e6edce6915ebe8000bbc19896e", - "tag": "beta-authorization/coherent-2-0-beta-21" - }, - "channel": "rc", - "components": { - "cli": { - "commit": "f06ed54d3adefa686d8b5575c989268fc15f8529", - "version": "2.0.0-rc.1" - }, - "sdk-php": { - "commit": "5136659df919f80bcac788f7148135fdb373ebbd", - "version": "2.0.0-rc.1" - }, - "sdk-python": { - "commit": "e8f21a0a886d055f662a1c893467d1edd0b50b1c", - "version": "2.0.0-rc.1" - }, - "sdk-rust": { - "commit": "0c69fed9a17e50c041e27d427d6dd043c014df84", - "version": "2.0.0-rc.1" - }, - "server": { - "commit": "2627e13c0a9c52572b6732f9696f5f78681eca7b", - "version": "2.0.0-rc.2" - }, - "waterline": { - "commit": "16c2fde277c19199ac3465fa79555aab97cd4115", - "version": "2.0.0-rc.1" - }, - "workflow": { - "commit": "864cd6f2e11a60ddbd221548019df8ef0cd8f812", - "version": "2.0.0-rc.1" - } - }, - "foundation": { - "commit": "4995052410bd4301c5796ffba54e0b6d2f490ed1", - "tag": "beta-candidate/beta-continuity-foundation" - }, - "plan": "coherent-2-0-rc-1", - "schema": "durable-workflow.release-plan/v2" -} diff --git a/release-plans/preparation-schema.json b/release-plans/preparation-schema.json deleted file mode 100644 index cfeae33..0000000 --- a/release-plans/preparation-schema.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/release-plans/preparation-schema.json", - "title": "Durable Workflow release preparation", - "type": "object", - "additionalProperties": false, - "required": ["schema", "release_plan", "components"], - "properties": { - "schema": { - "const": "durable-workflow.release-preparation/v1" - }, - "release_plan": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-plan/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - } - } - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": { "$ref": "#/$defs/component" }, - "waterline": { "$ref": "#/$defs/component" }, - "server": { "$ref": "#/$defs/component" }, - "cli": { "$ref": "#/$defs/component" }, - "sdk-php": { "$ref": "#/$defs/component" }, - "sdk-python": { "$ref": "#/$defs/component" }, - "sdk-rust": { "$ref": "#/$defs/component" } - } - } - }, - "$defs": { - "component": { - "type": "object", - "additionalProperties": false, - "required": ["version", "source_commit", "release_notes"], - "properties": { - "version": { - "type": "string", - "pattern": "^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?![\\s\\S])" - }, - "source_commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "release_notes": { - "type": "object", - "additionalProperties": false, - "required": [ - "format", - "heading", - "markdown", - "release_date", - "sha256", - "source" - ], - "properties": { - "format": { "const": "text/markdown" }, - "heading": { "type": "string", "minLength": 1 }, - "markdown": { "type": "string", "minLength": 1 }, - "release_date": { - "type": "string", - "format": "date" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "source": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "sha256", "url"], - "properties": { - "kind": { - "enum": ["changelog-unreleased", "source-commit-message"] - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://github\\.com/durable-workflow/" - } - } - } - } - } - } - } - } -} diff --git a/release-plans/schema.json b/release-plans/schema.json deleted file mode 100644 index a7df639..0000000 --- a/release-plans/schema.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/release-plans/schema.json", - "title": "Durable Workflow release plan", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "plan", - "channel", - "foundation", - "components", - "beta_authorization" - ], - "properties": { - "schema": { - "const": "durable-workflow.release-plan/v2" - }, - "plan": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9._-]{0,55}$" - }, - "channel": { - "enum": ["alpha", "beta", "rc"] - }, - "foundation": { - "oneOf": [ - { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit"], - "properties": { - "tag": { - "const": "beta-candidate/beta-continuity-foundation" - }, - "commit": { - "const": "4995052410bd4301c5796ffba54e0b6d2f490ed1" - } - } - }, - { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit"], - "properties": { - "tag": { - "type": "string", - "pattern": "^beta-candidate/rc-[a-z0-9][a-z0-9._-]{0,55}$" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - } - ] - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": { "$ref": "#/$defs/component" }, - "waterline": { "$ref": "#/$defs/component" }, - "server": { "$ref": "#/$defs/component" }, - "cli": { "$ref": "#/$defs/component" }, - "sdk-php": { "$ref": "#/$defs/component" }, - "sdk-python": { "$ref": "#/$defs/component" }, - "sdk-rust": { "$ref": "#/$defs/component" } - } - }, - "beta_authorization": { - "oneOf": [ - { "type": "null" }, - { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit"], - "properties": { - "tag": { - "type": "string", - "pattern": "^beta-authorization/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - } - ] - } - }, - "$defs": { - "component": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string", - "pattern": "^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?![\\s\\S])" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - } - } -} diff --git a/release-plans/source-preparation-schema.json b/release-plans/source-preparation-schema.json deleted file mode 100644 index 41f7312..0000000 --- a/release-plans/source-preparation-schema.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/release-plans/source-preparation-schema.json", - "title": "Durable Workflow release source preparation", - "type": "object", - "additionalProperties": false, - "required": [ - "$schema", - "schema", - "plan", - "channel", - "train", - "status", - "components", - "authorization" - ], - "properties": { - "$schema": {"const": "./source-preparation-schema.json"}, - "schema": {"const": "durable-workflow.release-source-preparation/v1"}, - "plan": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9._-]{0,55}$" - }, - "channel": {"enum": ["beta", "rc"]}, - "train": { - "type": "string", - "pattern": "^2\\.0\\.0-(?:beta|rc)\\.[1-9][0-9]*$" - }, - "status": {"const": "source-prepared"}, - "components": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": {"$ref": "#/$defs/changelogComponent"}, - "waterline": {"$ref": "#/$defs/changelogComponent"}, - "server": {"$ref": "#/$defs/commitMessageComponent"}, - "cli": {"$ref": "#/$defs/commitMessageComponent"}, - "sdk-php": {"$ref": "#/$defs/changelogComponent"}, - "sdk-python": {"$ref": "#/$defs/changelogComponent"}, - "sdk-rust": {"$ref": "#/$defs/commitMessageComponent"} - } - }, - "authorization": { - "type": "object", - "additionalProperties": false, - "required": ["state", "producer"], - "properties": { - "state": {"const": "required-after-source-landing"}, - "producer": { - "enum": [ - "protected-beta-authorization", - "protected-release-candidate-authorization" - ] - } - } - } - }, - "$defs": { - "componentBase": { - "type": "object", - "required": ["version", "commit", "release_notes"], - "properties": { - "version": { - "type": "string", - "pattern": "^2\\.0\\.0-(?:beta|rc)\\.[1-9][0-9]*$" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - }, - "changelogComponent": { - "unevaluatedProperties": false, - "allOf": [ - {"$ref": "#/$defs/componentBase"}, - { - "type": "object", - "properties": { - "release_notes": {"$ref": "#/$defs/changelogNotes"} - } - } - ] - }, - "commitMessageComponent": { - "unevaluatedProperties": false, - "allOf": [ - {"$ref": "#/$defs/componentBase"}, - { - "type": "object", - "properties": { - "release_notes": {"$ref": "#/$defs/commitMessageNotes"} - } - } - ] - }, - "changelogNotes": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "path", "sha256"], - "properties": { - "kind": {"const": "changelog-unreleased"}, - "path": {"const": "CHANGELOG.md"}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"} - } - }, - "commitMessageNotes": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "sha256"], - "properties": { - "kind": {"const": "source-commit-message"}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"} - } - } - }, - "allOf": [ - { - "if": { - "properties": {"channel": {"const": "beta"}}, - "required": ["channel"] - }, - "then": { - "properties": { - "train": {"pattern": "^2\\.0\\.0-beta\\.[1-9][0-9]*$"}, - "components": { - "additionalProperties": { - "properties": { - "version": {"pattern": "^2\\.0\\.0-beta\\.[1-9][0-9]*$"} - } - } - }, - "authorization": { - "properties": { - "producer": {"const": "protected-beta-authorization"} - } - } - } - }, - "else": { - "properties": { - "train": {"pattern": "^2\\.0\\.0-rc\\.[1-9][0-9]*$"}, - "components": { - "additionalProperties": { - "properties": { - "version": {"pattern": "^2\\.0\\.0-rc\\.[1-9][0-9]*$"} - } - } - }, - "authorization": { - "properties": { - "producer": {"const": "protected-release-candidate-authorization"} - } - } - } - } - } - ] -} diff --git a/release-recovery/README.md b/release-recovery/README.md deleted file mode 100644 index 5b89e10..0000000 --- a/release-recovery/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Component release recovery authority - -[`authority.json`](authority.json) is the protected, machine-readable source -identity for every component's scheduled and manual release recovery workflow. -Each component first resolves protected `main` to an exact commit and requires -the `Beta candidate` push run for that same commit to have completed -successfully. The workflow-runs response must identify the protected workflow -as `.github/workflows/beta-candidate.yml`, which is the path returned by the -GitHub REST API, or as the documented -`.github/workflows/beta-candidate.yml@main` form. The run's `head_branch` must -be `main`; a different workflow or ref is not authority. Only then does the -component read this document from the resolved commit, check the repository -and protected target branch against the built-in product topology, and require -the exact normalized workflow SHA-256 before release discovery can dispatch -publication. - -Workflow changes must land on their protected component branch before this -tuple is refreshed. A mismatched, missing, inactive, or differently located -workflow fails closed in every component. The source document intentionally -contains the full component tuple so a refresh is one reviewable change rather -than independent per-repository digest edits. Recovery evidence retains the -qualified authority commit, exact manifest SHA-256, and successful -qualification run identity. - -The versioned shared authority behavior and its seven target adapters are -defined by the -[release-recovery consumer conformance contract](consumer-conformance/README.md). - -[`protected-source-identities.json`](protected-source-identities.json) retains -at most 100 live-verified workflow identities per component and is limited to -1 MiB by runtime validation. When a qualified successor would exceed that -retention boundary, reconciliation replaces the full retained segment with the -successor and a checkpoint. The checkpoint binds the exact prior protected -document commit and SHA-256, the cumulative accepted-identity count, and the -prior segment's terminal workflow commit and SHA-256. The successor must -supersede that same terminal tuple. - -Routine verification resolves only the retained segment and its immediate -checkpoint. The checkpoint document must itself contain exactly 100 retained -identities, bind its terminal identity to the checkpoint predecessor, and -advance the accepted count by exactly 100. Older checkpoints remain recursively -auditable through immutable protected repository history without adding live -reads to each current-authority verification pass. diff --git a/release-recovery/authority.json b/release-recovery/authority.json deleted file mode 100644 index 88c5252..0000000 --- a/release-recovery/authority.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "schema": "durable-workflow.component-release-recovery-authority/v2", - "source": { - "path": "release-recovery/authority.json", - "qualification": { - "event": "push", - "workflow": ".github/workflows/beta-candidate.yml" - }, - "ref": "refs/heads/main", - "repository": "durable-workflow/.github" - }, - "workflows": { - "cli": { - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/cli", - "sha256": "c19453ac7807422fb58d4f6f8f2b8da41f705b0203abef39ae6e0b396f5c07b2", - "state": "active" - }, - "sdk-php": { - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/sdk-php", - "sha256": "02874a395b9402dc5d7c3c8b521a26637cd6fe66c513dd69e3fd3e1e8d506565", - "state": "active" - }, - "sdk-python": { - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/sdk-python", - "sha256": "6045b3131dae466f17308e50f65067b8664aedba46a5694ee6a2e82810020fa5", - "state": "active" - }, - "sdk-rust": { - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/sdk-rust", - "sha256": "a986697f575129947757be0ecba6a0aafeab3e674b806589c1bc54f5b74a3340", - "state": "active" - }, - "server": { - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/server", - "sha256": "37ef78fd199d9354d5faa6c157414cdce1695a2225e3209e7041c2517876379e", - "state": "active" - }, - "waterline": { - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/v2", - "repository": "durable-workflow/waterline", - "sha256": "b46b5139c6ed94c108a9096c32d67401460269e990020169456c1ca21e6bf8ac", - "state": "active" - }, - "workflow": { - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/v2", - "repository": "durable-workflow/workflow", - "sha256": "133b519f71aee37b00fe3e96cf8177190ea50ca98211d9a69e9c7dc632ae281d", - "state": "active" - } - } -} diff --git a/release-recovery/consumer-conformance/README.md b/release-recovery/consumer-conformance/README.md deleted file mode 100644 index 45de20b..0000000 --- a/release-recovery/consumer-conformance/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Release-recovery consumer conformance - -[`contract.json`](contract.json) is the public, versioned behavior contract for -the independently runnable recovery consumers in the seven component -repositories. Its suite digest binds the identical standard-library runner -copied into each repository. The contract lists the complete required case set -and exact protected target topology. - -Each component owns a small adapter that pins the contract version, contract -digest, local runner digest, local consumer path, and its distribution-specific -verification command. The shared runner requires the contract named by the -adapter to resolve to the same repository file passed on the command line, then -checks that file's canonical bytes against the adapter pins. It executes the -authority cases first and then delegates repository-specific verification -through that adapter. Neither recovery nor publication depends on this -repository at runtime. - -Component CI compares a changed contract with the previous target revision and -rejects changed contract content at the same version. Passing evidence records -the source commit, target branch, contract version and digests, every required -case, and the adapter-owned verification result. The control-plane target audit -resolves every protected target ref to an exact commit before comparing the -contract, suite, identity, and adapter pins on all seven public target branches. -That aggregate audit runs on target-branch activity and on a schedule under the -required control-plane qualification context. Pull requests remain available -for staging a coordinated contract update; the protected target check stays -failing until every consumer publishes the synchronized artifacts. - -To validate this source copy without contacting another repository: - -```console -python scripts/release_recovery_consumer_conformance.py \ - --contract release-recovery/consumer-conformance/contract.json -``` diff --git a/release-recovery/consumer-conformance/adapter-schema.json b/release-recovery/consumer-conformance/adapter-schema.json deleted file mode 100644 index e869b39..0000000 --- a/release-recovery/consumer-conformance/adapter-schema.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "$id": "https://github.com/durable-workflow/.github/blob/main/release-recovery/consumer-conformance/adapter-schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "relativePath": { - "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[A-Za-z0-9._/-]+$", - "type": "string" - }, - "sha256": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - } - }, - "additionalProperties": false, - "properties": { - "component": { - "enum": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ] - }, - "consumer": { - "$ref": "#/$defs/relativePath" - }, - "contract": { - "additionalProperties": false, - "properties": { - "path": { - "$ref": "#/$defs/relativePath" - }, - "sha256": { - "$ref": "#/$defs/sha256" - }, - "version": { - "type": "string" - } - }, - "required": [ - "path", - "sha256", - "version" - ], - "type": "object" - }, - "distribution_verification": { - "additionalProperties": false, - "properties": { - "command": { - "items": { - "minLength": 1, - "type": "string" - }, - "minItems": 2, - "type": "array" - } - }, - "required": [ - "command" - ], - "type": "object" - }, - "repository": { - "pattern": "^durable-workflow/[a-z0-9.-]+$", - "type": "string" - }, - "schema": { - "const": "durable-workflow.release-recovery-consumer-adapter/v1" - }, - "suite": { - "additionalProperties": false, - "properties": { - "path": { - "$ref": "#/$defs/relativePath" - }, - "sha256": { - "$ref": "#/$defs/sha256" - } - }, - "required": [ - "path", - "sha256" - ], - "type": "object" - }, - "target_branch": { - "enum": [ - "main", - "v2" - ] - } - }, - "required": [ - "component", - "consumer", - "contract", - "distribution_verification", - "repository", - "schema", - "suite", - "target_branch" - ], - "title": "Durable Workflow release-recovery consumer conformance adapter", - "type": "object" -} diff --git a/release-recovery/consumer-conformance/contract-schema.json b/release-recovery/consumer-conformance/contract-schema.json deleted file mode 100644 index 965eaf4..0000000 --- a/release-recovery/consumer-conformance/contract-schema.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$id": "https://github.com/durable-workflow/.github/blob/main/release-recovery/consumer-conformance/contract-schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "case": { - "additionalProperties": false, - "properties": { - "id": { - "pattern": "^[a-z][a-z0-9-]+$", - "type": "string" - }, - "requirement": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "id", - "requirement" - ], - "type": "object" - }, - "consumer": { - "additionalProperties": false, - "properties": { - "component": { - "enum": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ] - }, - "repository": { - "pattern": "^durable-workflow/[a-z0-9.-]+$", - "type": "string" - }, - "target_branch": { - "enum": [ - "main", - "v2" - ] - } - }, - "required": [ - "component", - "repository", - "target_branch" - ], - "type": "object" - } - }, - "additionalProperties": false, - "properties": { - "cases": { - "items": { - "$ref": "#/$defs/case" - }, - "maxItems": 14, - "minItems": 14, - "type": "array", - "uniqueItems": true - }, - "consumers": { - "items": { - "$ref": "#/$defs/consumer" - }, - "maxItems": 7, - "minItems": 7, - "type": "array", - "uniqueItems": true - }, - "schema": { - "const": "durable-workflow.release-recovery-consumer-conformance/v1" - }, - "suite": { - "additionalProperties": false, - "properties": { - "sha256": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - } - }, - "required": [ - "sha256" - ], - "type": "object" - }, - "version": { - "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", - "type": "string" - } - }, - "required": [ - "cases", - "consumers", - "schema", - "suite", - "version" - ], - "title": "Durable Workflow release-recovery consumer conformance contract", - "type": "object" -} diff --git a/release-recovery/consumer-conformance/contract.json b/release-recovery/consumer-conformance/contract.json deleted file mode 100644 index 02879cf..0000000 --- a/release-recovery/consumer-conformance/contract.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "cases": [ - { - "id": "immutable-plan-enumeration", - "requirement": "Enumerate the complete immutable Git tag registry and reject a missing, duplicate, or malformed plan authority." - }, - { - "id": "current-plan-schema", - "requirement": "Accept the current release-plan v2 authority, retain only the exact digest-allowlisted historical v1 plans, and reject unrecorded v1 or unsupported schema values." - }, - { - "id": "completed-plan-lifecycle", - "requirement": "Select the unique completed current plan for verification-only recovery while refusing any attempt to republish it whether selected explicitly or implicitly." - }, - { - "id": "superseded-plan-lifecycle", - "requirement": "Resolve a superseded plan only through its digest-bound successor and select the surviving product train." - }, - { - "id": "exact-successor-identity", - "requirement": "Reject a successor edge whose tag, plan document, or canonical plan digest is not an exact identity match." - }, - { - "id": "malformed-authority-rejection", - "requirement": "Reject malformed and type-coercible authority values before plan selection, including non-exact SemVer." - }, - { - "id": "continuity-ambiguity-rejection", - "requirement": "Accept one exactly qualified immutable digest-bound resolution selecting a member of the exact continuity claim set independent of enumeration order only when discovery, record, and qualification lookups use their declared authority identities, and reject every unresolved or invalid fork." - }, - { - "id": "explicit-terminal-plan-rejection", - "requirement": "Allow an explicitly requested completed plan to verify as a no-op, and reject a superseded plan before publication or distribution verification." - }, - { - "id": "bounded-authority-convergence", - "requirement": "Re-enumerate lifecycle authority and fail closed when the registry does not converge within the declared attempt bound." - }, - { - "id": "release-candidate-beta-qualification", - "requirement": "Accept an exact release-candidate plan only when it retains one coherent immutable beta qualification, and reject non-beta qualification records." - }, - { - "id": "authoritative-rc-foundation", - "requirement": "Accept the aggregate release-candidate foundation authenticated by the selected immutable plan only when its tag, commit, exact seven-component tuple, and verification evidence agree; reject substitutions, malformed identities, and conflicting authority." - }, - { - "id": "scheduled-empty-no-op", - "requirement": "Record a successful neutral no-op when scheduled recovery has no eligible release plan, while retaining failures for malformed or unavailable authority." - }, - { - "id": "trusted-github-api-transport", - "requirement": "Use the GitHub Actions-supported gh API transport with its certificate-verifying runner trust for GitHub API calls; retry a transient certificate transport failure within the declared bound, classify persistent trust failure as runner transport, and never retry an ordinary API rejection." - }, - { - "id": "transport-fail-closed-publication", - "requirement": "Record structured runner-transport evidence with no publication action after transport retry exhaustion while preserving the explicitly authorized publication-ready path." - } - ], - "consumers": [ - { - "component": "workflow", - "repository": "durable-workflow/workflow", - "target_branch": "v2" - }, - { - "component": "waterline", - "repository": "durable-workflow/waterline", - "target_branch": "v2" - }, - { - "component": "server", - "repository": "durable-workflow/server", - "target_branch": "main" - }, - { - "component": "cli", - "repository": "durable-workflow/cli", - "target_branch": "main" - }, - { - "component": "sdk-php", - "repository": "durable-workflow/sdk-php", - "target_branch": "main" - }, - { - "component": "sdk-python", - "repository": "durable-workflow/sdk-python", - "target_branch": "main" - }, - { - "component": "sdk-rust", - "repository": "durable-workflow/sdk-rust", - "target_branch": "main" - } - ], - "schema": "durable-workflow.release-recovery-consumer-conformance/v1", - "suite": { - "sha256": "87ec235eb15d1abb63abf0199607f66d0a49faa6903217858348bc0be67f309c" - }, - "version": "1.8.2" -} diff --git a/release-recovery/protected-source-identities-schema.json b/release-recovery/protected-source-identities-schema.json deleted file mode 100644 index b7c9994..0000000 --- a/release-recovery/protected-source-identities-schema.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/release-recovery/protected-source-identities-schema.json", - "title": "Protected component recovery workflow source identities", - "type": "object", - "required": ["schema", "source", "workflows"], - "additionalProperties": false, - "properties": { - "schema": { - "const": "durable-workflow.component-release-recovery-source-identities/v2" - }, - "source": { - "type": "object", - "required": ["authority_path", "path", "ref", "repository"], - "additionalProperties": false, - "properties": { - "authority_path": {"const": "release-recovery/authority.json"}, - "path": {"const": "release-recovery/protected-source-identities.json"}, - "ref": {"const": "refs/heads/main"}, - "repository": {"const": "durable-workflow/.github"} - } - }, - "workflows": { - "type": "object", - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "additionalProperties": false, - "properties": { - "workflow": {"$ref": "#/$defs/workflow"}, - "waterline": {"$ref": "#/$defs/workflow"}, - "server": {"$ref": "#/$defs/workflow"}, - "cli": {"$ref": "#/$defs/workflow"}, - "sdk-php": {"$ref": "#/$defs/workflow"}, - "sdk-python": {"$ref": "#/$defs/workflow"}, - "sdk-rust": {"$ref": "#/$defs/workflow"} - } - } - }, - "$defs": { - "workflow": { - "type": "object", - "required": ["identities", "path", "ref", "repository", "state"], - "additionalProperties": false, - "properties": { - "checkpoint": {"$ref": "#/$defs/checkpoint"}, - "identities": { - "type": "array", - "minItems": 1, - "maxItems": 100 - }, - "path": {"const": ".github/workflows/release-plan-recovery.yml"}, - "ref": {"type": "string", "pattern": "^refs/heads/(main|v2)$"}, - "repository": {"type": "string", "pattern": "^durable-workflow/[a-z0-9.-]+$"}, - "state": {"const": "active"} - }, - "allOf": [ - { - "if": {"required": ["checkpoint"]}, - "then": { - "properties": { - "identities": { - "prefixItems": [{"$ref": "#/$defs/successorIdentity"}], - "items": {"$ref": "#/$defs/successorIdentity"} - } - } - }, - "else": { - "properties": { - "identities": { - "prefixItems": [{"$ref": "#/$defs/rootIdentity"}], - "items": {"$ref": "#/$defs/successorIdentity"} - } - } - } - } - ] - }, - "identity": { - "type": "object", - "required": ["qualification", "qualification_policy", "sha256", "source_commit"], - "additionalProperties": false, - "properties": { - "qualification": {"$ref": "#/$defs/qualification"}, - "qualification_policy": {"$ref": "#/$defs/qualificationPolicy"}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, - "source_commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, - "supersedes": {"$ref": "#/$defs/predecessor"} - } - }, - "rootIdentity": { - "allOf": [ - {"$ref": "#/$defs/identity"}, - {"not": {"required": ["supersedes"]}} - ] - }, - "successorIdentity": { - "allOf": [ - {"$ref": "#/$defs/identity"}, - {"required": ["supersedes"]} - ] - }, - "checkpoint": { - "type": "object", - "required": ["accepted_identities", "predecessor", "source"], - "additionalProperties": false, - "properties": { - "accepted_identities": {"type": "integer", "minimum": 100, "multipleOf": 100}, - "predecessor": {"$ref": "#/$defs/predecessor"}, - "source": {"$ref": "#/$defs/checkpointSource"} - } - }, - "checkpointSource": { - "type": "object", - "required": ["commit", "path", "ref", "repository", "sha256"], - "additionalProperties": false, - "properties": { - "commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, - "path": {"const": "release-recovery/protected-source-identities.json"}, - "ref": {"const": "refs/heads/main"}, - "repository": {"const": "durable-workflow/.github"}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"} - } - }, - "qualificationPolicy": { - "type": "object", - "required": ["commit", "path", "ref", "repository", "sha256"], - "additionalProperties": false, - "properties": { - "commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, - "path": {"const": "qualification/policy.json"}, - "ref": {"const": "refs/heads/main"}, - "repository": {"const": "durable-workflow/.github"}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"} - } - }, - "predecessor": { - "type": "object", - "required": ["sha256", "source_commit"], - "additionalProperties": false, - "properties": { - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, - "source_commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"} - } - }, - "qualification": { - "type": "object", - "required": [ - "check_run_id", - "check_url", - "conclusion", - "event", - "head_branch", - "head_sha", - "required_check", - "run_attempt", - "run_id", - "status", - "url", - "workflow" - ], - "additionalProperties": false, - "properties": { - "check_run_id": {"type": "integer", "minimum": 1}, - "check_url": { - "type": "string", - "pattern": "^https://github\\.com/durable-workflow/[a-z0-9.-]+/actions/runs/[1-9][0-9]*/job/[1-9][0-9]*$" - }, - "conclusion": {"const": "success"}, - "event": {"const": "push"}, - "head_branch": {"enum": ["main", "v2"]}, - "head_sha": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, - "required_check": {"type": "string", "minLength": 1, "maxLength": 256}, - "run_attempt": {"type": "integer", "minimum": 1}, - "run_id": {"type": "integer", "minimum": 1}, - "status": {"const": "completed"}, - "url": { - "type": "string", - "pattern": "^https://github\\.com/durable-workflow/[a-z0-9.-]+/actions/runs/[1-9][0-9]*$" - }, - "workflow": { - "type": "string", - "maxLength": 256, - "pattern": "^\\.github/workflows/[A-Za-z0-9._-]+\\.ya?ml$" - } - } - } - } -} diff --git a/release-recovery/protected-source-identities.json b/release-recovery/protected-source-identities.json deleted file mode 100644 index 669f41c..0000000 --- a/release-recovery/protected-source-identities.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "schema": "durable-workflow.component-release-recovery-source-identities/v2", - "source": { - "authority_path": "release-recovery/authority.json", - "path": "release-recovery/protected-source-identities.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github" - }, - "workflows": { - "cli": { - "identities": [ - { - "qualification": { - "check_run_id": 93697654376, - "check_url": "https://github.com/durable-workflow/cli/actions/runs/31465530612/job/93697654376", - "conclusion": "success", - "event": "push", - "head_branch": "main", - "head_sha": "1bf1cad9cfea7eeff624c5c6b90bb7eb115eef98", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 31465530612, - "status": "completed", - "url": "https://github.com/durable-workflow/cli/actions/runs/31465530612", - "workflow": ".github/workflows/build.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "c19453ac7807422fb58d4f6f8f2b8da41f705b0203abef39ae6e0b396f5c07b2", - "source_commit": "1bf1cad9cfea7eeff624c5c6b90bb7eb115eef98" - } - ], - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/cli", - "state": "active" - }, - "sdk-php": { - "identities": [ - { - "qualification": { - "check_run_id": 93697095297, - "check_url": "https://github.com/durable-workflow/sdk-php/actions/runs/31465209024/job/93697095297", - "conclusion": "success", - "event": "push", - "head_branch": "main", - "head_sha": "16a9d21b1e923762a895c835e51399ffa262b36a", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 31465209024, - "status": "completed", - "url": "https://github.com/durable-workflow/sdk-php/actions/runs/31465209024", - "workflow": ".github/workflows/ci.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "02874a395b9402dc5d7c3c8b521a26637cd6fe66c513dd69e3fd3e1e8d506565", - "source_commit": "16a9d21b1e923762a895c835e51399ffa262b36a" - } - ], - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/sdk-php", - "state": "active" - }, - "sdk-python": { - "identities": [ - { - "qualification": { - "check_run_id": 93762613274, - "check_url": "https://github.com/durable-workflow/sdk-python/actions/runs/31486048588/job/93762613274", - "conclusion": "success", - "event": "push", - "head_branch": "main", - "head_sha": "b888d8e845cb33ea7729c0d94a0d205fa577dd69", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 31486048588, - "status": "completed", - "url": "https://github.com/durable-workflow/sdk-python/actions/runs/31486048588", - "workflow": ".github/workflows/ci.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "6045b3131dae466f17308e50f65067b8664aedba46a5694ee6a2e82810020fa5", - "source_commit": "b888d8e845cb33ea7729c0d94a0d205fa577dd69" - } - ], - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/sdk-python", - "state": "active" - }, - "sdk-rust": { - "identities": [ - { - "qualification": { - "check_run_id": 93389828947, - "check_url": "https://github.com/durable-workflow/sdk-rust/actions/runs/31367638797/job/93389828947", - "conclusion": "success", - "event": "push", - "head_branch": "main", - "head_sha": "44bae715e910aa175183079fb0b7e893cea8f4d1", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 31367638797, - "status": "completed", - "url": "https://github.com/durable-workflow/sdk-rust/actions/runs/31367638797", - "workflow": ".github/workflows/ci.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "c43b0e100c388301af12b9f5e9354955ff6c31b3156b4a0b66a8c3379516645c", - "source_commit": "44bae715e910aa175183079fb0b7e893cea8f4d1" - }, - { - "qualification": { - "check_run_id": 98031524252, - "check_url": "https://github.com/durable-workflow/sdk-rust/actions/runs/32919804627/job/98031524252", - "conclusion": "success", - "event": "push", - "head_branch": "main", - "head_sha": "cc1eba739f21f37f853a2c34c719e9f0f02bcbd8", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 32919804627, - "status": "completed", - "url": "https://github.com/durable-workflow/sdk-rust/actions/runs/32919804627", - "workflow": ".github/workflows/ci.yml" - }, - "qualification_policy": { - "commit": "b757a4294457ba432ef569f0dd35a9cc128e5c61", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "8fab211c6c22fabf6f4cb0a124e400c5d72376a58e43ef01efd92cbd760d6568" - }, - "sha256": "a986697f575129947757be0ecba6a0aafeab3e674b806589c1bc54f5b74a3340", - "source_commit": "cc1eba739f21f37f853a2c34c719e9f0f02bcbd8", - "supersedes": { - "sha256": "c43b0e100c388301af12b9f5e9354955ff6c31b3156b4a0b66a8c3379516645c", - "source_commit": "44bae715e910aa175183079fb0b7e893cea8f4d1" - } - } - ], - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/sdk-rust", - "state": "active" - }, - "server": { - "identities": [ - { - "qualification": { - "check_run_id": 93782312492, - "check_url": "https://github.com/durable-workflow/server/actions/runs/31492562083/job/93782312492", - "conclusion": "success", - "event": "push", - "head_branch": "main", - "head_sha": "383b14e389ac7ec4873c74034d6087ce9db0bea0", - "required_check": "PHPUnit feature suite", - "run_attempt": 1, - "run_id": 31492562083, - "status": "completed", - "url": "https://github.com/durable-workflow/server/actions/runs/31492562083", - "workflow": ".github/workflows/phpunit-feature.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "37ef78fd199d9354d5faa6c157414cdce1695a2225e3209e7041c2517876379e", - "source_commit": "383b14e389ac7ec4873c74034d6087ce9db0bea0" - } - ], - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/main", - "repository": "durable-workflow/server", - "state": "active" - }, - "waterline": { - "identities": [ - { - "qualification": { - "check_run_id": 93364928690, - "check_url": "https://github.com/durable-workflow/waterline/actions/runs/31358911626/job/93364928690", - "conclusion": "success", - "event": "push", - "head_branch": "v2", - "head_sha": "fa28853fe0e12743d0159eb29497d95b4e7d2d57", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 31358911626, - "status": "completed", - "url": "https://github.com/durable-workflow/waterline/actions/runs/31358911626", - "workflow": ".github/workflows/php.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "c702d5dadce236ce96b39f0c086728a6882d3f54a5349f3871a0ea8899bccb6b", - "source_commit": "fa28853fe0e12743d0159eb29497d95b4e7d2d57" - }, - { - "qualification": { - "check_run_id": 93787475167, - "check_url": "https://github.com/durable-workflow/waterline/actions/runs/31493470391/job/93787475167", - "conclusion": "success", - "event": "push", - "head_branch": "v2", - "head_sha": "bcd3f627d3f2af5c2b2cd5663653b3049f490fc2", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 31493470391, - "status": "completed", - "url": "https://github.com/durable-workflow/waterline/actions/runs/31493470391", - "workflow": ".github/workflows/php.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "90f68dad2ec05d48aadbd1bb144e809ffce67f974020ec7685add81495424ed2", - "source_commit": "bcd3f627d3f2af5c2b2cd5663653b3049f490fc2", - "supersedes": { - "sha256": "c702d5dadce236ce96b39f0c086728a6882d3f54a5349f3871a0ea8899bccb6b", - "source_commit": "fa28853fe0e12743d0159eb29497d95b4e7d2d57" - } - }, - { - "qualification": { - "check_run_id": 93901914270, - "check_url": "https://github.com/durable-workflow/waterline/actions/runs/31527712919/job/93901914270", - "conclusion": "success", - "event": "push", - "head_branch": "v2", - "head_sha": "367c0a1896ad2978b4b9e4f67e2b09558686962b", - "required_check": "Target branch qualification", - "run_attempt": 1, - "run_id": 31527712919, - "status": "completed", - "url": "https://github.com/durable-workflow/waterline/actions/runs/31527712919", - "workflow": ".github/workflows/php.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "b46b5139c6ed94c108a9096c32d67401460269e990020169456c1ca21e6bf8ac", - "source_commit": "367c0a1896ad2978b4b9e4f67e2b09558686962b", - "supersedes": { - "sha256": "90f68dad2ec05d48aadbd1bb144e809ffce67f974020ec7685add81495424ed2", - "source_commit": "bcd3f627d3f2af5c2b2cd5663653b3049f490fc2" - } - } - ], - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/v2", - "repository": "durable-workflow/waterline", - "state": "active" - }, - "workflow": { - "identities": [ - { - "qualification": { - "check_run_id": 93306988208, - "check_url": "https://github.com/durable-workflow/workflow/actions/runs/31337793441/job/93306988208", - "conclusion": "success", - "event": "push", - "head_branch": "v2", - "head_sha": "018d1a99f5a39452b02c68d5737f81c31f19f67f", - "required_check": "build", - "run_attempt": 1, - "run_id": 31337793441, - "status": "completed", - "url": "https://github.com/durable-workflow/workflow/actions/runs/31337793441", - "workflow": ".github/workflows/php.yml" - }, - "qualification_policy": { - "commit": "6c177285bd9224bad7a58da1e07c0d9f52850160", - "path": "qualification/policy.json", - "ref": "refs/heads/main", - "repository": "durable-workflow/.github", - "sha256": "c4383fe691fef8f571a9b58c6dbd7f0d41f9925b24df8ae651570204b394e724" - }, - "sha256": "133b519f71aee37b00fe3e96cf8177190ea50ca98211d9a69e9c7dc632ae281d", - "source_commit": "018d1a99f5a39452b02c68d5737f81c31f19f67f" - } - ], - "path": ".github/workflows/release-plan-recovery.yml", - "ref": "refs/heads/v2", - "repository": "durable-workflow/workflow", - "state": "active" - } - } -} diff --git a/release-recovery/schema.json b/release-recovery/schema.json deleted file mode 100644 index 38f2205..0000000 --- a/release-recovery/schema.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/release-recovery/schema.json", - "title": "Durable Workflow component release recovery authority", - "type": "object", - "required": ["schema", "source", "workflows"], - "additionalProperties": false, - "properties": { - "schema": { - "const": "durable-workflow.component-release-recovery-authority/v2" - }, - "source": { - "type": "object", - "required": ["repository", "ref", "path", "qualification"], - "additionalProperties": false, - "properties": { - "repository": {"const": "durable-workflow/.github"}, - "ref": {"const": "refs/heads/main"}, - "path": {"const": "release-recovery/authority.json"}, - "qualification": { - "type": "object", - "required": ["workflow", "event"], - "additionalProperties": false, - "properties": { - "workflow": {"const": ".github/workflows/beta-candidate.yml"}, - "event": {"const": "push"} - } - } - } - }, - "workflows": { - "type": "object", - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "additionalProperties": false, - "properties": { - "workflow": {"$ref": "#/$defs/workflow"}, - "waterline": {"$ref": "#/$defs/workflow"}, - "server": {"$ref": "#/$defs/workflow"}, - "cli": {"$ref": "#/$defs/workflow"}, - "sdk-php": {"$ref": "#/$defs/workflow"}, - "sdk-python": {"$ref": "#/$defs/workflow"}, - "sdk-rust": {"$ref": "#/$defs/workflow"} - } - } - }, - "$defs": { - "workflow": { - "type": "object", - "required": ["repository", "ref", "path", "state", "sha256"], - "additionalProperties": false, - "properties": { - "repository": {"type": "string", "pattern": "^durable-workflow/[a-z0-9.-]+$"}, - "ref": {"type": "string", "pattern": "^refs/heads/(main|v2)$"}, - "path": {"const": ".github/workflows/release-plan-recovery.yml"}, - "state": {"const": "active"}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"} - } - } - } -} diff --git a/repository-hygiene/inventory-schema.json b/repository-hygiene/inventory-schema.json deleted file mode 100644 index 523c0e6..0000000 --- a/repository-hygiene/inventory-schema.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/repository-hygiene/inventory-schema.json", - "title": "Durable Workflow public repository hygiene inventory", - "type": "object", - "additionalProperties": false, - "required": [ - "$schema", - "schema", - "organization", - "completed_on", - "cleanup_release_train", - "reviewed_surfaces", - "repositories" - ], - "properties": { - "$schema": { - "const": "./inventory-schema.json" - }, - "schema": { - "const": "durable-workflow.public-repository-hygiene/v1" - }, - "organization": { - "const": "durable-workflow" - }, - "completed_on": { - "type": "string", - "format": "date" - }, - "cleanup_release_train": { - "type": "string", - "pattern": "^2\\.0\\.0-beta\\.[1-9][0-9]*$" - }, - "reviewed_surfaces": { - "type": "array", - "minItems": 7, - "uniqueItems": true, - "items": { - "enum": [ - "phase-and-progress-documents", - "bootstrap-and-prerelease-plans", - "experiments", - "workflows", - "assets", - "dependencies", - "release-and-recovery-artifacts", - "documentation-validation" - ] - } - }, - "repositories": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/repository" - } - } - }, - "$defs": { - "repository": { - "type": "object", - "additionalProperties": false, - "required": [ - "repository", - "branch", - "disposition", - "removals", - "retained_surfaces" - ], - "properties": { - "repository": { - "type": "string", - "minLength": 1 - }, - "branch": { - "enum": [ - "main", - "v2" - ] - }, - "disposition": { - "enum": [ - "maintained", - "cleaned", - "retired-bootstrap" - ] - }, - "removals": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1 - } - }, - "retained_surfaces": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": [ - "product-source", - "public-api", - "operator-guidance", - "user-guidance", - "release-history", - "release-delivery", - "release-recovery", - "qualification", - "conformance", - "visual-evidence", - "examples", - "retirement-guidance" - ] - } - } - } - } - } -} diff --git a/repository-hygiene/inventory.json b/repository-hygiene/inventory.json deleted file mode 100644 index 961d16d..0000000 --- a/repository-hygiene/inventory.json +++ /dev/null @@ -1,188 +0,0 @@ -{ - "$schema": "./inventory-schema.json", - "schema": "durable-workflow.public-repository-hygiene/v1", - "organization": "durable-workflow", - "completed_on": "2026-07-26", - "cleanup_release_train": "2.0.0-beta.17", - "reviewed_surfaces": [ - "phase-and-progress-documents", - "bootstrap-and-prerelease-plans", - "experiments", - "workflows", - "assets", - "dependencies", - "release-and-recovery-artifacts", - "documentation-validation" - ], - "repositories": [ - { - "repository": ".github", - "branch": "main", - "disposition": "maintained", - "removals": [], - "retained_surfaces": [ - "release-delivery", - "release-recovery", - "qualification", - "conformance", - "visual-evidence", - "operator-guidance" - ] - }, - { - "repository": "workflow", - "branch": "v2", - "disposition": "cleaned", - "removals": [ - "copy-coupled Markdown documentation tests" - ], - "retained_surfaces": [ - "product-source", - "public-api", - "user-guidance", - "operator-guidance", - "release-history", - "release-delivery", - "release-recovery" - ] - }, - { - "repository": "waterline", - "branch": "v2", - "disposition": "cleaned", - "removals": [ - "ACCESSIBILITY_AUDIT.md", - "FRONTEND_MODERNIZATION_EVAL.md", - "PHASE_0_PROGRESS.md" - ], - "retained_surfaces": [ - "product-source", - "public-api", - "user-guidance", - "operator-guidance", - "release-history", - "release-delivery", - "release-recovery" - ] - }, - { - "repository": "server", - "branch": "main", - "disposition": "cleaned", - "removals": [ - "copy-coupled Markdown documentation assertions" - ], - "retained_surfaces": [ - "product-source", - "public-api", - "user-guidance", - "operator-guidance", - "release-delivery", - "release-recovery", - "conformance" - ] - }, - { - "repository": "cli", - "branch": "main", - "disposition": "cleaned", - "removals": [ - "references to retired Markdown-copy tests" - ], - "retained_surfaces": [ - "product-source", - "public-api", - "user-guidance", - "release-delivery", - "release-recovery", - "conformance" - ] - }, - { - "repository": "ai", - "branch": "main", - "disposition": "retired-bootstrap", - "removals": [ - ".gitignore", - "composer.json" - ], - "retained_surfaces": [ - "retirement-guidance", - "release-history" - ] - }, - { - "repository": "sample-app", - "branch": "main", - "disposition": "cleaned", - "removals": [ - "copy-coupled README assertions" - ], - "retained_surfaces": [ - "product-source", - "user-guidance", - "examples", - "release-delivery", - "conformance" - ] - }, - { - "repository": "sdk-php", - "branch": "main", - "disposition": "maintained", - "removals": [], - "retained_surfaces": [ - "product-source", - "public-api", - "user-guidance", - "release-history", - "release-delivery", - "release-recovery" - ] - }, - { - "repository": "sdk-python", - "branch": "main", - "disposition": "maintained", - "removals": [], - "retained_surfaces": [ - "product-source", - "public-api", - "user-guidance", - "release-history", - "release-delivery", - "release-recovery", - "conformance" - ] - }, - { - "repository": "sdk-rust", - "branch": "main", - "disposition": "maintained", - "removals": [], - "retained_surfaces": [ - "product-source", - "public-api", - "user-guidance", - "release-delivery", - "release-recovery" - ] - }, - { - "repository": "durable-workflow.github.io", - "branch": "main", - "disposition": "cleaned", - "removals": [ - "references to retired Markdown-copy tests" - ], - "retained_surfaces": [ - "user-guidance", - "operator-guidance", - "release-history", - "release-delivery", - "qualification", - "conformance" - ] - } - ] -} diff --git a/scripts/beta_authorization.py b/scripts/beta_authorization.py deleted file mode 100644 index 14ddd57..0000000 --- a/scripts/beta_authorization.py +++ /dev/null @@ -1,1777 +0,0 @@ -#!/usr/bin/env python3 -"""Validate and record the protected GitHub beta authorization decision.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import re -import subprocess -import sys -import tempfile -import urllib.parse -from pathlib import Path -from typing import Any - -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.beta_candidate import ( - COMPONENTS, - VERSION_PATTERN, - CandidateError, - PublicClient, - canonical_json, - fetch_existing_record, - manifest_digest, - read_record_file, - run_git, - validate_manifest, - validate_verification, - write_github_output, -) -from scripts.beta_conformance import ConformanceError -from scripts.beta_continuity import ( - ContinuityError, - exact_completion_authority, - load_config, -) -from scripts.beta_continuity import ( - validated_conformance_release as validate_conformance_release, -) -from scripts.release_plan import ( - EXPECTED_DEFAULT_BRANCHES, - read_public_record, - resolve_tag, -) -from scripts.release_plan import ( - validate_recorded_plan as validate_plan, -) - -REQUEST_SCHEMA = "durable-workflow.beta-authorization-request/v1" -AUTHORIZATION_SCHEMA = "durable-workflow.beta-authorization/v1" -EVIDENCE_SCHEMA = "durable-workflow.beta-authorization-evidence/v1" -QUALIFICATION_SCHEMA = "durable-workflow.github-target-qualification/v1" -CONTROL_REPOSITORY = "durable-workflow/.github" -AUTHORIZATION_TAG_PREFIX = "beta-authorization/" -AUTHORIZATION_ENVIRONMENT = "beta-authorization" -AUTHORIZATION_WORKFLOW = ".github/workflows/beta-authorization.yml" -AUTHORIZATION_WORKFLOW_REF = ( - "durable-workflow/.github/.github/workflows/beta-authorization.yml@refs/heads/main" -) -AUTHORITY_ISSUE = 3 -AUTHORITY_ISSUE_URL = f"https://github.com/{CONTROL_REPOSITORY}/issues/{AUTHORITY_ISSUE}" -AUTHORITY_WORK_ID = "authorize-2-0-beta" -PRODUCT_OWNER_REVIEWER_ID = 1130888 -API_VERSION = "2022-11-28" -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") -IDENTITY_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,55}$") -BETA_VERSION_PATTERN = re.compile(r"^2\.0\.0-beta\.[1-9][0-9]*$") -LOGIN_PATTERN = re.compile(r"^[A-Za-z0-9-]{1,39}$") -DECISION_MARKER = re.compile( - r"" -) -REQUIRED_AUTHORITY_LABELS = { - "authority:github", - "beta:blocker", - "completion:evidence-required", - "kind:release-blocker", - "priority:P0", -} -MAX_REQUEST_BYTES = 256 * 1024 -MAX_EVIDENCE_BYTES = 1024 * 1024 -MAX_QUALIFICATION_BYTES = 2 * 1024 * 1024 -MAX_QUALIFICATION_POLICY_BYTES = 256 * 1024 -MAX_ISSUE_PAGES = 100 -QUALIFICATION_BRANCH_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]{0,254}$") -QUALIFICATION_WORKFLOW_PATH_PATTERN = re.compile( - r"^\.github/workflows/[a-z0-9][a-z0-9.-]*\.ya?ml$" -) -QUALIFICATION_POLICY_WORKFLOW_PATH_PATTERN = re.compile(r"^[a-z0-9][a-z0-9.-]*\.ya?ml$") -QUALIFICATION_REPOSITORY_PATTERN = re.compile(r"^[A-Za-z0-9._-]{1,100}$") -ACTION_REPOSITORY_PATTERN = re.compile( - r"^[a-z0-9][a-z0-9_.-]*/[a-z0-9][a-z0-9_.-]*$" -) -ACTION_PATH_PART_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+$") -ACTION_REFERENCE_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]*$") -ACTION_RUNTIME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]*$") - - -def require_exact_keys(value: Any, expected: set[str], context: str) -> dict[str, Any]: - if not isinstance(value, dict) or set(value) != expected: - raise CandidateError(f"{context} keys must be exactly {sorted(expected)}") - return value - - -def require_git_record(value: Any, context: str, *, digest: bool = False) -> dict[str, Any]: - keys = {"tag", "commit", "sha256"} if digest else {"tag", "commit"} - record = require_exact_keys(value, keys, context) - if ( - not isinstance(record["tag"], str) - or not record["tag"] - or len(record["tag"]) > 180 - or not COMMIT_PATTERN.fullmatch(str(record["commit"])) - or (digest and not SHA256_PATTERN.fullmatch(str(record["sha256"]))) - ): - raise CandidateError(f"{context} has an invalid immutable Git identity") - return record - - -def validate_authorization(value: Any) -> dict[str, Any]: - authorization = require_exact_keys( - value, - {"schema", "channel", "candidate", "components"}, - "beta authorization", - ) - if authorization["schema"] != AUTHORIZATION_SCHEMA or authorization["channel"] != "beta": - raise CandidateError("authorization must explicitly select only the beta channel") - if not isinstance(authorization["candidate"], str) or not IDENTITY_PATTERN.fullmatch( - authorization["candidate"] - ): - raise CandidateError("authorization candidate must be a valid release-plan identity") - components = authorization["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise CandidateError(f"authorization components must be exactly {sorted(COMPONENTS)}") - for name, value in components.items(): - identity = require_exact_keys(value, {"version", "commit"}, f"authorization components.{name}") - if not isinstance(identity["version"], str) or not VERSION_PATTERN.fullmatch(identity["version"]): - raise CandidateError(f"authorization components.{name}.version must be exact SemVer") - if not COMMIT_PATTERN.fullmatch(str(identity["commit"])): - raise CandidateError(f"authorization components.{name}.commit must be a full lowercase commit") - for name in ("workflow", "waterline"): - if not BETA_VERSION_PATTERN.fullmatch(components[name]["version"]): - raise CandidateError(f"authorization {name} version must be an exact 2.0.0-beta.N identity") - return authorization - - -def validate_request(value: Any) -> dict[str, Any]: - request = require_exact_keys(value, {"schema", "authorization", "evidence"}, "authorization request") - if request["schema"] != REQUEST_SCHEMA: - raise CandidateError(f"authorization request schema must be {REQUEST_SCHEMA}") - validate_authorization(request["authorization"]) - evidence = require_exact_keys( - request["evidence"], - {"candidate", "conformance", "continuity", "decision"}, - "authorization request evidence", - ) - candidate = require_git_record(evidence["candidate"], "candidate evidence") - if not re.fullmatch(r"beta-candidate/[a-z0-9][a-z0-9._-]{0,62}", candidate["tag"]): - raise CandidateError("candidate evidence must cite an immutable beta-candidate tag") - conformance = require_git_record(evidence["conformance"], "conformance evidence") - if not re.fullmatch( - r"beta-conformance/[a-z0-9][a-z0-9._-]{0,62}/[1-9][0-9]*\.[1-9][0-9]*", - conformance["tag"], - ): - raise CandidateError("conformance evidence must cite a retained run and attempt") - continuity = require_exact_keys(evidence["continuity"], {"complete", "no_op"}, "continuity evidence") - complete = require_git_record(continuity["complete"], "continuity complete evidence") - no_op = require_git_record(continuity["no_op"], "continuity no-op evidence") - complete_match = re.fullmatch( - r"beta-continuity/(?P[a-z0-9][a-z0-9._-]{0,55})/complete", - complete["tag"], - ) - if complete_match is None or no_op["tag"] != f"beta-continuity/{complete_match.group('plan')}/no-op-confirmed": - raise CandidateError("continuity evidence must cite matching complete and no-op phase tags") - decision = require_exact_keys(evidence["decision"], {"issue", "comment"}, "decision evidence") - if decision["issue"] != AUTHORITY_ISSUE or type(decision["comment"]) is not int or decision["comment"] < 1: - raise CandidateError(f"decision evidence must cite a comment on {CONTROL_REPOSITORY}#{AUTHORITY_ISSUE}") - return request - - -def load_request(path: Path) -> dict[str, Any]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read authorization request {path}: {error}") from error - if len(raw) > MAX_REQUEST_BYTES: - raise CandidateError("authorization request exceeds the 256 KiB limit") - try: - request = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"authorization request is not valid JSON: {error}") from error - return validate_request(request) - - -def user_identity(value: Any, context: str) -> dict[str, Any]: - if not isinstance(value, dict): - raise CandidateError(f"{context} has no GitHub user identity") - identity = { - "login": value.get("login"), - "id": value.get("id"), - "node_id": value.get("node_id"), - "url": value.get("url"), - "html_url": value.get("html_url"), - } - login = identity["login"] - if ( - not isinstance(login, str) - or not LOGIN_PATTERN.fullmatch(login) - or type(identity["id"]) is not int - or identity["id"] < 1 - or not isinstance(identity["node_id"], str) - or not identity["node_id"] - or identity["url"] != f"https://api.github.com/users/{login}" - or identity["html_url"] != f"https://github.com/{login}" - ): - raise CandidateError(f"{context} has an invalid durable GitHub user identity") - return identity - - -def environment_urls() -> tuple[str, str]: - activity = ( - f"https://github.com/{CONTROL_REPOSITORY}/deployments/activity_log" - f"?environments_filter={AUTHORIZATION_ENVIRONMENT}" - ) - api = f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{AUTHORIZATION_ENVIRONMENT}" - return activity, api - - -def protected_environment_evidence(client: PublicClient) -> dict[str, Any]: - activity_url, _api_url = environment_urls() - encoded = urllib.parse.quote(AUTHORIZATION_ENVIRONMENT, safe="") - headers = {"X-GitHub-Api-Version": API_VERSION} - environment = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}", - headers=headers, - accept="application/vnd.github+json", - ) - rules = environment.get("protection_rules") if isinstance(environment, dict) else None - reviewer_rules = [ - rule - for rule in rules or [] - if isinstance(rule, dict) and rule.get("type") == "required_reviewers" and rule.get("reviewers") - ] - rule_ids = sorted( - rule["id"] for rule in reviewer_rules if type(rule.get("id")) is int and rule["id"] > 0 - ) - reviewer_user_ids: list[int] = [] - for rule in reviewer_rules: - for reviewer in rule.get("reviewers", []): - identity = reviewer.get("reviewer") if isinstance(reviewer, dict) else None - if ( - isinstance(identity, dict) - and reviewer.get("type") == "User" - and type(identity.get("id")) is int - and identity["id"] > 0 - ): - reviewer_user_ids.append(identity["id"]) - reviewer_user_ids.sort() - prevent_self_review = [rule.get("prevent_self_review") for rule in reviewer_rules] - branch_policy = environment.get("deployment_branch_policy") if isinstance(environment, dict) else None - if ( - not rule_ids - or reviewer_user_ids != [PRODUCT_OWNER_REVIEWER_ID] - or prevent_self_review != [False] - or type(environment.get("id") if isinstance(environment, dict) else None) is not int - or environment["id"] < 1 - or environment.get("html_url") != activity_url - or branch_policy != {"custom_branch_policies": True, "protected_branches": False} - ): - raise CandidateError("beta authorization environment must require reviewers and custom branch policies") - policies = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}/" - "deployment-branch-policies?per_page=100", - headers=headers, - accept="application/vnd.github+json", - ) - custom = policies.get("branch_policies") if isinstance(policies, dict) else None - if ( - not isinstance(custom, list) - or policies.get("total_count") != 1 - or len(custom) != 1 - or not isinstance(custom[0], dict) - or type(custom[0].get("id")) is not int - or custom[0]["id"] < 1 - or custom[0].get("name") != "main" - or custom[0].get("type", "branch") != "branch" - ): - raise CandidateError("beta authorization environment must allow only the main branch") - return { - "custom_branch_policies": [{"id": custom[0]["id"], "name": "main"}], - "deployment_branch_policy": branch_policy, - "environment_id": environment["id"], - "environment_url": activity_url, - "prevent_self_review": False, - "required_reviewer_rule_ids": rule_ids, - "required_reviewer_user_ids": reviewer_user_ids, - } - - -def protected_run_evidence( - client: PublicClient, - *, - actor: str, - run_id: int, - run_attempt: int, - workflow_commit: str, - environment_protection: dict[str, Any], -) -> dict[str, Any]: - headers = {"X-GitHub-Api-Version": API_VERSION} - run = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - headers=headers, - accept="application/vnd.github+json", - ) - accepted_paths = {AUTHORIZATION_WORKFLOW, f"{AUTHORIZATION_WORKFLOW}@main"} - if ( - not isinstance(run, dict) - or run.get("actor", {}).get("login") != actor - or run.get("repository", {}).get("full_name") != CONTROL_REPOSITORY - or run.get("id") != run_id - or run.get("run_attempt") != run_attempt - or run.get("event") != "workflow_dispatch" - or run.get("path") not in accepted_paths - or run.get("head_branch") != "main" - or run.get("head_sha") != workflow_commit - or run.get("html_url") != f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id}" - ): - raise CandidateError("beta authorization workflow run evidence does not match GitHub") - history = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}/approvals", - headers=headers, - accept="application/vnd.github+json", - ) - if ( - not isinstance(history, list) - or len(history) != 1 - or not isinstance(history[0], dict) - or history[0].get("state") != "approved" - ): - raise CandidateError("beta authorization run must contain exactly one approved environment review") - review = history[0] - environments = review.get("environments") - if ( - not isinstance(review.get("comment"), str) - or not isinstance(environments, list) - or len(environments) != 1 - or not isinstance(environments[0], dict) - ): - raise CandidateError("beta authorization approval history is malformed") - activity_url, api_url = environment_urls() - environment = environments[0] - if ( - environment.get("id") != environment_protection["environment_id"] - or environment.get("name") != AUTHORIZATION_ENVIRONMENT - or environment.get("html_url") != activity_url - or environment.get("url") != api_url - or not isinstance(environment.get("node_id"), str) - or not environment["node_id"] - ): - raise CandidateError("beta authorization review names the wrong protected environment") - reviewer = user_identity(review.get("user"), "beta authorization approving reviewer") - if reviewer["id"] != PRODUCT_OWNER_REVIEWER_ID: - raise CandidateError("beta authorization review was not submitted by the required product owner") - return { - "comment": review["comment"], - "environments": [ - { - "html_url": activity_url, - "id": environment["id"], - "name": AUTHORIZATION_ENVIRONMENT, - "node_id": environment["node_id"], - "url": api_url, - } - ], - "run_attempt": run_attempt, - "run_id": run_id, - "state": "approved", - "user": reviewer, - } - - -def verify_candidate_evidence(client: PublicClient, request: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]: - reference = request["evidence"]["candidate"] - resolved = resolve_tag(client, CONTROL_REPOSITORY, reference["tag"]) - if resolved != reference["commit"]: - raise CandidateError("cited beta candidate tag does not resolve to its requested immutable commit") - manifest = read_public_record(client, reference["tag"], reference["commit"], "candidate.json") - verification = read_public_record(client, reference["tag"], reference["commit"], "verification.json") - validate_manifest(manifest) - validate_verification(verification, manifest) - intended = request["authorization"]["components"] - mismatches = [ - name for name in COMPONENTS if manifest["components"][name]["commit"] != intended[name]["commit"] - ] - if mismatches: - raise CandidateError(f"beta candidate source commits differ from the intended release plan: {mismatches}") - - release = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/releases/tags/" - f"{urllib.parse.quote(reference['tag'], safe='')}" - ) - assets = { - asset.get("name"): asset - for asset in release.get("assets", []) - if isinstance(asset, dict) and isinstance(asset.get("name"), str) - } if isinstance(release, dict) else {} - for filename, value in (("candidate.json", manifest), ("verification.json", verification)): - asset = assets.get(filename) - if not isinstance(asset, dict) or not isinstance(asset.get("browser_download_url"), str): - raise CandidateError(f"cited beta candidate lacks its durable {filename} Release mirror") - if client.bytes(asset["browser_download_url"]) != canonical_json(value): - raise CandidateError(f"cited beta candidate {filename} Release mirror differs from Git authority") - return manifest, { - "tag": reference["tag"], - "commit": reference["commit"], - "manifest_sha256": manifest_digest(manifest), - "verification_sha256": manifest_digest(verification), - } - - -def validate_qualification_policy_contract(value: Any, context: str) -> dict[str, Any]: - if not isinstance(value, dict) or set(value) not in { - frozenset( - { - "$schema", - "action_runtime", - "organization", - "required_status_checks_strict", - "schema", - "targets", - } - ), - frozenset( - { - "$schema", - "action_runtime", - "organization", - "required_status_checks_strict", - "schema", - "targets", - "workflow_trust", - } - ), - }: - raise CandidateError(f"{context} has an invalid immutable contract") - policy = value - action_runtime = policy["action_runtime"] - if not isinstance(action_runtime, dict) or set(action_runtime) not in { - frozenset({"allowed_releases", "supported_javascript_runtimes"}), - frozenset({"allowed_container_images", "allowed_releases", "supported_javascript_runtimes"}), - }: - raise CandidateError(f"{context} action runtime has an invalid immutable contract") - allowed_releases = action_runtime["allowed_releases"] - supported_runtimes = action_runtime["supported_javascript_runtimes"] - targets = policy["targets"] - if ( - policy["$schema"] != "./schema.json" - or policy["schema"] != QUALIFICATION_SCHEMA - or policy["organization"] != "durable-workflow" - or policy["required_status_checks_strict"] is not True - or not isinstance(allowed_releases, dict) - or not allowed_releases - or any( - not isinstance(repository, str) - or not ACTION_REPOSITORY_PATTERN.fullmatch(repository) - or ( - not isinstance(references, list) - and not isinstance(references, dict) - ) - or not references - or ( - isinstance(references, list) - and ( - len(references) != len(set(references)) - or any( - not isinstance(reference, str) - or not ACTION_REFERENCE_PATTERN.fullmatch(reference) - for reference in references - ) - ) - ) - or ( - isinstance(references, dict) - and any( - not isinstance(commit, str) - or not COMMIT_PATTERN.fullmatch(commit) - or not isinstance(version, str) - or not ACTION_REFERENCE_PATTERN.fullmatch(version) - for commit, version in references.items() - ) - ) - for repository, references in allowed_releases.items() - ) - or not isinstance(supported_runtimes, list) - or not supported_runtimes - or len(supported_runtimes) != len(set(supported_runtimes)) - or any( - not isinstance(runtime, str) or not ACTION_RUNTIME_PATTERN.fullmatch(runtime) - for runtime in supported_runtimes - ) - or not isinstance(targets, dict) - or not targets - or any( - not isinstance(name, str) or not IDENTITY_PATTERN.fullmatch(name) - for name in targets - ) - ): - raise CandidateError(f"{context} has an invalid immutable contract") - - if "allowed_container_images" in action_runtime: - containers = action_runtime["allowed_container_images"] - if ( - not isinstance(containers, dict) - or not containers - or any( - not isinstance(image, str) - or not image - or not isinstance(releases, dict) - or not releases - or any( - not isinstance(digest, str) - or not re.fullmatch(r"sha256:[0-9a-f]{64}", digest) - or not isinstance(version, str) - or not ACTION_REFERENCE_PATTERN.fullmatch(version) - for digest, version in releases.items() - ) - for image, releases in containers.items() - ) - ): - raise CandidateError(f"{context} has invalid immutable container releases") - if "workflow_trust" in policy: - workflow_trust = policy["workflow_trust"] - trust_fields = set(workflow_trust) if isinstance(workflow_trust, dict) else set() - if ( - not isinstance(workflow_trust, dict) - or trust_fields - not in { - frozenset({"privileged_workflow_run_consumers", "pull_request_target_exceptions"}), - frozenset( - { - "privileged_artifact_handoffs", - "privileged_workflow_run_consumers", - "pull_request_target_exceptions", - } - ), - } - or workflow_trust["pull_request_target_exceptions"] != [] - or not isinstance(workflow_trust["privileged_workflow_run_consumers"], dict) - ): - raise CandidateError(f"{context} has an invalid workflow trust contract") - if "privileged_artifact_handoffs" in workflow_trust: - handoffs = workflow_trust["privileged_artifact_handoffs"] - preceding = handoffs.get("validator_preceding_steps") if isinstance(handoffs, dict) else None - checkout = preceding[0] if isinstance(preceding, list) and len(preceding) == 1 else None - checkout_reference = checkout.get("uses") if isinstance(checkout, dict) else None - checkout_settings = checkout.get("with") if isinstance(checkout, dict) else None - checkout_release = ( - checkout_reference.rsplit("@", 1)[1] - if isinstance(checkout_reference, str) and "@" in checkout_reference - else None - ) - if ( - not isinstance(handoffs, dict) - or set(handoffs) - != { - "validator_command", - "validator_environment", - "validator_preceding_steps", - "validator_runner", - } - or not isinstance(handoffs["validator_command"], str) - or not handoffs["validator_command"].strip() - or handoffs["validator_environment"] - != [ - "ARTIFACT_DIRECTORY", - "EXPECTED_ARTIFACT_DIGEST", - "EXPECTED_ARTIFACT_ID", - "EXPECTED_SOURCE_RUN_ATTEMPT", - "EXPECTED_SOURCE_RUN_ID", - ] - or handoffs["validator_runner"] != "ubuntu-latest" - or not isinstance(checkout, dict) - or set(checkout) != {"uses", "with"} - or not isinstance(checkout_reference, str) - or not checkout_reference.startswith("actions/checkout@") - or not isinstance(checkout_release, str) - or not COMMIT_PATTERN.fullmatch(checkout_release) - or checkout_release not in allowed_releases.get("actions/checkout", {}) - or checkout_settings - != { - "fetch-depth": "0", - "persist-credentials": "false", - "ref": "${{ github.sha }}", - } - ): - raise CandidateError(f"{context} has an invalid privileged artifact handoff contract") - - repositories: set[str] = set() - for name, value in targets.items(): - target_keys = {"branch", "repository", "workflows"} - if isinstance(value, dict) and "public_audit" in value: - target_keys.add("public_audit") - target = require_exact_keys( - value, - target_keys, - f"{context} target {name}", - ) - repository = target["repository"] - branch = target["branch"] - workflows = target["workflows"] - if ( - not isinstance(repository, str) - or not QUALIFICATION_REPOSITORY_PATTERN.fullmatch(repository) - or repository in repositories - or not isinstance(branch, str) - or not QUALIFICATION_BRANCH_PATTERN.fullmatch(branch) - or not isinstance(workflows, list) - or not workflows - or ("public_audit" in target and not isinstance(target["public_audit"], bool)) - ): - raise CandidateError(f"{context} target {name} has an invalid immutable contract") - repositories.add(repository) - paths: set[str] = set() - checks: set[str] = set() - for value in workflows: - workflow_keys = {"matrix_independent", "path", "required_check"} - if isinstance(value, dict) and "action_policy_preflight" in value: - workflow_keys.add("action_policy_preflight") - workflow = require_exact_keys( - value, - workflow_keys, - f"{context} target {name} workflow", - ) - path = workflow["path"] - check = workflow["required_check"] - if ( - not isinstance(path, str) - or not QUALIFICATION_POLICY_WORKFLOW_PATH_PATTERN.fullmatch(path) - or path in paths - or not isinstance(check, str) - or not check.strip() - or check in checks - or not isinstance(workflow["matrix_independent"], bool) - or ( - "action_policy_preflight" in workflow - and not isinstance(workflow["action_policy_preflight"], bool) - ) - ): - raise CandidateError( - f"{context} target {name} workflow has an invalid immutable contract" - ) - paths.add(path) - checks.add(check) - return policy - - -def current_qualification_policy() -> dict[str, Any]: - path = Path(__file__).resolve().parents[1] / "qualification" / "policy.json" - try: - raw = path.read_bytes() - policy = json.loads(raw) - except (OSError, json.JSONDecodeError) as error: - raise CandidateError(f"cannot read current qualification policy: {error}") from error - if len(raw) > MAX_QUALIFICATION_POLICY_BYTES: - raise CandidateError("current qualification policy exceeds the 256 KiB limit") - return validate_qualification_policy_contract(policy, "current qualification policy") - - -def historical_qualification_policy(repository: Path, commit: str) -> dict[str, Any]: - ancestry = subprocess.run( - ["git", "merge-base", "--is-ancestor", commit, "HEAD"], - cwd=repository, - check=False, - capture_output=True, - ) - if ancestry.returncode: - raise CandidateError( - "recorded beta authorization qualification policy is not in controller history" - ) - process = subprocess.run( - ["git", "show", f"{commit}:qualification/policy.json"], - cwd=repository, - check=False, - capture_output=True, - ) - if process.returncode: - raise CandidateError( - "recorded beta authorization lacks its historical qualification policy" - ) - if len(process.stdout) > MAX_QUALIFICATION_POLICY_BYTES: - raise CandidateError("recorded historical qualification policy exceeds the 256 KiB limit") - try: - policy = json.loads(process.stdout) - except json.JSONDecodeError as error: - raise CandidateError( - "recorded historical qualification policy is not valid JSON" - ) from error - return validate_qualification_policy_contract( - policy, - "recorded historical qualification policy", - ) - - -def valid_recorded_action_releases( - value: Any, - *, - action_runtime: dict[str, Any] | None = None, -) -> bool: - if not isinstance(value, list): - return False - allowed_releases = action_runtime["allowed_releases"] if action_runtime is not None else None - supported_runtimes = ( - set(action_runtime["supported_javascript_runtimes"]) - if action_runtime is not None - else None - ) - identities: set[tuple[str, str]] = set() - for release in value: - repository = release.get("repository") if isinstance(release, dict) else None - approved = allowed_releases.get(repository) if allowed_releases is not None else None - immutable = isinstance(approved, dict) - expected_fields = { - "action", - "commit", - "reference", - "repository", - "runtime", - "workflows", - } - if immutable: - expected_fields.add("version") - if not isinstance(release, dict) or set(release) != expected_fields: - return False - action = release["action"] - reference = release["reference"] - runtime = release["runtime"] - workflows = release["workflows"] - action_directory_parts = action.split("/")[2:] if isinstance(action, str) else [] - if ( - not isinstance(repository, str) - or not ACTION_REPOSITORY_PATTERN.fullmatch(repository) - or not isinstance(action, str) - or not (action == repository or action.startswith(f"{repository}/")) - or any( - part in {".", ".."} or not ACTION_PATH_PART_PATTERN.fullmatch(part) - for part in action_directory_parts - ) - or not isinstance(release["commit"], str) - or not COMMIT_PATTERN.fullmatch(release["commit"]) - or not isinstance(reference, str) - or not reference.strip() - or reference != reference.strip() - or "${{" in reference - or (immutable and reference != release["commit"]) - or ( - immutable - and ( - not isinstance(release.get("version"), str) - or approved.get(reference) != release["version"] - ) - ) - or not isinstance(runtime, str) - or not ACTION_RUNTIME_PATTERN.fullmatch(runtime) - or not isinstance(workflows, list) - or not workflows - or any( - not isinstance(path, str) - or not QUALIFICATION_WORKFLOW_PATH_PATTERN.fullmatch(path) - for path in workflows - ) - or len(set(workflows)) != len(workflows) - or (action, reference) in identities - or ( - allowed_releases is not None - and reference not in (approved or {}) - ) - or ( - supported_runtimes is not None - and runtime.startswith("node") - and runtime not in supported_runtimes - ) - ): - return False - identities.add((action, reference)) - return True - - -def valid_recorded_workflow_trust(value: Any) -> bool: - if not isinstance(value, dict) or not value: - return False - expected_fields = {"containers", "external_actions", "local_actions", "privileged_jobs"} - for path, record in value.items(): - if ( - not isinstance(path, str) - or not QUALIFICATION_WORKFLOW_PATH_PATTERN.fullmatch(path) - or not isinstance(record, dict) - or set(record) != expected_fields - ): - return False - for field in expected_fields: - entries = record[field] - if ( - not isinstance(entries, list) - or len(entries) != len(set(entries)) - or any(not isinstance(entry, str) or not entry for entry in entries) - ): - return False - if any( - not re.fullmatch(r"[^@\s]+@[0-9a-f]{40}", specification) - for specification in record["external_actions"] - ): - return False - if any( - not re.fullmatch(r"docker://[^@\s]+@sha256:[0-9a-f]{64}", specification) - for specification in record["containers"] - ): - return False - if any(not specification.startswith("./") for specification in record["local_actions"]): - return False - return True - - -def validate_qualification_evidence( - qualification: Any, - request: dict[str, Any], - *, - current_policy: bool = True, - qualification_policy: dict[str, Any] | None = None, -) -> dict[str, Any]: - targets = qualification.get("targets") if isinstance(qualification, dict) else None - policy = ( - current_qualification_policy() - if current_policy - else ( - validate_qualification_policy_contract( - qualification_policy, - "recorded historical qualification policy", - ) - if qualification_policy is not None - else None - ) - ) - policy_targets = ( - { - name: target - for name, target in policy["targets"].items() - if target.get("public_audit", True) is not False - } - if policy is not None - else None - ) - if ( - not isinstance(qualification, dict) - or set(qualification) != {"schema", "targets"} - or qualification.get("schema") != QUALIFICATION_SCHEMA - or not isinstance(targets, dict) - or (policy_targets is not None and set(targets) != set(policy_targets)) - or ( - policy is None - and not set(targets) >= set(request["authorization"]["components"]) - ) - or any( - not isinstance(name, str) or not IDENTITY_PATTERN.fullmatch(name) - for name in targets - ) - ): - raise CandidateError("cited qualification evidence has an invalid authority shape") - target_contracts = ( - policy_targets - if policy_targets is not None - else {name: None for name in targets} - ) - for name, target_policy in target_contracts.items(): - target = targets.get(name) - protected = target.get("protected_checks") if isinstance(target, dict) else None - successful = target.get("successful_check_runs") if isinstance(target, dict) else None - expected_commit = request["authorization"]["components"].get(name, {}).get("commit") - expected_checks = ( - {workflow["required_check"] for workflow in target_policy["workflows"]} - if target_policy is not None - else None - ) - workflows = target.get("workflows") if isinstance(target, dict) else None - workflows_are_valid = ( - isinstance(workflows, list) - and bool(workflows) - and all( - isinstance(workflow, dict) - and set(workflow) == {"path", "required_check", "workflow_id"} - and isinstance(workflow["path"], str) - and QUALIFICATION_WORKFLOW_PATH_PATTERN.fullmatch(workflow["path"]) - and isinstance(workflow["required_check"], str) - and bool(workflow["required_check"].strip()) - and type(workflow["workflow_id"]) is int - and workflow["workflow_id"] > 0 - for workflow in workflows - ) - ) - recorded_workflows = ( - {(workflow["path"], workflow["required_check"]) for workflow in workflows} - if workflows_are_valid - else set() - ) - recorded_paths = ( - {workflow["path"] for workflow in workflows} - if workflows_are_valid - else set() - ) - recorded_checks = ( - {workflow["required_check"] for workflow in workflows} - if workflows_are_valid - else set() - ) - expected_workflows = ( - { - (f".github/workflows/{workflow['path']}", workflow["required_check"]) - for workflow in target_policy["workflows"] - } - if target_policy is not None - else None - ) - branch = target.get("branch") if isinstance(target, dict) else None - requires_workflow_trust = target_policy is not None and "workflow_trust" in policy - records_workflow_trust = requires_workflow_trust or ( - policy is None and isinstance(target, dict) and "workflow_trust" in target - ) - target_fields = { - "action_releases", - "branch", - "commit", - "protected_checks", - "successful_check_runs", - "workflows", - } - if records_workflow_trust: - target_fields.add("workflow_trust") - if ( - not isinstance(target, dict) - or set(target) != target_fields - or not isinstance(branch, str) - or not QUALIFICATION_BRANCH_PATTERN.fullmatch(branch) - or (target_policy is not None and branch != target_policy["branch"]) - or not isinstance(target.get("commit"), str) - or not COMMIT_PATTERN.fullmatch(target["commit"]) - or (expected_commit is not None and target.get("commit") != expected_commit) - or not isinstance(protected, list) - or not protected - or any(not isinstance(check, str) or not check for check in protected) - or (expected_checks is not None and set(protected) != expected_checks) - or not isinstance(successful, dict) - or set(successful) != set(protected) - or any(type(run_id) is not int or run_id < 1 for run_id in successful.values()) - or not valid_recorded_action_releases( - target.get("action_releases"), - action_runtime=policy["action_runtime"] if policy is not None else None, - ) - or ( - records_workflow_trust - and ( - not valid_recorded_workflow_trust(target.get("workflow_trust")) - or not recorded_paths <= set(target["workflow_trust"]) - ) - ) - or not workflows_are_valid - or set(protected) != recorded_checks - or len(protected) != len(recorded_checks) - or len(recorded_paths) != len(workflows) - or len(recorded_checks) != len(workflows) - or (target_policy is not None and len(workflows) != len(target_policy["workflows"])) - or (expected_workflows is not None and recorded_workflows != expected_workflows) - ): - if name in request["authorization"]["components"]: - raise CandidateError(f"qualification evidence does not prove intended {name} source commit") - raise CandidateError(f"qualification evidence for {name} has an invalid protected target record") - return qualification - - -def load_qualification_evidence(path: Path | None, request: dict[str, Any]) -> dict[str, Any]: - if path is None: - raise CandidateError("a new beta authorization requires fresh target qualification evidence") - try: - raw = path.read_bytes() - except FileNotFoundError as error: - raise CandidateError("a new beta authorization requires fresh target qualification evidence") from error - except OSError as error: - raise CandidateError(f"cannot read target qualification evidence {path}: {error}") from error - if len(raw) > MAX_QUALIFICATION_BYTES: - raise CandidateError("target qualification evidence exceeds the 2 MiB limit") - try: - qualification = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError("target qualification evidence is not valid JSON") from error - return validate_qualification_evidence(qualification, request) - - -def expected_qualification_commits(request: dict[str, Any]) -> dict[str, str]: - return { - name: identity["commit"] - for name, identity in request["authorization"]["components"].items() - } - - -def verify_qualified_heads_stable( - client: PublicClient, - request: dict[str, Any], - qualification: dict[str, Any], -) -> None: - for name, identity in request["authorization"]["components"].items(): - component = COMPONENTS[name] - branch = EXPECTED_DEFAULT_BRANCHES[name] - encoded_branch = urllib.parse.quote(branch, safe="") - current = client.json(f"https://api.github.com/repos/{component.repository}/branches/{encoded_branch}") - current_commit = current.get("commit") if isinstance(current, dict) else None - if ( - not isinstance(current, dict) - or not isinstance(current_commit, dict) - or current_commit.get("sha") != identity["commit"] - or qualification["targets"][name]["commit"] != identity["commit"] - ): - raise CandidateError(f"qualified {name} source changed before beta authorization publication") - - -def conformance_rank(tag: str) -> tuple[int, int]: - match = re.fullmatch(r"beta-conformance/.+/(?P[1-9][0-9]*)\.(?P[1-9][0-9]*)", tag) - if match is None: - raise CandidateError("conformance evidence tag has an invalid run identity") - return int(match.group("run")), int(match.group("attempt")) - - -def verify_conformance_evidence( - client: PublicClient, - request: dict[str, Any], - manifest: dict[str, Any], -) -> dict[str, Any]: - reference = request["evidence"]["conformance"] - if resolve_tag(client, CONTROL_REPOSITORY, reference["tag"]) != reference["commit"]: - raise CandidateError("cited conformance tag does not resolve to its requested immutable commit") - release = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/releases/tags/" - f"{urllib.parse.quote(reference['tag'], safe='')}" - ) - pseudo_plan = {"components": manifest["components"]} - try: - evidence = validate_conformance_release( - client, - pseudo_plan, - manifest, - release, - conformance_rank(reference["tag"]), - ) - except ConformanceError as error: - raise CandidateError(f"cited conformance Release is invalid: {error}") from error - if evidence is None or evidence["tag"] != reference["tag"]: - raise CandidateError("cited conformance Release is not retained passing exact-candidate evidence") - return {"commit": reference["commit"], **evidence} - - -def verify_continuity_evidence(client: PublicClient, request: dict[str, Any]) -> dict[str, Any]: - reference = request["evidence"]["continuity"] - complete = reference["complete"] - no_op = reference["no_op"] - if resolve_tag(client, CONTROL_REPOSITORY, complete["tag"]) != complete["commit"]: - raise CandidateError("cited continuity completion tag moved or is missing") - if resolve_tag(client, CONTROL_REPOSITORY, no_op["tag"]) != no_op["commit"]: - raise CandidateError("cited continuity no-op tag moved or is missing") - plan = read_public_record(client, complete["tag"], complete["commit"], "release-plan.json") - validate_plan(plan) - expected_complete = f"beta-continuity/{plan['plan']}/complete" - expected_no_op = f"beta-continuity/{plan['plan']}/no-op-confirmed" - if complete["tag"] != expected_complete or no_op["tag"] != expected_no_op: - raise CandidateError("cited continuity phase tags differ from their exact release plan") - try: - completion = exact_completion_authority( - client, - load_config(Path(__file__).resolve().parents[1] / "beta-continuity" / "config.json"), - plan, - complete["commit"], - no_op["commit"], - ) - except (ContinuityError, ConformanceError) as error: - raise CandidateError(f"cited continuity completion is invalid: {error}") from error - return { - "complete": complete, - "no_op": no_op, - "plan": completion["plan_record"], - } - - -def authority_issue_and_decision( - client: PublicClient, - request: dict[str, Any], - *, - actor: str, -) -> dict[str, Any]: - issue = client.json(f"https://api.github.com/repos/{CONTROL_REPOSITORY}/issues/{AUTHORITY_ISSUE}") - labels = { - label.get("name") for label in issue.get("labels", []) if isinstance(label, dict) - } if isinstance(issue, dict) else set() - body = issue.get("body") if isinstance(issue, dict) else None - milestone = issue.get("milestone") if isinstance(issue, dict) else None - if ( - not isinstance(issue, dict) - or issue.get("number") != AUTHORITY_ISSUE - or issue.get("state") != "open" - or issue.get("html_url") != AUTHORITY_ISSUE_URL - or "pull_request" in issue - or not labels >= REQUIRED_AUTHORITY_LABELS - or not isinstance(body, str) - or f"" not in body - or not isinstance(milestone, dict) - or milestone.get("title") != "2.0 beta" - ): - raise CandidateError("the public beta authority issue is not an open, classified authorization gate") - - comment_id = request["evidence"]["decision"]["comment"] - comment = client.json(f"https://api.github.com/repos/{CONTROL_REPOSITORY}/issues/comments/{comment_id}") - comment_body = comment.get("body") if isinstance(comment, dict) else None - marker_matches = list(DECISION_MARKER.finditer(comment_body or "")) - expected_digest = manifest_digest(request["authorization"]) - author = user_identity(comment.get("user") if isinstance(comment, dict) else None, "beta decision comment") - if ( - not isinstance(comment, dict) - or comment.get("id") != comment_id - or comment.get("issue_url") != f"https://api.github.com/repos/{CONTROL_REPOSITORY}/issues/{AUTHORITY_ISSUE}" - or comment.get("html_url") != f"{AUTHORITY_ISSUE_URL}#issuecomment-{comment_id}" - or author["login"] != actor - or comment.get("author_association") not in {"OWNER", "MEMBER", "COLLABORATOR"} - or len(marker_matches) != 1 - or marker_matches[0].group("digest") != expected_digest - ): - raise CandidateError("the cited product-owner comment does not authorize this exact beta release plan") - return { - "repository": CONTROL_REPOSITORY, - "issue": AUTHORITY_ISSUE, - "issue_url": AUTHORITY_ISSUE_URL, - "comment": comment_id, - "comment_url": comment["html_url"], - "author": author, - "body_sha256": hashlib.sha256(comment_body.encode()).hexdigest(), - } - - -def public_backlog_evidence(client: PublicClient) -> dict[str, Any]: - policy = json.loads( - (Path(__file__).resolve().parents[1] / "issue-authority" / "policy.json").read_bytes() - ) - repositories = [f"durable-workflow/{name}" for name in policy["repositories"]] - unresolved: list[dict[str, Any]] = [] - seen: set[tuple[str, int]] = set() - for repository in repositories: - for priority in ("priority:P0", "priority:P1"): - for page in range(1, MAX_ISSUE_PAGES + 1): - query = urllib.parse.urlencode( - {"state": "open", "labels": priority, "per_page": 100, "page": page} - ) - payload = client.json(f"https://api.github.com/repos/{repository}/issues?{query}") - if not isinstance(payload, list): - raise CandidateError(f"public backlog query for {repository} returned invalid evidence") - for issue in payload: - if not isinstance(issue, dict): - raise CandidateError(f"public backlog query for {repository} contains an invalid issue") - if "pull_request" in issue: - continue - number = issue.get("number") - if type(number) is not int or number < 1: - raise CandidateError(f"public backlog query for {repository} contains an invalid issue") - key = (repository, number) - if key in seen: - continue - seen.add(key) - if key == (CONTROL_REPOSITORY, AUTHORITY_ISSUE): - continue - unresolved.append( - { - "repository": repository, - "number": number, - "url": issue.get("html_url"), - "priority": priority, - } - ) - if len(payload) < 100: - break - else: - raise CandidateError(f"public backlog query for {repository} exceeded its page limit") - if unresolved: - identities = [f"{item['repository']}#{item['number']}" for item in unresolved] - raise CandidateError(f"unresolved public P0/P1 work blocks beta authorization: {identities}") - return { - "repositories": repositories, - "allowed_authorization_gate": { - "repository": CONTROL_REPOSITORY, - "number": AUTHORITY_ISSUE, - "url": AUTHORITY_ISSUE_URL, - }, - "unresolved_p0_p1": [], - } - - -def verify_requested_refs_stable(client: PublicClient, request: dict[str, Any]) -> None: - references = [ - request["evidence"]["candidate"], - request["evidence"]["conformance"], - request["evidence"]["continuity"]["complete"], - request["evidence"]["continuity"]["no_op"], - ] - for reference in references: - if resolve_tag(client, CONTROL_REPOSITORY, reference["tag"]) != reference["commit"]: - raise CandidateError(f"public evidence tag {reference['tag']} changed before authorization publication") - - -def build_evidence( - client: PublicClient, - request: dict[str, Any], - qualification: dict[str, Any], - *, - actor: str, - run_id: int, - run_attempt: int, - workflow_ref: str, - workflow_commit: str, -) -> dict[str, Any]: - if ( - not LOGIN_PATTERN.fullmatch(actor) - or run_id < 1 - or run_attempt < 1 - or workflow_ref != AUTHORIZATION_WORKFLOW_REF - or not COMMIT_PATTERN.fullmatch(workflow_commit) - ): - raise CandidateError("beta authorization workflow dispatch identity is invalid") - protection = protected_environment_evidence(client) - approval = protected_run_evidence( - client, - actor=actor, - run_id=run_id, - run_attempt=run_attempt, - workflow_commit=workflow_commit, - environment_protection=protection, - ) - decision = authority_issue_and_decision(client, request, actor=actor) - candidate_manifest, candidate = verify_candidate_evidence(client, request) - validate_qualification_evidence(qualification, request, current_policy=False) - conformance = verify_conformance_evidence(client, request, candidate_manifest) - continuity = verify_continuity_evidence(client, request) - backlog = public_backlog_evidence(client) - verify_requested_refs_stable(client, request) - verify_qualified_heads_stable(client, request, qualification) - return { - "schema": EVIDENCE_SCHEMA, - "authorization_sha256": manifest_digest(request["authorization"]), - "request_sha256": manifest_digest(request), - "decision": decision, - "candidate": candidate, - "qualification": { - "path": "target-qualification-evidence.json", - "sha256": manifest_digest(qualification), - }, - "conformance": conformance, - "continuity": continuity, - "backlog": backlog, - "github_authority": { - "actor": actor, - "repository": CONTROL_REPOSITORY, - "workflow_ref": workflow_ref, - "workflow_commit": workflow_commit, - "run_id": run_id, - "run_attempt": run_attempt, - "run_url": f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - "environment": AUTHORIZATION_ENVIRONMENT, - "environment_protection": protection, - "environment_approval": approval, - }, - } - - -def validate_recorded_evidence( - value: Any, - request: dict[str, Any], - qualification: dict[str, Any], - repository: Path | None = None, -) -> dict[str, Any]: - evidence = require_exact_keys( - value, - { - "schema", - "authorization_sha256", - "request_sha256", - "decision", - "candidate", - "qualification", - "conformance", - "continuity", - "backlog", - "github_authority", - }, - "recorded beta authorization evidence", - ) - if ( - evidence["schema"] != EVIDENCE_SCHEMA - or evidence["authorization_sha256"] != manifest_digest(request["authorization"]) - or evidence["request_sha256"] != manifest_digest(request) - ): - raise CandidateError("existing beta authorization was created from a different request") - decision = require_exact_keys( - evidence["decision"], - {"repository", "issue", "issue_url", "comment", "comment_url", "author", "body_sha256"}, - "recorded decision evidence", - ) - authority = require_exact_keys( - evidence["github_authority"], - { - "actor", - "repository", - "workflow_ref", - "workflow_commit", - "run_id", - "run_attempt", - "run_url", - "environment", - "environment_protection", - "environment_approval", - }, - "recorded GitHub authority", - ) - actor = authority["actor"] - if ( - authority["repository"] != CONTROL_REPOSITORY - or authority["workflow_ref"] != AUTHORIZATION_WORKFLOW_REF - or authority["environment"] != AUTHORIZATION_ENVIRONMENT - or not isinstance(actor, str) - or not LOGIN_PATTERN.fullmatch(actor) - or not isinstance(authority["workflow_commit"], str) - or not COMMIT_PATTERN.fullmatch(authority["workflow_commit"]) - or type(authority["run_id"]) is not int - or authority["run_id"] < 1 - or type(authority["run_attempt"]) is not int - or authority["run_attempt"] < 1 - or authority["run_url"] - != f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{authority['run_id']}" - ): - raise CandidateError("existing beta authorization has invalid GitHub workflow authority") - if ( - decision["repository"] != CONTROL_REPOSITORY - or decision["issue"] != AUTHORITY_ISSUE - or decision["issue_url"] != AUTHORITY_ISSUE_URL - or decision["comment"] != request["evidence"]["decision"]["comment"] - or decision["comment_url"] != f"{AUTHORITY_ISSUE_URL}#issuecomment-{decision['comment']}" - or not SHA256_PATTERN.fullmatch(str(decision["body_sha256"])) - or user_identity(decision["author"], "recorded decision author")["login"] != actor - ): - raise CandidateError("existing beta authorization has invalid product-owner decision evidence") - - candidate = require_exact_keys( - evidence["candidate"], - {"tag", "commit", "manifest_sha256", "verification_sha256"}, - "recorded candidate evidence", - ) - if ( - {key: candidate[key] for key in ("tag", "commit")} != request["evidence"]["candidate"] - or not SHA256_PATTERN.fullmatch(str(candidate["manifest_sha256"])) - or not SHA256_PATTERN.fullmatch(str(candidate["verification_sha256"])) - ): - raise CandidateError("existing beta authorization has invalid candidate evidence") - if evidence["qualification"] != { - "path": "target-qualification-evidence.json", - "sha256": manifest_digest(qualification), - }: - raise CandidateError("existing beta authorization has different qualification evidence") - historical_policy = historical_qualification_policy( - repository if repository is not None else Path.cwd(), - authority["workflow_commit"], - ) - validate_qualification_evidence( - qualification, - request, - current_policy=False, - qualification_policy=historical_policy, - ) - conformance = require_exact_keys( - evidence["conformance"], - {"tag", "commit", "release", "run"}, - "recorded conformance evidence", - ) - expected_conformance = request["evidence"]["conformance"] - run_id, run_attempt = conformance_rank(conformance["tag"]) - if ( - {key: conformance[key] for key in ("tag", "commit")} != expected_conformance - or not isinstance(conformance["release"], str) - or not conformance["release"].startswith( - f"https://github.com/{CONTROL_REPOSITORY}/releases/tag/" - ) - or conformance["run"] - != { - "repository": CONTROL_REPOSITORY, - "run_id": run_id, - "run_attempt": run_attempt, - "evidence_tag": conformance["tag"], - } - ): - raise CandidateError("existing beta authorization has invalid retained conformance evidence") - continuity = require_exact_keys( - evidence["continuity"], {"complete", "no_op", "plan"}, "recorded continuity evidence" - ) - if ( - continuity["complete"] != request["evidence"]["continuity"]["complete"] - or continuity["no_op"] != request["evidence"]["continuity"]["no_op"] - ): - raise CandidateError("existing beta authorization has different continuity evidence") - require_git_record(continuity["plan"], "recorded continuity plan", digest=True) - - backlog = require_exact_keys( - evidence["backlog"], - {"repositories", "allowed_authorization_gate", "unresolved_p0_p1"}, - "recorded backlog evidence", - ) - recorded_repositories = backlog["repositories"] - minimum_repositories = {CONTROL_REPOSITORY} | { - component.repository for component in COMPONENTS.values() - } - if ( - not isinstance(recorded_repositories, list) - or len(recorded_repositories) != len(set(recorded_repositories)) - or any( - not isinstance(repository, str) - or not repository.startswith("durable-workflow/") - for repository in recorded_repositories - ) - or not set(recorded_repositories) >= minimum_repositories - or backlog["unresolved_p0_p1"] != [] - or backlog["allowed_authorization_gate"] - != {"repository": CONTROL_REPOSITORY, "number": AUTHORITY_ISSUE, "url": AUTHORITY_ISSUE_URL} - ): - raise CandidateError("existing beta authorization has invalid final backlog evidence") - protection = require_exact_keys( - authority["environment_protection"], - { - "custom_branch_policies", - "deployment_branch_policy", - "environment_id", - "environment_url", - "prevent_self_review", - "required_reviewer_rule_ids", - "required_reviewer_user_ids", - }, - "recorded environment protection", - ) - activity_url, api_url = environment_urls() - custom_policies = protection["custom_branch_policies"] - reviewer_rule_ids = protection["required_reviewer_rule_ids"] - reviewer_user_ids = protection["required_reviewer_user_ids"] - if ( - protection["deployment_branch_policy"] - != {"custom_branch_policies": True, "protected_branches": False} - or type(protection["environment_id"]) is not int - or protection["environment_id"] < 1 - or not isinstance(reviewer_rule_ids, list) - or not reviewer_rule_ids - or any(type(rule_id) is not int or rule_id < 1 for rule_id in reviewer_rule_ids) - or reviewer_rule_ids != sorted(set(reviewer_rule_ids)) - or reviewer_user_ids != [PRODUCT_OWNER_REVIEWER_ID] - or protection["prevent_self_review"] is not False - or not isinstance(custom_policies, list) - or len(custom_policies) != 1 - or not isinstance(custom_policies[0], dict) - or set(custom_policies[0]) != {"id", "name"} - or type(custom_policies[0]["id"]) is not int - or custom_policies[0]["id"] < 1 - or custom_policies[0]["name"] != "main" - or protection["environment_url"] != activity_url - ): - raise CandidateError("existing beta authorization lacks protected environment evidence") - approval = require_exact_keys( - authority["environment_approval"], - {"comment", "environments", "run_attempt", "run_id", "state", "user"}, - "recorded environment approval", - ) - approved_environments = approval["environments"] - approved_environment = ( - approved_environments[0] - if isinstance(approved_environments, list) - and len(approved_environments) == 1 - and isinstance(approved_environments[0], dict) - else {} - ) - expected_environment = { - "html_url": activity_url, - "id": protection["environment_id"], - "name": AUTHORIZATION_ENVIRONMENT, - "node_id": approved_environment.get("node_id"), - "url": api_url, - } - if ( - approval["state"] != "approved" - or approval["run_id"] != authority["run_id"] - or approval["run_attempt"] != authority["run_attempt"] - or not isinstance(approval["comment"], str) - or approved_environment != expected_environment - or not isinstance(expected_environment["node_id"], str) - or not expected_environment["node_id"] - ): - raise CandidateError("existing beta authorization lacks an approved environment review") - approving_reviewer = user_identity(approval["user"], "recorded approving reviewer") - if approving_reviewer["id"] not in reviewer_user_ids: - raise CandidateError("existing beta authorization has an unrecognized approving reviewer") - return evidence - - -def load_recorded_evidence( - raw: bytes, - request: dict[str, Any], - qualification: dict[str, Any], - repository: Path, -) -> dict[str, Any]: - if len(raw) > MAX_EVIDENCE_BYTES: - raise CandidateError("recorded beta authorization evidence exceeds the 1 MiB limit") - try: - value = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError("existing beta authorization evidence is not valid JSON") from error - return validate_recorded_evidence(value, request, qualification, repository) - - -def existing_authorization( - repository: Path, - request: dict[str, Any], - *, - remote: str, - authoritative_authorization: Path | None, - authoritative_evidence: Path | None, - authoritative_qualification: Path | None, -) -> dict[str, str] | None: - authorization = request["authorization"] - tag = f"{AUTHORIZATION_TAG_PREFIX}{authorization['candidate']}" - existing_ref = fetch_existing_record(repository, remote, tag) - if existing_ref is None: - return None - existing_authorization = read_record_file(repository, existing_ref, "beta-authorization.json") - if existing_authorization != canonical_json(authorization): - raise CandidateError(f"beta authorization {authorization['candidate']} is immutable and differs") - existing_evidence = read_record_file(repository, existing_ref, "beta-authorization-evidence.json") - existing_qualification = read_record_file(repository, existing_ref, "target-qualification-evidence.json") - if len(existing_qualification) > MAX_QUALIFICATION_BYTES: - raise CandidateError("existing target qualification evidence exceeds the 2 MiB limit") - try: - qualification = json.loads(existing_qualification) - except json.JSONDecodeError as error: - raise CandidateError("existing target qualification evidence is not valid JSON") from error - load_recorded_evidence(existing_evidence, request, qualification, repository) - if authoritative_authorization is not None: - authoritative_authorization.write_bytes(existing_authorization) - if authoritative_evidence is not None: - authoritative_evidence.write_bytes(existing_evidence) - if authoritative_qualification is not None: - authoritative_qualification.write_bytes(existing_qualification) - return { - "status": "existing", - "candidate": authorization["candidate"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - - -def check_authorization(repository: Path, request_path: Path, *, remote: str) -> dict[str, str]: - request = load_request(request_path) - existing = existing_authorization( - repository, - request, - remote=remote, - authoritative_authorization=None, - authoritative_evidence=None, - authoritative_qualification=None, - ) - if existing is not None: - return existing - authorization = request["authorization"] - return { - "status": "new", - "candidate": authorization["candidate"], - "tag": f"{AUTHORIZATION_TAG_PREFIX}{authorization['candidate']}", - } - - -def record_authorization( - repository: Path, - request_path: Path, - *, - qualification_path: Path | None, - remote: str, - authoritative_authorization: Path, - authoritative_evidence: Path, - authoritative_qualification: Path, - client: PublicClient, - actor: str, - run_id: int, - run_attempt: int, - workflow_ref: str, - workflow_commit: str, -) -> dict[str, str]: - request = load_request(request_path) - existing = existing_authorization( - repository, - request, - remote=remote, - authoritative_authorization=authoritative_authorization, - authoritative_evidence=authoritative_evidence, - authoritative_qualification=authoritative_qualification, - ) - if existing is not None: - return existing - - authorization = request["authorization"] - qualification = load_qualification_evidence(qualification_path, request) - evidence = build_evidence( - client, - request, - qualification, - actor=actor, - run_id=run_id, - run_attempt=run_attempt, - workflow_ref=workflow_ref, - workflow_commit=workflow_commit, - ) - validate_recorded_evidence(evidence, request, qualification, repository) - canonical_authorization = canonical_json(authorization) - canonical_evidence = canonical_json(evidence) - canonical_qualification = canonical_json(qualification) - tag = f"{AUTHORIZATION_TAG_PREFIX}{authorization['candidate']}" - with tempfile.NamedTemporaryFile(prefix="beta-authorization-index-", delete=False) as index: - index_path = Path(index.name) - try: - env = os.environ.copy() - env["GIT_INDEX_FILE"] = str(index_path) - index_path.unlink(missing_ok=True) - run_git(["read-tree", "--empty"], cwd=repository, env=env) - for filename, content in ( - ("beta-authorization.json", canonical_authorization), - ("beta-authorization-evidence.json", canonical_evidence), - ("target-qualification-evidence.json", canonical_qualification), - ): - blob = subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=repository, - env=env, - input=content, - check=True, - stdout=subprocess.PIPE, - ).stdout.decode().strip() - run_git( - ["update-index", "--add", "--cacheinfo", f"100644,{blob},{filename}"], - cwd=repository, - env=env, - ) - tree = run_git(["write-tree"], cwd=repository, env=env) - commit_env = env | { - "GIT_AUTHOR_NAME": "Durable Workflow Beta Authorizer", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Beta Authorizer", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - } - commit = subprocess.run( - ["git", "commit-tree", tree], - cwd=repository, - env=commit_env, - input=f"Record beta authorization {authorization['candidate']}\n", - check=True, - text=True, - capture_output=True, - ).stdout.strip() - push = subprocess.run( - ["git", "push", remote, f"{commit}:refs/tags/{tag}"], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if push.returncode: - recovered = existing_authorization( - repository, - request, - remote=remote, - authoritative_authorization=authoritative_authorization, - authoritative_evidence=authoritative_evidence, - authoritative_qualification=authoritative_qualification, - ) - if recovered is None: - raise CandidateError(f"cannot publish immutable beta authorization: {push.stderr.strip()}") - return recovered - finally: - index_path.unlink(missing_ok=True) - authoritative_authorization.write_bytes(canonical_authorization) - authoritative_evidence.write_bytes(canonical_evidence) - authoritative_qualification.write_bytes(canonical_qualification) - return { - "status": "created", - "candidate": authorization["candidate"], - "tag": tag, - "commit": commit, - } - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - commands = parser.add_subparsers(dest="command", required=True) - - validate = commands.add_parser("validate") - validate.add_argument("request", type=Path) - validate.add_argument("destination", type=Path) - - expected = commands.add_parser("expected-commits") - expected.add_argument("request", type=Path) - expected.add_argument("destination", type=Path) - - check = commands.add_parser("check") - check.add_argument("request", type=Path) - check.add_argument("--repository", type=Path, default=Path.cwd()) - check.add_argument("--remote", default="origin") - check.add_argument("--github-output", type=Path) - - record = commands.add_parser("record") - record.add_argument("request", type=Path) - record.add_argument("--qualification", type=Path) - record.add_argument("--repository", type=Path, default=Path.cwd()) - record.add_argument("--remote", default="origin") - record.add_argument("--authoritative-authorization", required=True, type=Path) - record.add_argument("--authoritative-evidence", required=True, type=Path) - record.add_argument("--authoritative-qualification", required=True, type=Path) - record.add_argument("--actor", required=True) - record.add_argument("--run-id", required=True, type=int) - record.add_argument("--run-attempt", required=True, type=int) - record.add_argument("--workflow-ref", required=True) - record.add_argument("--workflow-commit", required=True) - record.add_argument("--github-output", type=Path) - - arguments = parser.parse_args() - try: - if arguments.command == "validate": - request = load_request(arguments.request) - arguments.destination.write_bytes(canonical_json(request)) - elif arguments.command == "expected-commits": - request = load_request(arguments.request) - arguments.destination.write_bytes(canonical_json(expected_qualification_commits(request))) - elif arguments.command == "check": - result = check_authorization( - arguments.repository, - arguments.request, - remote=arguments.remote, - ) - write_github_output(arguments.github_output, result) - print(json.dumps(result, sort_keys=True)) - else: - result = record_authorization( - arguments.repository, - arguments.request, - qualification_path=arguments.qualification, - remote=arguments.remote, - authoritative_authorization=arguments.authoritative_authorization, - authoritative_evidence=arguments.authoritative_evidence, - authoritative_qualification=arguments.authoritative_qualification, - client=PublicClient(os.environ.get("GITHUB_TOKEN")), - actor=arguments.actor, - run_id=arguments.run_id, - run_attempt=arguments.run_attempt, - workflow_ref=arguments.workflow_ref, - workflow_commit=arguments.workflow_commit, - ) - write_github_output(arguments.github_output, result) - print(json.dumps(result, sort_keys=True)) - except CandidateError as error: - print(f"beta authorization failed: {error}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/beta_candidate.py b/scripts/beta_candidate.py deleted file mode 100644 index 5782077..0000000 --- a/scripts/beta_candidate.py +++ /dev/null @@ -1,1694 +0,0 @@ -#!/usr/bin/env python3 -"""Validate, verify, and immutably record a public beta candidate tuple.""" - -from __future__ import annotations - -import argparse -import base64 -import binascii -import contextlib -import datetime as dt -import email.utils -import errno -import hashlib -import http.client -import json -import os -import re -import shutil -import subprocess -import sys -import tarfile -import tempfile -import time -import urllib.error -import urllib.parse -import urllib.request -import zipfile -from collections.abc import Callable, Mapping -from dataclasses import dataclass -from pathlib import Path -from typing import Any - -# GitHub Actions invokes this file directly from the repository root. In that -# mode Python adds scripts/, rather than the repository root, to sys.path. -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.packagist_metadata import PackagistMetadataError, exact_package_version - -SCHEMA = "durable-workflow.beta-candidate/v2" -LEGACY_SCHEMA = "durable-workflow.beta-candidate/v1" -VERIFICATION_SCHEMA = "durable-workflow.beta-candidate-verification/v2" -LEGACY_VERIFICATION_SCHEMA = "durable-workflow.beta-candidate-verification/v1" -LEGACY_MANIFEST_DIGESTS = frozenset( - { - "1104bbb8d40c1acd8062a15b7fd385966bdb4428a533ef5d947820944e85d294", - "26084d2e8f12faeebb7b09bf3de41e1dd65f8afdaa5d38f786fcbcd1bf770f15", - "2fbeda4e3368edf7cda7bcc749359d4bcdf7fcccca289e32316782613a84b4a6", - "43243594ba34ff220365d9c514e6a54b93789788676ca8b1d678b679afa6c1c5", - "43f535585fc225a2d2cfcf347ec45c78daae1ff2d244e422d8952347d1ef4a95", - "47fdba440315d2f05b16c66b5ef37139db4b306fbf8e168c5e15a27a26592742", - "561c4544a8b9305056f863f20f84791921952f1dbcff97929805a5dac01027fb", - "5e083e07e6abecbb0547466812f866a3650039b210dbe1d486ca98528479cd29", - "5fce05154eb66ee1551bad7eadda0911ed867ea3cedd8f37d091df0649bbc5db", - "80282957f3a417af6025b4ad4abf461b03388d799ee570de128f25a676909f70", - "81f3346f59f414b29fc88efc993dd3c3d5ee759a819d9f82720118981776f4f3", - "a4b72535496346aa47bf0a8ebedd84308231776c9df015dd913095163eda3ce2", - "c6821caad478ee255b9e8cb70638d96ec46a70bd5afc54636861fe71840a6cbe", - "d70e45ce40c0959f38345abb1b6e53e2da8026832a918a640fe69d48b534f1e3", - "dd5e8d3bb248c2b1b292b5badf29376cc5c5b6fa73dedfb343e33987a2b6d7a2", - "ec5a4c032dfcaa73878b4126af4e2b2bb90c09fe46def19314d66be441b16174", - "ffee151da2e3835bf32bc2ef05dc5d0f3261e45c13da0ef6e2d6e6f19c1ca50f", - } -) -CANDIDATE_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,62}$") -VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?$") -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -TAG_PREFIX = "beta-candidate/" -GITHUB_READ_MAX_ATTEMPTS = 5 -GITHUB_READ_RETRY_BASE_SECONDS = 2.0 -GITHUB_READ_RETRY_MAX_SECONDS = 120.0 -GITHUB_READ_REQUEST_TIMEOUT_SECONDS = 30.0 -GITHUB_READ_DEADLINE_SECONDS = 600.0 -INFRASTRUCTURE_EXIT_CODE = 75 -VERIFICATION_MAX_BYTES = 256 * 1024 -MAX_URL_LENGTH = 2048 -MAX_TEXT_LENGTH = 4096 -MAX_PYPI_FILES = 32 -PHAR_BUILD_INFO_MAX_BYTES = 16 * 1024 -RFC3339_SECONDS_PATTERN = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$") -PYPI_PRERELEASE_LABELS = {"alpha": "a", "beta": "b", "rc": "rc"} - - -@dataclass(frozen=True) -class Component: - repository: str - distribution: str - package: str - - -COMPONENTS = { - "workflow": Component("durable-workflow/workflow", "composer", "durable-workflow/workflow"), - "waterline": Component("durable-workflow/waterline", "composer", "durable-workflow/waterline"), - "server": Component("durable-workflow/server", "oci", "docker.io/durableworkflow/server"), - "cli": Component("durable-workflow/cli", "github-release", "durable-workflow/cli"), - "sdk-php": Component("durable-workflow/sdk-php", "composer", "durable-workflow/sdk"), - "sdk-python": Component("durable-workflow/sdk-python", "pypi", "durable-workflow"), - "sdk-rust": Component("durable-workflow/sdk-rust", "crates.io", "durable-workflow"), -} - -WATERLINE_SERVICE = Component( - "durable-workflow/waterline", - "oci", - "docker.io/durableworkflow/waterline", -) - -CLI_ASSETS = { - "dw.phar", - "dw-linux-x86_64", - "dw-linux-aarch64", - "dw-macos-aarch64", - "dw-windows-x86_64.exe", - "dw.rb", - "install.sh", - "install.ps1", - "verify-release.sh", - "SHA256SUMS", -} - - -class CandidateError(RuntimeError): - """A candidate does not satisfy the public identity contract.""" - - -class PublicInfrastructureError(RuntimeError): - """A bounded set of transient GitHub public-read attempts was exhausted.""" - - def __init__( - self, - endpoint_class: str, - attempts: int, - *, - reason: str, - failure: str | None = None, - ) -> None: - evidence = [ - "classification=github-read-transient", - f"endpoint_class={endpoint_class}", - f"attempts={attempts}", - f"reason={reason}", - ] - if failure is not None: - evidence.append(failure) - super().__init__(f"GitHub public read transient failure exhausted ({', '.join(evidence)})") - - -class _TransientGitHubRead(RuntimeError): - """One GitHub public-read attempt encountered retryable infrastructure.""" - - def __init__(self, evidence: str, headers: Mapping[str, str] | None = None) -> None: - self.evidence = evidence - self.headers = headers or {} - super().__init__(evidence) - - -def canonical_json(value: Any) -> bytes: - return (json.dumps(value, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - - -def load_manifest(path: Path) -> dict[str, Any]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read candidate manifest {path}: {error}") from error - if len(raw) > 64 * 1024: - raise CandidateError("candidate manifest exceeds the 64 KiB limit") - try: - value = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"candidate manifest is not valid JSON: {error}") from error - validate_manifest(value) - return value - - -def validate_manifest(value: Any) -> None: - _validate_manifest(value, {SCHEMA}) - - -def validate_recorded_manifest(value: Any) -> None: - """Validate a current manifest or an exact pre-service manifest.""" - _validate_manifest(value, {LEGACY_SCHEMA, SCHEMA}) - if value["schema"] == LEGACY_SCHEMA and manifest_digest(value) not in LEGACY_MANIFEST_DIGESTS: - raise CandidateError("legacy candidate manifest is not an exact recorded historical contract") - - -def _validate_manifest(value: Any, schemas: set[str]) -> None: - if not isinstance(value, dict): - raise CandidateError("candidate manifest must be a JSON object") - expected_top = {"schema", "candidate", "components"} - if set(value) != expected_top: - raise CandidateError(f"candidate manifest keys must be exactly {sorted(expected_top)}") - if value["schema"] not in schemas: - raise CandidateError(f"candidate manifest schema must be one of {sorted(schemas)}") - candidate = value["candidate"] - if not isinstance(candidate, str) or not CANDIDATE_PATTERN.fullmatch(candidate): - raise CandidateError("candidate must be 1-63 lowercase letters, digits, dots, underscores, or hyphens") - components = value["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise CandidateError(f"components must be exactly {sorted(COMPONENTS)}") - for name, identity in components.items(): - if not isinstance(identity, dict) or set(identity) != {"version", "commit"}: - raise CandidateError(f"components.{name} must contain only version and commit") - version = identity["version"] - commit = identity["commit"] - if not isinstance(version, str) or not VERSION_PATTERN.fullmatch(version): - raise CandidateError(f"components.{name}.version must be an exact SemVer release") - if not isinstance(commit, str) or not COMMIT_PATTERN.fullmatch(commit): - raise CandidateError(f"components.{name}.commit must be a full lowercase Git commit identity") - - -def manifest_digest(manifest: dict[str, Any]) -> str: - return hashlib.sha256(canonical_json(manifest)).hexdigest() - - -def canonical_cli_embedded_identity(version: str, commit: str) -> str: - """Return the writer-reproducible portion of the verified PHAR identity.""" - return f"dw {version.lstrip('v')} (commit {commit[:12]})" - - -def canonical_pypi_version(version: str) -> str: - """Translate the supported SemVer prerelease spelling to PEP 440.""" - match = re.fullmatch( - r"([0-9]+\.[0-9]+\.[0-9]+)-(alpha|beta|rc)\.([1-9][0-9]*)", - version, - ) - if match is None: - return version - base, channel, sequence = match.groups() - return f"{base}{PYPI_PRERELEASE_LABELS[channel]}{sequence}" - - -def _require_exact_keys(value: Any, keys: set[str], context: str) -> dict[str, Any]: - if not isinstance(value, dict) or set(value) != keys: - raise CandidateError(f"{context} keys must be exactly {sorted(keys)}") - return value - - -def _require_bounded_string(value: Any, context: str, *, maximum: int = MAX_TEXT_LENGTH) -> str: - if not isinstance(value, str) or not value or len(value) > maximum or "\x00" in value: - raise CandidateError(f"{context} must be a non-empty string of at most {maximum} characters") - return value - - -def _require_https_url(value: Any, context: str) -> str: - url = _require_bounded_string(value, context, maximum=MAX_URL_LENGTH) - parsed = urllib.parse.urlsplit(url) - if parsed.scheme != "https" or not parsed.netloc or parsed.username is not None or parsed.password is not None: - raise CandidateError(f"{context} must be a bounded public HTTPS URL") - return url - - -def _require_positive_integer(value: Any, context: str, *, allow_zero: bool = False) -> int: - minimum = 0 if allow_zero else 1 - if type(value) is not int or value < minimum or value > 2**63 - 1: - raise CandidateError(f"{context} must be an integer between {minimum} and {2**63 - 1}") - return value - - -def _validate_download_evidence(value: Any, context: str) -> None: - download = _require_exact_keys(value, {"url", "size", "sha256"}, context) - _require_https_url(download["url"], f"{context}.url") - _require_positive_integer(download["size"], f"{context}.size") - if not re.fullmatch(r"[0-9a-f]{64}", str(download["sha256"])): - raise CandidateError(f"{context}.sha256 must be a lowercase SHA-256 identity") - - -def _validate_source_evidence(value: Any, component: Component, version: str, commit: str, context: str) -> None: - source = _require_exact_keys(value, {"repository", "tag", "tag_object", "commit", "url"}, context) - if ( - source["repository"] != component.repository - or source["tag"] != version - or source["commit"] != commit - or not COMMIT_PATTERN.fullmatch(str(source["tag_object"])) - or source["url"] != f"https://github.com/{component.repository}/tree/{version}" - ): - raise CandidateError(f"{context} does not prove the planned source tag and commit") - - -def _validate_composer_evidence(value: Any, component: Component, _version: str, commit: str, context: str) -> None: - distribution = _require_exact_keys( - value, - {"kind", "package", "registry", "source_reference", "dist_reference", "dist"}, - context, - ) - encoded = "/".join(urllib.parse.quote(part, safe="") for part in component.package.split("/")) - if ( - distribution["kind"] != "composer" - or distribution["package"] != component.package - or distribution["registry"] != f"https://repo.packagist.org/p2/{encoded}.json" - or distribution["source_reference"] != commit - or distribution["dist_reference"] != commit - ): - raise CandidateError(f"{context} does not prove the planned Composer package identity") - _validate_download_evidence(distribution["dist"], f"{context}.dist") - - -def _validate_github_release_evidence( - value: Any, component: Component, version: str, commit: str, context: str -) -> None: - distribution = _require_exact_keys( - value, - { - "kind", - "repository", - "release_id", - "release_url", - "build_attestations_verified", - "build_attestation_authority", - "package_source", - "assets", - }, - context, - ) - release_url = _require_https_url(distribution["release_url"], f"{context}.release_url") - if ( - distribution["kind"] != "github-release" - or distribution["repository"] != component.repository - or not release_url.startswith(f"https://github.com/{component.repository}/releases/") - or distribution["build_attestations_verified"] is not True - ): - raise CandidateError(f"{context} does not prove the planned GitHub Release") - _require_positive_integer(distribution["release_id"], f"{context}.release_id") - - authority = distribution["build_attestation_authority"] - if isinstance(authority, dict) and authority.get("mode") == "exact-tag": - expected_authority = {"mode": "exact-tag", "ref": f"refs/tags/{version}", "commit": commit} - else: - expected_authority = { - "mode": "qualified-main-workflow", - "ref": "refs/heads/main", - "workflow": f"{component.repository}/.github/workflows/release.yml", - } - if authority != expected_authority: - raise CandidateError(f"{context}.build_attestation_authority is not an accepted exact authority") - - package_source = _require_exact_keys( - distribution["package_source"], {"commit", "embedded_phar_identity"}, f"{context}.package_source" - ) - embedded_identity = package_source["embedded_phar_identity"] - if package_source["commit"] != commit or embedded_identity != canonical_cli_embedded_identity(version, commit): - raise CandidateError(f"{context}.package_source does not bind the planned CLI source") - - assets = distribution["assets"] - if not isinstance(assets, list) or len(assets) != len(CLI_ASSETS): - raise CandidateError(f"{context}.assets must contain exactly the required CLI release assets") - names: set[str] = set() - for index, raw_asset in enumerate(assets): - asset = _require_exact_keys( - raw_asset, {"name", "asset_id", "url", "size", "sha256"}, f"{context}.assets[{index}]" - ) - if asset["name"] not in CLI_ASSETS or asset["name"] in names: - raise CandidateError(f"{context}.assets contains an invalid or duplicate asset name") - names.add(asset["name"]) - _require_positive_integer(asset["asset_id"], f"{context}.assets[{index}].asset_id") - _validate_download_evidence( - {key: asset[key] for key in ("url", "size", "sha256")}, - f"{context}.assets[{index}]", - ) - if names != CLI_ASSETS: - raise CandidateError(f"{context}.assets does not cover every required CLI release asset") - - -def _validate_pypi_evidence(value: Any, component: Component, version: str, commit: str, context: str) -> None: - distribution = _require_exact_keys(value, {"kind", "package", "registry", "source_identity", "files"}, context) - encoded_package = urllib.parse.quote(component.package, safe="") - registry_urls = { - f"https://pypi.org/pypi/{encoded_package}/{urllib.parse.quote(registry_version, safe='')}/json" - for registry_version in {version, canonical_pypi_version(version)} - } - if ( - distribution["kind"] != "pypi" - or distribution["package"] != component.package - or distribution["registry"] not in registry_urls - ): - raise CandidateError(f"{context} does not prove the planned PyPI package identity") - source_identity = _require_exact_keys( - distribution["source_identity"], - {"source_archive", "source_files_compared", "wheel_files_compared", "source_commit"}, - f"{context}.source_identity", - ) - _validate_download_evidence(source_identity["source_archive"], f"{context}.source_identity.source_archive") - _require_positive_integer( - source_identity["source_files_compared"], f"{context}.source_identity.source_files_compared" - ) - _require_positive_integer( - source_identity["wheel_files_compared"], f"{context}.source_identity.wheel_files_compared" - ) - if source_identity["source_commit"] != commit: - raise CandidateError(f"{context}.source_identity does not bind the planned source commit") - - files = distribution["files"] - if not isinstance(files, list) or not 2 <= len(files) <= MAX_PYPI_FILES: - raise CandidateError(f"{context}.files must contain 2-{MAX_PYPI_FILES} bounded distribution files") - names: set[str] = set() - package_types: set[str] = set() - for index, raw_file in enumerate(files): - file = _require_exact_keys( - raw_file, - {"url", "size", "sha256", "filename", "package_type"}, - f"{context}.files[{index}]", - ) - filename = _require_bounded_string(file["filename"], f"{context}.files[{index}].filename", maximum=255) - if filename in names or "/" in filename or "\\" in filename: - raise CandidateError(f"{context}.files contains an invalid or duplicate filename") - names.add(filename) - if file["package_type"] not in {"bdist_wheel", "sdist"}: - raise CandidateError(f"{context}.files contains an unsupported package type") - package_types.add(file["package_type"]) - _validate_download_evidence( - {key: file[key] for key in ("url", "size", "sha256")}, - f"{context}.files[{index}]", - ) - if package_types != {"bdist_wheel", "sdist"}: - raise CandidateError(f"{context}.files must prove both wheel and source distributions") - - -def _validate_crate_evidence(value: Any, component: Component, version: str, commit: str, context: str) -> None: - distribution = _require_exact_keys( - value, - {"kind", "package", "registry", "archive_vcs_commit", "archive_vcs_dirty", "archive"}, - context, - ) - encoded_package = urllib.parse.quote(component.package, safe="") - encoded_version = urllib.parse.quote(version, safe="") - if ( - distribution["kind"] != "crates.io" - or distribution["package"] != component.package - or distribution["registry"] != f"https://crates.io/api/v1/crates/{encoded_package}/{encoded_version}" - or distribution["archive_vcs_commit"] != commit - or distribution["archive_vcs_dirty"] is not False - ): - raise CandidateError(f"{context} does not prove the planned crates.io package identity") - _validate_download_evidence(distribution["archive"], f"{context}.archive") - - -def _validate_oci_evidence(value: Any, component: Component, version: str, commit: str, context: str) -> None: - distribution = _require_exact_keys(value, {"kind", "image", "manifest_digest", "platforms", "configs"}, context) - if ( - distribution["kind"] != "oci" - or distribution["image"] != f"{component.package}:{version}" - or not re.fullmatch(r"sha256:[0-9a-f]{64}", str(distribution["manifest_digest"])) - ): - raise CandidateError(f"{context} does not prove the planned OCI image identity") - platforms = distribution["platforms"] - if not isinstance(platforms, list) or len(platforms) != 2 or set(platforms) != {"linux/amd64", "linux/arm64"}: - raise CandidateError(f"{context}.platforms must prove exactly the required Linux platforms") - configs = distribution["configs"] - if not isinstance(configs, list) or len(configs) != 2: - raise CandidateError(f"{context}.configs must prove exactly two platform configurations") - digests: set[str] = set() - expected_labels = { - "org.opencontainers.image.revision": commit, - "dev.durable-workflow.release.tag": version, - } - for index, raw_config in enumerate(configs): - config = _require_exact_keys(raw_config, {"digest", "labels"}, f"{context}.configs[{index}]") - if not re.fullmatch(r"sha256:[0-9a-f]{64}", str(config["digest"])) or config["digest"] in digests: - raise CandidateError(f"{context}.configs contains an invalid or duplicate digest") - digests.add(config["digest"]) - if config["labels"] != expected_labels: - raise CandidateError(f"{context}.configs labels do not bind the planned source identity") - - -def load_verification(path: Path, manifest: dict[str, Any]) -> dict[str, Any]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read verification result: {error}") from error - if len(raw) > VERIFICATION_MAX_BYTES: - raise CandidateError(f"verification result exceeds the {VERIFICATION_MAX_BYTES // 1024} KiB limit") - try: - verification = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"cannot read verification result: {error}") from error - validate_verification(verification, manifest) - return verification - - -def validate_verification(verification: Any, manifest: dict[str, Any]) -> None: - _validate_verification(verification, manifest, VERIFICATION_SCHEMA) - - -def validate_recorded_verification(verification: Any, manifest: dict[str, Any]) -> None: - """Validate immutable evidence under the verification schema that produced it.""" - validate_recorded_manifest(manifest) - expected_schema = LEGACY_VERIFICATION_SCHEMA if manifest["schema"] == LEGACY_SCHEMA else VERIFICATION_SCHEMA - _validate_verification(verification, manifest, expected_schema) - - -def _validate_verification(verification: Any, manifest: dict[str, Any], schema: str) -> None: - if not isinstance(verification, dict): - raise CandidateError("verification result must be a JSON object") - expected_top = {"schema", "candidate", "manifest_sha256", "verified_at", "outcome", "components"} - if set(verification) != expected_top: - raise CandidateError(f"verification result keys must be exactly {sorted(expected_top)}") - if ( - verification["schema"] != schema - or verification["candidate"] != manifest["candidate"] - or verification["manifest_sha256"] != manifest_digest(manifest) - or verification["outcome"] != "verified" - ): - raise CandidateError("verification result does not prove this exact candidate manifest") - verified_at = verification["verified_at"] - if not isinstance(verified_at, str) or not RFC3339_SECONDS_PATTERN.fullmatch(verified_at): - raise CandidateError("verification result verified_at must be an RFC 3339 UTC timestamp") - try: - dt.datetime.strptime(verified_at, "%Y-%m-%dT%H:%M:%SZ") - except ValueError as error: - raise CandidateError("verification result verified_at must be a valid UTC timestamp") from error - - components = verification["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise CandidateError("verification result does not cover every candidate component") - for name, identity in manifest["components"].items(): - result = components[name] - dual_waterline = name == "waterline" and schema == VERIFICATION_SCHEMA - expected_result = ( - {"version", "commit", "source", "distributions", "outcome"} - if dual_waterline - else {"version", "commit", "source", "distribution", "outcome"} - ) - if not isinstance(result, dict) or set(result) != expected_result or result["outcome"] != "verified": - raise CandidateError(f"verification result for {name} is not successful") - if result["version"] != identity["version"] or result["commit"] != identity["commit"]: - raise CandidateError(f"verification result for {name} proves a different source release") - component = COMPONENTS[name] - context = f"verification result for {name}" - _validate_source_evidence(result["source"], component, identity["version"], identity["commit"], context) - distribution_validators = { - "composer": _validate_composer_evidence, - "github-release": _validate_github_release_evidence, - "pypi": _validate_pypi_evidence, - "crates.io": _validate_crate_evidence, - "oci": _validate_oci_evidence, - } - if dual_waterline: - distributions = _require_exact_keys( - result["distributions"], - {"embedded", "service"}, - f"{context}.distributions", - ) - _validate_composer_evidence( - distributions["embedded"], - component, - identity["version"], - identity["commit"], - f"{context}.distributions.embedded", - ) - _validate_oci_evidence( - distributions["service"], - WATERLINE_SERVICE, - identity["version"], - identity["commit"], - f"{context}.distributions.service", - ) - else: - distribution_validators[component.distribution]( - result["distribution"], - component, - identity["version"], - identity["commit"], - f"{context}.distribution", - ) - - -class PublicClient: - def __init__( - self, - github_token: str | None = None, - *, - max_attempts: int = GITHUB_READ_MAX_ATTEMPTS, - retry_base_seconds: float = GITHUB_READ_RETRY_BASE_SECONDS, - retry_max_seconds: float = GITHUB_READ_RETRY_MAX_SECONDS, - request_timeout_seconds: float = GITHUB_READ_REQUEST_TIMEOUT_SECONDS, - deadline_seconds: float = GITHUB_READ_DEADLINE_SECONDS, - sleep: Callable[[float], None] = time.sleep, - now: Callable[[], float] = time.time, - monotonic: Callable[[], float] = time.monotonic, - ) -> None: - if ( - max_attempts < 1 - or retry_base_seconds < 0 - or retry_max_seconds < retry_base_seconds - or request_timeout_seconds <= 0 - or deadline_seconds <= 0 - ): - raise ValueError("invalid GitHub public-read retry configuration") - self.github_token = github_token - self.max_attempts = max_attempts - self.retry_base_seconds = retry_base_seconds - self.retry_max_seconds = retry_max_seconds - self.request_timeout_seconds = request_timeout_seconds - self.sleep = sleep - self.now = now - self.monotonic = monotonic - self.deadline = monotonic() + deadline_seconds - - @staticmethod - def _github_endpoint_class(url: str) -> str | None: - parsed = urllib.parse.urlsplit(url) - host = (parsed.hostname or "").lower() - if host == "api.github.com": - path = parsed.path - endpoint_classes = ( - ("/releases", "releases-api"), - ("/git/", "git-api"), - ("/contents/", "contents-api"), - ("/commits/", "commits-api"), - ("/actions/", "actions-api"), - ("/environments/", "environments-api"), - ) - for marker, endpoint_class in endpoint_classes: - if marker in path: - return endpoint_class - if path.startswith("/users/"): - return "users-api" - return "repositories-api" - if host == "github.com" or host.endswith(".github.com") or host.endswith(".githubusercontent.com"): - return "github-download" - return None - - @staticmethod - def _error_detail(error: urllib.error.HTTPError) -> str: - try: - return error.read(1024).decode(errors="replace") - except OSError: - return "response body unavailable" - - @staticmethod - def _is_rate_limited(error: urllib.error.HTTPError, detail: str) -> bool: - headers = error.headers or {} - return error.code == 429 or ( - error.code == 403 - and ( - headers.get("Retry-After") is not None - or headers.get("X-RateLimit-Remaining") == "0" - or "rate limit" in detail.lower() - ) - ) - - @staticmethod - def _transport_name(error: BaseException) -> str | None: - reason = error.reason if isinstance(error, urllib.error.URLError) else error - if isinstance( - reason, - ConnectionError | TimeoutError | http.client.IncompleteRead | http.client.RemoteDisconnected, - ): - return type(reason).__name__ - if isinstance(reason, OSError) and reason.errno in { - errno.ECONNABORTED, - errno.ECONNRESET, - errno.EPIPE, - errno.ETIMEDOUT, - }: - return type(reason).__name__ - return None - - def _server_retry_delay(self, headers: Mapping[str, str]) -> float | None: - delays: list[float] = [] - retry_after = headers.get("Retry-After") - if retry_after: - try: - delays.append(float(retry_after)) - except ValueError: - try: - retry_at = email.utils.parsedate_to_datetime(retry_after) - except (TypeError, ValueError): - pass - else: - if retry_at.tzinfo is None: - retry_at = retry_at.replace(tzinfo=dt.UTC) - delays.append(retry_at.timestamp() - self.now()) - rate_limit_reset = headers.get("X-RateLimit-Reset") - if rate_limit_reset: - with contextlib.suppress(ValueError): - delays.append(float(rate_limit_reset) - self.now()) - return max((delay for delay in delays if delay > 0), default=None) - - def _retry_delay(self, attempt: int, failure: _TransientGitHubRead) -> float: - backoff = min(self.retry_base_seconds * (2 ** (attempt - 1)), self.retry_max_seconds) - return max(backoff, self._server_retry_delay(failure.headers) or 0) - - def _remaining_time(self) -> float: - return self.deadline - self.monotonic() - - def _run( - self, - url: str, - operation: Callable[[urllib.response.addinfourl], Any], - *, - headers: dict[str, str] | None, - accept: str | None, - ) -> Any: - endpoint_class = self._github_endpoint_class(url) - attempt_limit = self.max_attempts if endpoint_class is not None else 1 - request_headers = { - "User-Agent": "durable-workflow-beta-candidate-verifier/1", - **(headers or {}), - } - if accept: - request_headers["Accept"] = accept - if self.github_token and urllib.parse.urlsplit(url).hostname == "api.github.com": - request_headers["Authorization"] = f"Bearer {self.github_token}" - request_headers.setdefault("X-GitHub-Api-Version", "2022-11-28") - - for attempt in range(1, attempt_limit + 1): - if endpoint_class is not None and self._remaining_time() <= 0: - raise PublicInfrastructureError(endpoint_class, attempt - 1, reason="workflow-deadline") - timeout = min(self.request_timeout_seconds, self._remaining_time()) if endpoint_class is not None else 60 - request = urllib.request.Request(url, headers=request_headers) - failure: _TransientGitHubRead | None = None - try: - response = urllib.request.urlopen(request, timeout=timeout) - result = operation(response) - if endpoint_class is not None and self._remaining_time() <= 0: - raise PublicInfrastructureError(endpoint_class, attempt, reason="workflow-deadline") - return result - except urllib.error.HTTPError as error: - detail = self._error_detail(error) - if endpoint_class is not None and (500 <= error.code <= 599 or self._is_rate_limited(error, detail)): - failure = _TransientGitHubRead(f"status={error.code}", error.headers) - else: - raise CandidateError(f"public request failed ({error.code}) for {url}: {detail}") from error - except (urllib.error.URLError, ConnectionError, TimeoutError, http.client.IncompleteRead) as error: - transport = self._transport_name(error) - if endpoint_class is not None and transport is not None: - failure = _TransientGitHubRead(f"transport={transport}") - else: - reason = error.reason if isinstance(error, urllib.error.URLError) else error - raise CandidateError(f"public request failed for {url}: {reason}") from error - - assert endpoint_class is not None and failure is not None - if attempt == attempt_limit: - raise PublicInfrastructureError( - endpoint_class, - attempt, - reason="retry-exhausted", - failure=failure.evidence, - ) - delay = self._retry_delay(attempt, failure) - if delay >= self._remaining_time(): - raise PublicInfrastructureError( - endpoint_class, - attempt, - reason="workflow-deadline", - failure=failure.evidence, - ) - print( - f"GitHub public read retry: endpoint_class={endpoint_class} " - f"attempt={attempt}/{attempt_limit} {failure.evidence} delay={delay:g}s", - file=sys.stderr, - ) - self.sleep(delay) - raise AssertionError("GitHub public-read retry loop ended unexpectedly") - - def request( - self, - url: str, - *, - headers: dict[str, str] | None = None, - accept: str | None = None, - ) -> urllib.response.addinfourl: - return self._run(url, lambda response: response, headers=headers, accept=accept) - - def json(self, url: str, *, headers: dict[str, str] | None = None, accept: str | None = None) -> Any: - def read_json(response: urllib.response.addinfourl) -> Any: - with response: - try: - return json.load(response) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise CandidateError(f"public endpoint did not return valid JSON: {url}") from error - - return self._run(url, read_json, headers=headers, accept=accept) - - def bytes(self, url: str, *, headers: dict[str, str] | None = None, accept: str | None = None) -> bytes: - def read_bytes(response: urllib.response.addinfourl) -> bytes: - with response: - return response.read() - - return self._run(url, read_bytes, headers=headers, accept=accept) - - def download(self, url: str, path: Path, *, expected_sha256: str | None = None) -> dict[str, Any]: - def download_once(response: urllib.response.addinfourl) -> tuple[str, int]: - digest = hashlib.sha256() - size = 0 - with response, path.open("wb") as destination: - while chunk := response.read(1024 * 1024): - digest.update(chunk) - destination.write(chunk) - size += len(chunk) - return digest.hexdigest(), size - - actual, size = self._run(url, download_once, headers=None, accept=None) - if expected_sha256 and actual != expected_sha256.lower(): - raise CandidateError(f"download digest mismatch for {url}: expected {expected_sha256}, got {actual}") - return {"url": url, "size": size, "sha256": actual} - - -def resolve_github_tag(client: PublicClient, repository: str, version: str) -> dict[str, Any]: - encoded = urllib.parse.quote(version, safe="") - url = f"https://api.github.com/repos/{repository}/git/ref/tags/{encoded}" - ref = client.json(url) - target = ref.get("object", {}) - tag_object = target.get("sha") - seen: set[str] = set() - while target.get("type") == "tag": - sha = target.get("sha") - if not isinstance(sha, str) or sha in seen: - raise CandidateError(f"invalid annotated tag chain for {repository}@{version}") - seen.add(sha) - annotated = client.json(f"https://api.github.com/repos/{repository}/git/tags/{sha}") - target = annotated.get("object", {}) - if target.get("type") != "commit" or not COMMIT_PATTERN.fullmatch(str(target.get("sha", ""))): - raise CandidateError(f"release tag {repository}@{version} does not resolve to a commit") - return { - "repository": repository, - "tag": version, - "tag_object": tag_object, - "commit": target["sha"], - "url": f"https://github.com/{repository}/tree/{version}", - } - - -def require_tag_commit(source: dict[str, Any], expected_commit: str) -> None: - if source["commit"] != expected_commit: - raise CandidateError( - f"release tag {source['repository']}@{source['tag']} points to {source['commit']}, not {expected_commit}" - ) - - -def verify_composer( - client: PublicClient, component: Component, version: str, expected_commit: str, directory: Path -) -> dict[str, Any]: - encoded = "/".join(urllib.parse.quote(part, safe="") for part in component.package.split("/")) - metadata_url = f"https://repo.packagist.org/p2/{encoded}.json" - payload = client.json(metadata_url) - try: - release = exact_package_version(payload, component.package, version) - except PackagistMetadataError as error: - raise CandidateError(f"Packagist metadata for {component.package} is invalid: {error}") from error - if not release: - raise CandidateError(f"Packagist does not expose {component.package}@{version}") - source = release.get("source") - source_reference = source.get("reference") if isinstance(source, dict) else None - dist = release.get("dist", {}) - dist_reference = dist.get("reference") if isinstance(dist, dict) else None - if source_reference != expected_commit or dist_reference != expected_commit: - raise CandidateError( - f"Packagist source identity for {component.package}@{version} does not match {expected_commit}" - ) - dist_url = dist.get("url") if isinstance(dist, dict) else None - if not isinstance(dist_url, str) or not dist_url.startswith("https://"): - raise CandidateError(f"Packagist release {component.package}@{version} has no public dist URL") - download = client.download(dist_url, directory / f"{component.package.replace('/', '-')}.zip") - return { - "kind": "composer", - "package": component.package, - "registry": metadata_url, - "source_reference": source_reference, - "dist_reference": dist_reference, - "dist": download, - } - - -def parse_checksums(raw: bytes) -> dict[str, str]: - checksums: dict[str, str] = {} - for line in raw.decode("utf-8").splitlines(): - match = re.fullmatch(r"([0-9a-fA-F]{64})\s+[*]?([^/\s]+)", line.strip()) - if match: - checksums[match.group(2)] = match.group(1).lower() - return checksums - - -def inspect_cli_phar_identity(phar_path: Path, version: str, expected_commit: str) -> str: - """Read generated source identity from a PHAR archive without executing its stub.""" - if shutil.which("php") is None: - raise CandidateError("PHP is required to inspect CLI release source metadata") - inspection = r""" -try { - $archive = new Phar($argv[1]); - $path = 'src/GeneratedBuildInfo.php'; - if (!$archive->offsetExists($path)) { - exit(41); - } - $contents = $archive[$path]->getContent(); - if (strlen($contents) > 16384) { - exit(42); - } - fwrite(STDOUT, base64_encode($contents)); -} catch (Throwable $error) { - exit(43); -} -""" - try: - process = subprocess.run( - [ - "php", - "-d", - "display_errors=0", - "-d", - "log_errors=0", - "-r", - inspection, - "--", - str(phar_path), - ], - cwd=phar_path.parent, - check=False, - text=True, - capture_output=True, - env={"PATH": os.environ.get("PATH", os.defpath)}, - timeout=30, - ) - except subprocess.TimeoutExpired as error: - raise CandidateError(f"CLI PHAR for {version} source metadata inspection timed out") from error - if process.returncode: - raise CandidateError(f"CLI PHAR for {version} has no inspectable generated source metadata") - try: - generated = base64.b64decode(process.stdout, validate=True) - except (ValueError, binascii.Error) as error: - raise CandidateError(f"CLI PHAR for {version} has invalid generated source metadata") from error - if len(generated) > PHAR_BUILD_INFO_MAX_BYTES: - raise CandidateError(f"CLI PHAR for {version} has oversized generated source metadata") - try: - source = generated.decode("utf-8") - except UnicodeDecodeError as error: - raise CandidateError(f"CLI PHAR for {version} has non-UTF-8 generated source metadata") from error - - def generated_constant(name: str) -> str | None: - matches = re.findall(rf"\bpublic\s+const\s+{name}\s*=\s*'([^'\\\r\n]*)'\s*;", source) - return matches[0] if len(matches) == 1 else None - - if generated_constant("VERSION") != version.lstrip("v") or generated_constant("COMMIT") != expected_commit: - raise CandidateError(f"CLI PHAR for {version} does not embed planned source commit {expected_commit}") - return canonical_cli_embedded_identity(version, expected_commit) - - -def verify_github_release( - client: PublicClient, - component: Component, - version: str, - expected_commit: str, - directory: Path, -) -> dict[str, Any]: - encoded = urllib.parse.quote(version, safe="") - api_url = f"https://api.github.com/repos/{component.repository}/releases/tags/{encoded}" - release = client.json(api_url) - if release.get("draft") or release.get("tag_name") != version: - raise CandidateError(f"GitHub release {component.repository}@{version} is absent or still a draft") - assets = {asset.get("name"): asset for asset in release.get("assets", [])} - missing = CLI_ASSETS - set(assets) - if missing: - raise CandidateError(f"GitHub release {component.repository}@{version} lacks assets: {sorted(missing)}") - - checksum_asset = assets["SHA256SUMS"] - checksum_raw = client.bytes(checksum_asset["browser_download_url"]) - checksum_path = directory / "SHA256SUMS" - checksum_path.write_bytes(checksum_raw) - checksums = parse_checksums(checksum_raw) - downloadable = sorted(CLI_ASSETS - {"SHA256SUMS"}) - if set(downloadable) - set(checksums): - raise CandidateError("CLI SHA256SUMS does not cover every public release asset") - - verified_assets: list[dict[str, Any]] = [] - downloaded_paths = [] - for name in downloadable: - asset = assets[name] - asset_path = directory / name - result = client.download(asset["browser_download_url"], asset_path, expected_sha256=checksums[name]) - result.update({"name": name, "asset_id": asset.get("id")}) - verified_assets.append(result) - downloaded_paths.append(asset_path) - verified_assets.append( - { - "name": "SHA256SUMS", - "asset_id": checksum_asset.get("id"), - "url": checksum_asset["browser_download_url"], - "size": len(checksum_raw), - "sha256": hashlib.sha256(checksum_raw).hexdigest(), - } - ) - downloaded_paths.append(checksum_path) - if shutil.which("gh") is None: - raise CandidateError("GitHub CLI is required to verify CLI release attestations") - signer_workflow = f"{component.repository}/.github/workflows/release.yml" - attestation_modes = [ - ( - "exact-tag", - ["--source-ref", f"refs/tags/{version}", "--source-digest", expected_commit], - {"mode": "exact-tag", "ref": f"refs/tags/{version}", "commit": expected_commit}, - ), - ( - "qualified-main-workflow", - ["--source-ref", "refs/heads/main", "--signer-workflow", signer_workflow], - {"mode": "qualified-main-workflow", "ref": "refs/heads/main", "workflow": signer_workflow}, - ), - ] - selected_attestation_mode: tuple[str, list[str], dict[str, str]] | None = None - for asset_path in downloaded_paths: - base_arguments = ["gh", "attestation", "verify", str(asset_path), "--repo", component.repository] - candidates = attestation_modes if selected_attestation_mode is None else [selected_attestation_mode] - failures: list[str] = [] - for mode in candidates: - process = subprocess.run([*base_arguments, *mode[1]], check=False, text=True, capture_output=True) - if process.returncode == 0: - selected_attestation_mode = mode - break - failures.append(f"{mode[0]}: {process.stderr.strip()}") - else: - raise CandidateError(f"CLI build attestation failed for {asset_path.name}: {'; '.join(failures)}") - - assert selected_attestation_mode is not None - embedded_identity = inspect_cli_phar_identity(directory / "dw.phar", version, expected_commit) - return { - "kind": "github-release", - "repository": component.repository, - "release_id": release.get("id"), - "release_url": release.get("html_url"), - "build_attestations_verified": True, - "build_attestation_authority": selected_attestation_mode[2], - "package_source": { - "commit": expected_commit, - "embedded_phar_identity": embedded_identity, - }, - "assets": verified_assets, - } - - -def archive_files(path: Path, *, zip_archive: bool = False) -> dict[str, bytes]: - files: dict[str, bytes] = {} - if zip_archive: - with zipfile.ZipFile(path) as archive: - for member in archive.infolist(): - if member.is_dir(): - continue - files[member.filename] = archive.read(member) - return files - with tarfile.open(path, "r:*") as archive: - for member in archive.getmembers(): - if not member.isfile(): - continue - extracted = archive.extractfile(member) - if extracted is not None: - files[member.name] = extracted.read() - return files - - -def strip_archive_root(files: dict[str, bytes]) -> dict[str, bytes]: - stripped = {} - for name, content in files.items(): - _, separator, relative = name.partition("/") - if separator and relative: - stripped[relative] = content - return stripped - - -def verify_python_archive_identity( - client: PublicClient, - component: Component, - expected_commit: str, - sdist_path: Path, - wheel_paths: list[Path], - directory: Path, -) -> dict[str, Any]: - source_url = f"https://github.com/{component.repository}/archive/{expected_commit}.tar.gz" - source_path = directory / "python-source.tar.gz" - source_download = client.download(source_url, source_path) - source_files = strip_archive_root(archive_files(source_path)) - sdist_files = strip_archive_root(archive_files(sdist_path)) - compared_source_files = [] - for name, content in sdist_files.items(): - if ".egg-info/" in name or name.endswith(("/PKG-INFO", "PKG-INFO")): - continue - if name == "setup.cfg" and name not in source_files: - # setuptools may synthesize this legacy metadata file while - # assembling an sdist from a pyproject-only source tree. - continue - if name not in source_files or source_files[name] != content: - raise CandidateError(f"PyPI source archive file {name} does not match source commit {expected_commit}") - compared_source_files.append(name) - if "pyproject.toml" not in compared_source_files or not any( - name.startswith("src/durable_workflow/") and name.endswith(".py") for name in compared_source_files - ): - raise CandidateError("PyPI source archive did not provide enough source files for identity verification") - - compared_wheel_files = [] - for wheel_path in wheel_paths: - wheel_files = archive_files(wheel_path, zip_archive=True) - for name, content in wheel_files.items(): - if ".dist-info/" in name: - continue - source_name = f"src/{name}" - if source_name not in sdist_files or sdist_files[source_name] != content: - raise CandidateError(f"PyPI wheel file {name} does not match the verified source archive") - compared_wheel_files.append(name) - if not any(name.startswith("durable_workflow/") and name.endswith(".py") for name in compared_wheel_files): - raise CandidateError("PyPI wheel did not provide enough package files for identity verification") - return { - "source_archive": source_download, - "source_files_compared": len(compared_source_files), - "wheel_files_compared": len(compared_wheel_files), - "source_commit": expected_commit, - } - - -def verify_pypi( - client: PublicClient, component: Component, version: str, expected_commit: str, directory: Path -) -> dict[str, Any]: - encoded_package = urllib.parse.quote(component.package, safe="") - registry_version = canonical_pypi_version(version) - encoded_version = urllib.parse.quote(registry_version, safe="") - api_url = f"https://pypi.org/pypi/{encoded_package}/{encoded_version}/json" - payload = client.json(api_url) - published_version = payload.get("info", {}).get("version") - if published_version not in {version, registry_version}: - raise CandidateError(f"PyPI does not expose {component.package}=={version}") - project_urls = payload.get("info", {}).get("project_urls") or {} - repository_urls = {str(value).rstrip("/") for value in project_urls.values()} - expected_repository = f"https://github.com/{component.repository}" - if expected_repository not in repository_urls: - raise CandidateError(f"PyPI metadata for {component.package}=={version} does not name {expected_repository}") - files = [ - item - for item in payload.get("urls", []) - if not item.get("yanked") and item.get("packagetype") in {"bdist_wheel", "sdist"} - ] - package_types = {item.get("packagetype") for item in files} - if not {"bdist_wheel", "sdist"}.issubset(package_types): - raise CandidateError(f"PyPI release {component.package}=={version} must provide a wheel and source archive") - verified_files = [] - sdist_path = None - wheel_paths = [] - for item in files: - expected_digest = item.get("digests", {}).get("sha256") - if not re.fullmatch(r"[0-9a-f]{64}", str(expected_digest or "")): - raise CandidateError(f"PyPI file {item.get('filename')} has no SHA-256 identity") - file_path = directory / item["filename"] - result = client.download(item["url"], file_path, expected_sha256=expected_digest) - result.update({"filename": item["filename"], "package_type": item.get("packagetype")}) - verified_files.append(result) - if item.get("packagetype") == "sdist": - sdist_path = file_path - elif item.get("packagetype") == "bdist_wheel": - wheel_paths.append(file_path) - if sdist_path is None: - raise CandidateError(f"PyPI release {component.package}=={version} has no source archive") - source_identity = verify_python_archive_identity( - client, component, expected_commit, sdist_path, wheel_paths, directory - ) - return { - "kind": "pypi", - "package": component.package, - "registry": api_url, - "source_identity": source_identity, - "files": verified_files, - } - - -def verify_crate( - client: PublicClient, component: Component, version: str, expected_commit: str, directory: Path -) -> dict[str, Any]: - encoded_package = urllib.parse.quote(component.package, safe="") - encoded_version = urllib.parse.quote(version, safe="") - api_url = f"https://crates.io/api/v1/crates/{encoded_package}/{encoded_version}" - payload = client.json(api_url) - crate_version = payload.get("version", {}) - if crate_version.get("num") != version or crate_version.get("yanked"): - raise CandidateError(f"crates.io does not expose an active {component.package}@{version}") - crate_payload = client.json(f"https://crates.io/api/v1/crates/{encoded_package}") - crate_metadata = crate_payload.get("crate", {}) - expected_repository = f"https://github.com/{component.repository}" - if str(crate_metadata.get("repository", "")).rstrip("/") != expected_repository: - raise CandidateError( - f"crates.io metadata for {component.package}@{version} names a different source repository" - ) - expected_digest = crate_version.get("checksum") - if not re.fullmatch(r"[0-9a-f]{64}", str(expected_digest or "")): - raise CandidateError(f"crates.io release {component.package}@{version} has no SHA-256 identity") - download_url = f"https://crates.io/api/v1/crates/{encoded_package}/{encoded_version}/download" - archive_path = directory / f"{component.package}-{version}.crate" - download = client.download(download_url, archive_path, expected_sha256=expected_digest) - with tarfile.open(archive_path, "r:gz") as archive: - vcs_members = [member for member in archive.getmembers() if member.name.endswith("/.cargo_vcs_info.json")] - if len(vcs_members) != 1: - raise CandidateError("published crate must contain exactly one .cargo_vcs_info.json") - extracted = archive.extractfile(vcs_members[0]) - if extracted is None: - raise CandidateError("cannot read crate source identity") - vcs_info = json.load(extracted) - archive_commit = vcs_info.get("git", {}).get("sha1") - if archive_commit != expected_commit or vcs_info.get("git", {}).get("dirty", False) is not False: - raise CandidateError(f"published crate source identity does not match clean commit {expected_commit}") - return { - "kind": "crates.io", - "package": component.package, - "registry": api_url, - "archive_vcs_commit": archive_commit, - "archive_vcs_dirty": False, - "archive": download, - } - - -def oci_json(client: PublicClient, url: str, token: str, accept: str) -> tuple[Any, str | None]: - response = client.request(url, headers={"Authorization": f"Bearer {token}"}, accept=accept) - with response: - digest = response.headers.get("Docker-Content-Digest") - return json.load(response), digest - - -def verify_oci( - client: PublicClient, component: Component, version: str, expected_commit: str, _directory: Path -) -> dict[str, Any]: - registry, repository = component.package.split("/", 1) - if registry == "docker.io": - registry_api = "registry-1.docker.io" - token_host = "auth.docker.io" - token_service = "registry.docker.io" - else: - registry_api = registry - token_host = registry - token_service = registry - token_url = ( - f"https://{token_host}/token?service={urllib.parse.quote(token_service)}" - f"&scope={urllib.parse.quote(f'repository:{repository}:pull')}" - ) - token = client.json(token_url).get("token") - if not isinstance(token, str) or not token: - raise CandidateError(f"public OCI registry did not grant pull access to {component.package}:{version}") - manifest_accept = ", ".join( - [ - "application/vnd.oci.image.index.v1+json", - "application/vnd.docker.distribution.manifest.list.v2+json", - "application/vnd.oci.image.manifest.v1+json", - "application/vnd.docker.distribution.manifest.v2+json", - ] - ) - manifest_url = f"https://{registry_api}/v2/{repository}/manifests/{urllib.parse.quote(version, safe='')}" - manifest, manifest_digest_header = oci_json(client, manifest_url, token, manifest_accept) - if not re.fullmatch(r"sha256:[0-9a-f]{64}", str(manifest_digest_header or "")): - raise CandidateError(f"OCI image {component.package}:{version} has no immutable manifest digest") - descriptors = manifest.get("manifests") - if not isinstance(descriptors, list): - raise CandidateError(f"OCI image {component.package}:{version} is not a multi-platform release") - selected_platforms: list[str] = [] - candidates = [] - for descriptor in descriptors: - platform = descriptor.get("platform", {}) - label = f"{platform.get('os')}/{platform.get('architecture')}" - if platform.get("os") == "linux" and platform.get("architecture") in {"amd64", "arm64"}: - candidates.append(descriptor) - selected_platforms.append(label) - if set(selected_platforms) != {"linux/amd64", "linux/arm64"}: - raise CandidateError(f"OCI image {component.package}:{version} lacks required Linux platforms") - - configs = [] - for descriptor in candidates: - child_url = f"https://{registry_api}/v2/{repository}/manifests/{descriptor['digest']}" - child, child_digest = oci_json(client, child_url, token, manifest_accept) - if child_digest != descriptor["digest"]: - raise CandidateError(f"OCI image {component.package}:{version} platform digest changed during verification") - config_digest = child.get("config", {}).get("digest") - if not re.fullmatch(r"sha256:[0-9a-f]{64}", str(config_digest or "")): - raise CandidateError(f"OCI image {component.package}:{version} has no immutable config digest") - config_url = f"https://{registry_api}/v2/{repository}/blobs/{config_digest}" - config = client.json(config_url, headers={"Authorization": f"Bearer {token}"}) - labels = config.get("config", {}).get("Labels") or {} - expected_labels = { - "org.opencontainers.image.revision": expected_commit, - "dev.durable-workflow.release.tag": version, - } - if any(labels.get(key) != value for key, value in expected_labels.items()): - raise CandidateError(f"OCI image {component.package}:{version} labels do not match its source release") - configs.append({"digest": config_digest, "labels": expected_labels}) - return { - "kind": "oci", - "image": f"{component.package}:{version}", - "manifest_digest": manifest_digest_header, - "platforms": selected_platforms, - "configs": configs, - } - - -VERIFIERS = { - "composer": verify_composer, - "github-release": verify_github_release, - "pypi": verify_pypi, - "crates.io": verify_crate, - "oci": verify_oci, -} - - -def verify_candidate(manifest: dict[str, Any], client: PublicClient) -> dict[str, Any]: - components = {} - with tempfile.TemporaryDirectory(prefix="beta-candidate-") as temporary: - directory = Path(temporary) - for name, component in COMPONENTS.items(): - identity = manifest["components"][name] - try: - source = resolve_github_tag(client, component.repository, identity["version"]) - require_tag_commit(source, identity["commit"]) - if name == "waterline": - distributions = { - "embedded": verify_composer( - client, component, identity["version"], identity["commit"], directory - ), - "service": verify_oci( - client, WATERLINE_SERVICE, identity["version"], identity["commit"], directory - ), - } - else: - distribution = VERIFIERS[component.distribution]( - client, component, identity["version"], identity["commit"], directory - ) - except CandidateError as error: - raise CandidateError(f"{name}: {error}") from error - components[name] = { - "version": identity["version"], - "commit": identity["commit"], - "source": source, - "outcome": "verified", - } - if name == "waterline": - components[name]["distributions"] = distributions - else: - components[name]["distribution"] = distribution - return { - "schema": VERIFICATION_SCHEMA, - "candidate": manifest["candidate"], - "manifest_sha256": manifest_digest(manifest), - "verified_at": dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"), - "outcome": "verified", - "components": components, - } - - -def revalidate_verification( - verification: dict[str, Any], manifest: dict[str, Any], client: PublicClient -) -> dict[str, Any]: - """Independently reproduce and canonicalize evidence before an authenticated write.""" - validate_verification(verification, manifest) - components: dict[str, Any] = {} - with tempfile.TemporaryDirectory(prefix="beta-candidate-writer-revalidation-") as temporary: - directory = Path(temporary) - for name, component in COMPONENTS.items(): - identity = manifest["components"][name] - submitted = verification["components"][name] - try: - source = resolve_github_tag(client, component.repository, identity["version"]) - require_tag_commit(source, identity["commit"]) - if name == "waterline": - distributions = { - "embedded": verify_composer( - client, - component, - identity["version"], - identity["commit"], - directory, - ), - "service": verify_oci( - client, - WATERLINE_SERVICE, - identity["version"], - identity["commit"], - directory, - ), - } - elif component.distribution == "github-release": - distribution = verify_github_release( - client, - component, - identity["version"], - identity["commit"], - directory, - ) - else: - distribution = VERIFIERS[component.distribution]( - client, component, identity["version"], identity["commit"], directory - ) - except CandidateError as error: - raise CandidateError(f"fresh writer revalidation failed for {name}: {error}") from error - submitted_distributions = submitted.get("distributions") if name == "waterline" else None - if source != submitted["source"] or ( - distributions != submitted_distributions - if name == "waterline" - else distribution != submitted["distribution"] - ): - raise CandidateError( - f"fresh writer revalidation for {name} differs from the isolated verification handoff" - ) - components[name] = { - "version": identity["version"], - "commit": identity["commit"], - "source": source, - "outcome": "verified", - } - if name == "waterline": - components[name]["distributions"] = distributions - else: - components[name]["distribution"] = distribution - return { - "schema": VERIFICATION_SCHEMA, - "candidate": manifest["candidate"], - "manifest_sha256": manifest_digest(manifest), - "verified_at": dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"), - "outcome": "verified", - "components": components, - } - - -def run_git(arguments: list[str], *, cwd: Path, env: dict[str, str] | None = None, check: bool = True) -> str: - process = subprocess.run( - ["git", *arguments], - cwd=cwd, - env=env, - check=False, - text=True, - capture_output=True, - ) - if check and process.returncode: - raise CandidateError(f"git {' '.join(arguments)} failed: {process.stderr.strip()}") - return process.stdout.strip() - - -def read_record_file(repository: Path, ref: str, filename: str) -> bytes: - process = subprocess.run( - ["git", "show", f"{ref}:{filename}"], - cwd=repository, - check=False, - capture_output=True, - ) - if process.returncode: - raise CandidateError(f"existing candidate record is incomplete: missing {filename}") - return process.stdout - - -def fetch_existing_record(repository: Path, remote: str, tag: str) -> str | None: - remote_ref = f"refs/tags/{tag}" - query = subprocess.run( - ["git", "ls-remote", "--exit-code", "--refs", remote, remote_ref], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if query.returncode == 2: - return None - if query.returncode: - raise CandidateError(f"cannot inspect remote candidate record: {query.stderr.strip()}") - local_ref = f"refs/beta-candidate-check/{hashlib.sha256(tag.encode()).hexdigest()}" - run_git(["fetch", "--no-tags", "--force", remote, f"{remote_ref}:{local_ref}"], cwd=repository) - return local_ref - - -def write_github_output(path: Path | None, values: dict[str, str]) -> None: - if path is None: - return - with path.open("a", encoding="utf-8") as output: - for key, value in values.items(): - output.write(f"{key}={value}\n") - - -def check_candidate_compatibility(repository: Path, manifest_path: Path, *, remote: str) -> dict[str, str]: - manifest = load_manifest(manifest_path) - canonical_manifest = canonical_json(manifest) - tag = f"{TAG_PREFIX}{manifest['candidate']}" - existing_ref = fetch_existing_record(repository, remote, tag) - if not existing_ref: - return {"status": "new", "candidate": manifest["candidate"], "tag": tag} - existing_manifest = read_record_file(repository, existing_ref, "candidate.json") - if existing_manifest != canonical_manifest: - raise CandidateError(f"candidate {manifest['candidate']} is immutable and the requested tuple is different") - read_record_file(repository, existing_ref, "verification.json") - return { - "status": "existing", - "candidate": manifest["candidate"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - - -def record_candidate( - repository: Path, - manifest_path: Path, - verification_path: Path, - *, - remote: str, - authoritative_verification: Path, - client: PublicClient, -) -> dict[str, str]: - manifest = load_manifest(manifest_path) - canonical_manifest = canonical_json(manifest) - verification = load_verification(verification_path, manifest) - tag = f"{TAG_PREFIX}{manifest['candidate']}" - - existing_ref = fetch_existing_record(repository, remote, tag) - if existing_ref: - existing_manifest = read_record_file(repository, existing_ref, "candidate.json") - if existing_manifest != canonical_manifest: - raise CandidateError(f"candidate {manifest['candidate']} is immutable and the requested tuple is different") - existing_verification = read_record_file(repository, existing_ref, "verification.json") - authoritative_verification.write_bytes(existing_verification) - return { - "status": "existing", - "candidate": manifest["candidate"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - - verification = revalidate_verification(verification, manifest, client) - canonical_verification = canonical_json(verification) - - with tempfile.NamedTemporaryFile(prefix="beta-candidate-index-", delete=False) as index: - index_path = Path(index.name) - try: - env = os.environ.copy() - env["GIT_INDEX_FILE"] = str(index_path) - index_path.unlink(missing_ok=True) - run_git(["read-tree", "--empty"], cwd=repository, env=env) - record_files = (("candidate.json", canonical_manifest), ("verification.json", canonical_verification)) - for filename, content in record_files: - blob = ( - subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=repository, - env=env, - input=content, - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - run_git(["update-index", "--add", "--cacheinfo", f"100644,{blob},{filename}"], cwd=repository, env=env) - tree = run_git(["write-tree"], cwd=repository, env=env) - commit_env = env.copy() - commit_env.update( - { - "GIT_AUTHOR_NAME": "Durable Workflow Candidate Recorder", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Candidate Recorder", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - } - ) - commit = ( - subprocess.run( - ["git", "commit-tree", tree], - cwd=repository, - env=commit_env, - input=f"Record beta candidate {manifest['candidate']}\n".encode(), - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - finally: - index_path.unlink(missing_ok=True) - - push = subprocess.run( - ["git", "push", remote, f"{commit}:refs/tags/{tag}"], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if push.returncode: - # A concurrent first writer can win between inspection and push. Its - # manifest is still authoritative, so apply the normal equality check. - existing_ref = fetch_existing_record(repository, remote, tag) - if not existing_ref: - raise CandidateError(f"cannot publish candidate record: {push.stderr.strip()}") - if read_record_file(repository, existing_ref, "candidate.json") != canonical_manifest: - raise CandidateError(f"candidate {manifest['candidate']} was concurrently recorded with a different tuple") - existing_verification = read_record_file(repository, existing_ref, "verification.json") - authoritative_verification.write_bytes(existing_verification) - return { - "status": "existing", - "candidate": manifest["candidate"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - - authoritative_verification.write_bytes(canonical_verification) - return {"status": "created", "candidate": manifest["candidate"], "tag": tag, "commit": commit} - - -def command_validate(arguments: argparse.Namespace) -> None: - manifest = load_manifest(arguments.manifest) - if arguments.require_supported_train: - from scripts.product_train import require_current_product_train - - require_current_product_train(manifest["components"]) - arguments.output.write_bytes(canonical_json(manifest)) - - -def command_compare(arguments: argparse.Namespace) -> None: - requested = canonical_json(load_manifest(arguments.requested)) - existing = canonical_json(load_manifest(arguments.existing)) - if requested != existing: - raise CandidateError("candidate manifest mutation rejected") - - -def command_verify(arguments: argparse.Namespace) -> None: - manifest = load_manifest(arguments.manifest) - client = PublicClient(os.environ.get("GITHUB_TOKEN")) - arguments.output.write_bytes(canonical_json(verify_candidate(manifest, client))) - - -def command_validate_verification(arguments: argparse.Namespace) -> None: - manifest = load_manifest(arguments.manifest) - load_verification(arguments.verification, manifest) - - -def command_check(arguments: argparse.Namespace) -> None: - result = check_candidate_compatibility(arguments.repository, arguments.manifest, remote=arguments.remote) - write_github_output(arguments.github_output, result) - print(json.dumps(result, sort_keys=True)) - - -def command_record(arguments: argparse.Namespace) -> None: - result = record_candidate( - arguments.repository, - arguments.manifest, - arguments.verification, - remote=arguments.remote, - authoritative_verification=arguments.authoritative_verification, - client=PublicClient(os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")), - ) - write_github_output(arguments.github_output, result) - print(json.dumps(result, sort_keys=True)) - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - - validate = subparsers.add_parser("validate", help="validate and canonicalize a candidate manifest") - validate.add_argument("manifest", type=Path) - validate.add_argument("output", type=Path) - validate.add_argument("--require-supported-train", action="store_true") - validate.set_defaults(handler=command_validate) - - compare = subparsers.add_parser("compare", help="prove two manifests have the same identity") - compare.add_argument("requested", type=Path) - compare.add_argument("existing", type=Path) - compare.set_defaults(handler=command_compare) - - verify = subparsers.add_parser("verify", help="verify every public artifact and source release") - verify.add_argument("manifest", type=Path) - verify.add_argument("output", type=Path) - verify.set_defaults(handler=command_verify) - - validate_verification_parser = subparsers.add_parser( - "validate-verification", - help="validate isolated verification evidence before granting mutation authority", - ) - validate_verification_parser.add_argument("manifest", type=Path) - validate_verification_parser.add_argument("verification", type=Path) - validate_verification_parser.set_defaults(handler=command_validate_verification) - - check = subparsers.add_parser("check", help="reject mutation of an existing candidate before verification") - check.add_argument("manifest", type=Path) - check.add_argument("--repository", type=Path, default=Path.cwd()) - check.add_argument("--remote", default="origin") - check.add_argument("--github-output", type=Path) - check.set_defaults(handler=command_check) - - record = subparsers.add_parser("record", help="create or compare an immutable candidate Git tag") - record.add_argument("manifest", type=Path) - record.add_argument("verification", type=Path) - record.add_argument("--repository", type=Path, default=Path.cwd()) - record.add_argument("--remote", default="origin") - record.add_argument("--authoritative-verification", type=Path, required=True) - record.add_argument("--github-output", type=Path) - record.set_defaults(handler=command_record) - return parser - - -def main() -> int: - parser = build_parser() - arguments = parser.parse_args() - try: - arguments.handler(arguments) - except PublicInfrastructureError as error: - print(f"beta candidate infrastructure failed: {error}", file=sys.stderr) - return INFRASTRUCTURE_EXIT_CODE - except CandidateError as error: - print(f"beta candidate error: {error}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/beta_conformance.py b/scripts/beta_conformance.py deleted file mode 100644 index 0f4660e..0000000 --- a/scripts/beta_conformance.py +++ /dev/null @@ -1,3416 +0,0 @@ -#!/usr/bin/env python3 -"""Run beta conformance from immutable public artifacts and retain bounded evidence.""" - -from __future__ import annotations - -import argparse -import base64 -import contextlib -import datetime as dt -import hashlib -import json -import os -import re -import shutil -import signal -import subprocess -import sys -import tempfile -import time -import urllib.error -import urllib.request -from collections.abc import Iterator -from pathlib import Path -from typing import Any - -# Direct workflow invocation adds scripts/, rather than the repository root, to -# sys.path. Keep module and command-line execution equivalent. -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.beta_candidate import ( - CANDIDATE_PATTERN, - CLI_ASSETS, - COMPONENTS, - VERSION_PATTERN, - WATERLINE_SERVICE, - CandidateError, - Component, - canonical_json, - load_manifest, - manifest_digest, - validate_verification, -) - -CONTRACT_SCHEMA = "durable-workflow.beta-conformance.contract/v2" -PLAN_SCHEMA = "durable-workflow.beta-conformance.plan/v2" -EXPERIMENT_RESULT_SCHEMA = "durable-workflow.beta-conformance.experiment-result/v2" -SUITE_RESULT_SCHEMA = "durable-workflow.beta-conformance.suite-result/v2" -LEGACY_PLAN_SCHEMA = "durable-workflow.beta-conformance.plan/v1" -LEGACY_EXPERIMENT_RESULT_SCHEMA = "durable-workflow.beta-conformance.experiment-result/v1" -LEGACY_SUITE_RESULT_SCHEMA = "durable-workflow.beta-conformance.suite-result/v1" -CONTROL_REPOSITORY = "durable-workflow/.github" -CONFORMANCE_WORKFLOW_NAME = "Beta conformance" -CONFORMANCE_WORKFLOW_PATH = ".github/workflows/beta-conformance.yml" -CONFORMANCE_WORKFLOW_PATHS = {CONFORMANCE_WORKFLOW_PATH, f"{CONFORMANCE_WORKFLOW_PATH}@main"} -EXPERIMENTS = ("heartbeats", "polyglot", "replay", "signals-queries") -WATERLINE_SERVICE_DISTRIBUTION = "waterline-service" -DISTRIBUTIONS: dict[str, tuple[str, Component]] = { - **{name: (name, component) for name, component in COMPONENTS.items()}, - WATERLINE_SERVICE_DISTRIBUTION: ("waterline", WATERLINE_SERVICE), -} -PASS_OUTCOMES = {"pass", "passed", "success", "successful", "completed", "verified"} -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") -OCI_DIGEST_PATTERN = re.compile(r"^sha256:[0-9a-f]{64}$") -PYPI_SEMVER_PRERELEASE_PATTERN = re.compile( - r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-(alpha|beta|rc)\.(0|[1-9][0-9]*)$" -) -PYPI_NATIVE_PRERELEASE_PATTERN = re.compile( - r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(a|b|rc)(0|[1-9][0-9]*)$" -) -DIAGNOSTIC_LIMIT = 8192 -NATIVE_RESULT_LIMIT = 4 * 1024 * 1024 -NATIVE_RESULT_PREFIX_LIMIT = 64 * 1024 -NATIVE_FAILURE_PROJECTION_LIMIT = 24 * 1024 -NATIVE_FAILURE_COMPONENT_LIMIT = 6 * 1024 -FINDING_LIMIT = 20 -FINDING_TEXT_LIMIT = 2048 -MAX_INFRASTRUCTURE_ATTEMPTS = 2 -GITHUB_API_URL = "https://api.github.com" -GITHUB_API_RESPONSE_LIMIT = 4 * 1024 * 1024 -RUNTIME_DEPENDENCY_SELECTORS = { - "mysql": "docker.io/library/mysql:8.0", - "redis": "docker.io/library/redis:7-alpine", -} -NATIVE_SCENARIO_STATUSES = {"pass", "fail", "unsupported", "not_covered", "runner_blocked"} -TRANSIENT_PATTERNS = ( - re.compile( - r"\b(?:registry|pypi|packagist|crates\.io|docker hub|package download|artifact download)\b" - r".{0,160}\b(?:429|50[234])\b", - re.IGNORECASE | re.DOTALL, - ), - re.compile(r"\b(?:registry|package download|artifact download)\b.{0,160}too many requests", re.IGNORECASE), - re.compile(r"tls handshake timeout", re.IGNORECASE), - re.compile(r"connection (?:reset|timed out)", re.IGNORECASE), - re.compile(r"temporary failure in name resolution", re.IGNORECASE), - re.compile(r"registry.*service unavailable", re.IGNORECASE), -) -SENSITIVE_EVIDENCE_KEY = re.compile( - r"(?:authorization|credential|password|passwd|secret|api[_-]?(?:key|token)|token)", - re.IGNORECASE, -) -EVIDENCE_OPTIONAL_QUOTE = r"(?:\\?[\"'])?" -EVIDENCE_QUOTED_TERMINATORS = frozenset(" \t\r\n,;:)}]") -EVIDENCE_ASSIGNMENT_PREFIX = re.compile( - r"(?P(?[a-z0-9_-]+)" - + EVIDENCE_OPTIONAL_QUOTE - + r"\s*[:=]\s*)", - re.IGNORECASE, -) -BETA_TOKEN_EVIDENCE = re.compile(r"\bbeta-[0-9a-f]{32}\b", re.IGNORECASE) -CREDENTIAL_URL_EVIDENCE = re.compile( - r"https?://[^\s/@:]+:[^\s/@]+@", - re.IGNORECASE, -) -SYNTHETIC_CREDENTIAL_CANARY = "beta-00000000000000000000000000000000" - - -class ConformanceError(RuntimeError): - """The portable beta conformance contract is invalid or cannot run.""" - - -def github_api_json(path: str, token: str) -> Any: - """Retrieve one bounded GitHub API document with the workflow token.""" - - if not token: - raise ConformanceError("GitHub API authentication is required for conformance retention") - request = urllib.request.Request( - f"{GITHUB_API_URL}{path}", - headers={ - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", - }, - ) - try: - with urllib.request.urlopen(request, timeout=30) as response: - payload = response.read(GITHUB_API_RESPONSE_LIMIT + 1) - except urllib.error.HTTPError as error: - raise ConformanceError(f"GitHub API metadata request failed with HTTP {error.code}") from error - except urllib.error.URLError as error: - raise ConformanceError("GitHub API metadata request failed") from error - if len(payload) > GITHUB_API_RESPONSE_LIMIT: - raise ConformanceError("GitHub API metadata response exceeds the retention limit") - try: - return json.loads(payload) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise ConformanceError("GitHub API metadata response is not valid JSON") from error - - -def fetch_retention_source_metadata( - expected_run_id: int, - expected_run_attempt: int, - token: str, -) -> tuple[Any, Any]: - """Retrieve the exact source attempt and reviewed workflow identity.""" - - if expected_run_id < 1 or expected_run_attempt < 1: - raise ConformanceError("conformance retention source identity must be positive") - run = github_api_json( - f"/repos/{CONTROL_REPOSITORY}/actions/runs/{expected_run_id}/attempts/{expected_run_attempt}", - token, - ) - workflow = github_api_json( - f"/repos/{CONTROL_REPOSITORY}/actions/workflows/beta-conformance.yml", - token, - ) - return run, workflow - - -def now() -> str: - return dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def github_timestamp(value: Any, label: str) -> str: - if not isinstance(value, str) or not value.endswith("Z"): - raise ConformanceError(f"{label} is not a UTC GitHub timestamp") - try: - parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError as error: - raise ConformanceError(f"{label} is not a UTC GitHub timestamp") from error - if parsed.tzinfo != dt.UTC: - raise ConformanceError(f"{label} is not a UTC GitHub timestamp") - return value - - -def validate_retention_source( - run: Any, - workflow: Any, - *, - expected_run_id: int, - expected_run_attempt: int | None = None, -) -> dict[str, int | str]: - """Bind retention to a completed default-branch conformance execution.""" - if not isinstance(run, dict): - raise ConformanceError("conformance retention source must be a GitHub workflow run") - run_id = run.get("id") - run_attempt = run.get("run_attempt") - repository = run.get("repository") - head_repository = run.get("head_repository") - if ( - not isinstance(workflow, dict) - or type(workflow.get("id")) is not int - or workflow["id"] < 1 - or workflow.get("name") != CONFORMANCE_WORKFLOW_NAME - or workflow.get("path") != CONFORMANCE_WORKFLOW_PATH - or workflow.get("state") != "active" - ): - raise ConformanceError("trusted conformance workflow metadata is invalid") - if type(run_id) is not int or run_id < 1 or run_id != expected_run_id: - raise ConformanceError("conformance retention source has a mismatched run identity") - if type(run_attempt) is not int or run_attempt < 1: - raise ConformanceError("conformance retention source has an invalid run attempt") - if expected_run_attempt is not None and run_attempt != expected_run_attempt: - raise ConformanceError("conformance retention source has a mismatched run attempt") - if ( - not isinstance(repository, dict) - or repository.get("full_name") != CONTROL_REPOSITORY - or not isinstance(head_repository, dict) - or head_repository.get("full_name") != CONTROL_REPOSITORY - ): - raise ConformanceError("conformance retention source is not owned by the control repository") - if ( - run.get("workflow_id") != workflow["id"] - or run.get("path") not in CONFORMANCE_WORKFLOW_PATHS - or run.get("event") != "workflow_dispatch" - ): - raise ConformanceError("conformance retention source is not the dispatched conformance workflow") - if run.get("head_branch") != "main" or not COMMIT_PATTERN.fullmatch(str(run.get("head_sha", ""))): - raise ConformanceError("conformance retention source is not bound to the default branch") - if run.get("status") != "completed" or not isinstance(run.get("conclusion"), str): - raise ConformanceError("conformance retention source is not completed") - display_title = run.get("display_title") - candidate = display_title.removeprefix("Conformance ") if isinstance(display_title, str) else "" - if not CANDIDATE_PATTERN.fullmatch(candidate): - raise ConformanceError("conformance retention source has no candidate identity") - return { - "source_candidate": candidate, - "source_completed_at": github_timestamp(run.get("updated_at"), "conformance completion"), - "source_head_sha": run["head_sha"], - "source_run_id": run_id, - "source_run_attempt": run_attempt, - } - - -def validate_retention_ref( - ref: Any, - comparison: Any, - *, - expected_tag: str, - source_sha: str, - controller_sha: str, -) -> dict[str, str]: - """Bind an immutable evidence tag to protected retention-controller history.""" - expected_ref = f"refs/tags/{expected_tag}" - target = ref.get("object") if isinstance(ref, dict) else None - if not isinstance(ref, dict) or ref.get("ref") != expected_ref: - raise ConformanceError("conformance evidence ref has a mismatched tag identity") - if not COMMIT_PATTERN.fullmatch(source_sha) or not COMMIT_PATTERN.fullmatch(controller_sha): - raise ConformanceError("conformance evidence ref has an invalid source or controller commit") - if ( - not isinstance(target, dict) - or target.get("type") != "commit" - or not COMMIT_PATTERN.fullmatch(str(target.get("sha", ""))) - ): - raise ConformanceError("conformance evidence ref does not resolve to a commit") - target_sha = target["sha"] - base = comparison.get("base_commit") if isinstance(comparison, dict) else None - merge_base = comparison.get("merge_base_commit") if isinstance(comparison, dict) else None - status = comparison.get("status") if isinstance(comparison, dict) else None - ahead_by = comparison.get("ahead_by") if isinstance(comparison, dict) else None - behind_by = comparison.get("behind_by") if isinstance(comparison, dict) else None - if ( - not isinstance(base, dict) - or base.get("sha") != target_sha - or not isinstance(merge_base, dict) - or merge_base.get("sha") != target_sha - or status not in {"ahead", "identical"} - or type(ahead_by) is not int - or ahead_by < 0 - or behind_by != 0 - or (status == "identical" and (target_sha != controller_sha or ahead_by != 0)) - or (status == "ahead" and (target_sha == controller_sha or ahead_by < 1)) - ): - raise ConformanceError("conformance evidence ref is outside protected controller history") - return { - "controller_sha": controller_sha, - "evidence_ref": expected_ref, - "evidence_sha": target_sha, - "source_sha": source_sha, - } - - -def sha256_bytes(value: bytes) -> str: - return hashlib.sha256(value).hexdigest() - - -def sha256_file(path: Path) -> str: - digest = hashlib.sha256() - with path.open("rb") as handle: - while chunk := handle.read(1024 * 1024): - digest.update(chunk) - return digest.hexdigest() - - -def write_json(path: Path, value: Any) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(canonical_json(value)) - - -def load_json(path: Path, *, limit: int = 4 * 1024 * 1024) -> Any: - try: - raw = path.read_bytes() - except OSError as error: - raise ConformanceError(f"cannot read JSON document {path}: {error}") from error - if len(raw) > limit: - raise ConformanceError(f"JSON document exceeds the {limit}-byte limit: {path}") - try: - return json.loads(raw) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise ConformanceError(f"invalid JSON document {path}: {error}") from error - - -def load_native_result( - path: Path, -) -> tuple[Any, int | None, str | None, str | None, int | None, str | None]: - """Read native evidence with bounded work and distinguish complete from prefix identities.""" - observed_size: int | None = None - try: - with path.open("rb") as handle: - observed_size = os.fstat(handle.fileno()).st_size - if observed_size > NATIVE_RESULT_LIMIT: - prefix = handle.read(NATIVE_RESULT_PREFIX_LIMIT) - return ( - None, - observed_size, - None, - sha256_bytes(prefix), - len(prefix), - "oversized", - ) - raw = handle.read(NATIVE_RESULT_LIMIT + 1) - observed_size = max(observed_size, os.fstat(handle.fileno()).st_size, len(raw)) - except OSError: - return None, observed_size, None, None, None, "unreadable" - - if observed_size > NATIVE_RESULT_LIMIT or len(raw) > NATIVE_RESULT_LIMIT: - prefix = raw[:NATIVE_RESULT_PREFIX_LIMIT] - return None, observed_size, None, sha256_bytes(prefix), len(prefix), "oversized" - - digest = sha256_bytes(raw) - try: - native = json.loads(raw) - except (json.JSONDecodeError, RecursionError, UnicodeDecodeError, ValueError): - return None, observed_size, digest, None, None, "invalid_json" - return native, observed_size, digest, None, None, None - - -def safe_relative_path(value: Any, *, suffix: str | None = None) -> str: - if not isinstance(value, str) or not value: - raise ConformanceError("runner paths must be non-empty strings") - path = Path(value) - if path.is_absolute() or ".." in path.parts or path.as_posix() != value: - raise ConformanceError(f"runner path must be portable and relative: {value}") - if suffix and not value.endswith(suffix): - raise ConformanceError(f"runner path must end in {suffix}: {value}") - return value - - -def validate_contract(contract: Any) -> None: - if not isinstance(contract, dict) or set(contract) != { - "$schema", - "schema", - "runtime_dependencies", - "experiments", - }: - raise ConformanceError("beta conformance contract has an invalid top-level shape") - if contract["$schema"] != "./contract-schema.json": - raise ConformanceError("beta conformance contract must reference its repository schema") - if contract["schema"] != CONTRACT_SCHEMA: - raise ConformanceError(f"beta conformance contract schema must be {CONTRACT_SCHEMA}") - if contract["runtime_dependencies"] != RUNTIME_DEPENDENCY_SELECTORS: - raise ConformanceError("beta conformance contract has invalid runtime dependency selectors") - experiments = contract["experiments"] - if not isinstance(experiments, dict) or set(experiments) != set(EXPERIMENTS): - raise ConformanceError(f"beta conformance experiments must be exactly {list(EXPERIMENTS)}") - for name, specification in experiments.items(): - if not isinstance(specification, dict) or set(specification) != { - "owning_contract", - "required_clients", - "required_distributions", - "runners", - "timeout_seconds", - }: - raise ConformanceError(f"experiment {name} has an invalid shape") - owner = specification["owning_contract"] - if not isinstance(owner, str) or not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,62}", owner): - raise ConformanceError(f"experiment {name} has an invalid owning contract") - clients = specification["required_clients"] - if ( - not isinstance(clients, list) - or not clients - or len(clients) != len(set(clients)) - or not set(clients).issubset({"sdk-php", "sdk-python", "sdk-rust"}) - ): - raise ConformanceError(f"experiment {name} has invalid required clients") - required_distributions = specification["required_distributions"] - if ( - not isinstance(required_distributions, list) - or not required_distributions - or len(required_distributions) != len(set(required_distributions)) - or not set(required_distributions).issubset(DISTRIBUTIONS) - or not {"server", *clients}.issubset(required_distributions) - ): - raise ConformanceError(f"experiment {name} has invalid required distributions") - timeout = specification["timeout_seconds"] - if not isinstance(timeout, int) or not 60 <= timeout <= 5400: - raise ConformanceError(f"experiment {name} timeout must be between 60 and 5400 seconds") - runners = specification["runners"] - if not isinstance(runners, list) or not 1 <= len(runners) <= 3: - raise ConformanceError(f"experiment {name} must have between one and three runners") - runner_ids: set[str] = set() - for runner in runners: - if ( - not isinstance(runner, dict) - or not {"id", "path", "result"}.issubset(runner) - or not set(runner).issubset( - { - "id", - "path", - "result", - "required_distributions", - "result_schema", - "required_result_fields", - "required_scenarios", - "runtime", - "source", - } - ) - ): - raise ConformanceError(f"experiment {name} runner has an invalid shape") - runner_id = runner["id"] - if ( - not isinstance(runner_id, str) - or not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,62}", runner_id) - or runner_id in runner_ids - ): - raise ConformanceError(f"experiment {name} has an invalid or duplicate runner id") - runner_ids.add(runner_id) - runner_required_distributions = runner.get("required_distributions") - if ( - not isinstance(runner_required_distributions, list) - or not runner_required_distributions - or len(runner_required_distributions) != len(set(runner_required_distributions)) - or not set(runner_required_distributions).issubset(required_distributions) - ): - raise ConformanceError(f"experiment {name} runner has invalid required distributions") - source = runner.get("source") - if source not in {"server-image", "control-plane"}: - raise ConformanceError(f"experiment {name} runner has an invalid source") - path = safe_relative_path(runner["path"]) - expected_prefix = "scripts/conformance/" if source == "server-image" else "scripts/" - if not path.startswith(expected_prefix) or not path.endswith((".sh", ".mjs", ".py")): - raise ConformanceError(f"experiment {name} runner is outside the published conformance surface") - if source == "control-plane" and path != "scripts/waterline_service_conformance.py": - raise ConformanceError(f"experiment {name} names an unsupported control-plane runner") - safe_relative_path(runner["result"], suffix=".json") - if "/" in runner["result"]: - raise ConformanceError(f"experiment {name} native result must be a file name") - result_schema = runner.get("result_schema") - required_result_fields = runner.get("required_result_fields") - required_scenarios = runner.get("required_scenarios") - if (result_schema is None) != (required_result_fields is None): - raise ConformanceError( - f"experiment {name} runner must declare its result schema and required fields together" - ) - if result_schema is not None: - if not isinstance(result_schema, str) or not re.fullmatch(r"[a-z0-9][a-z0-9.-]{0,126}", result_schema): - raise ConformanceError(f"experiment {name} runner has an invalid result schema") - if ( - not isinstance(required_result_fields, list) - or not required_result_fields - or len(required_result_fields) != len(set(required_result_fields)) - or any( - not isinstance(field, str) or not re.fullmatch(r"[A-Za-z][A-Za-z0-9_]{0,126}", field) - for field in required_result_fields - ) - ): - raise ConformanceError(f"experiment {name} runner has invalid required result fields") - if required_scenarios is not None: - if required_result_fields is None or "scenario_results" not in required_result_fields: - raise ConformanceError( - f"experiment {name} runner scenarios require a declared scenario_results field" - ) - if ( - not isinstance(required_scenarios, list) - or not required_scenarios - or len(required_scenarios) != len(set(required_scenarios)) - or any( - not isinstance(scenario, str) or not re.fullmatch(r"[a-z][a-z0-9_]{0,126}", scenario) - for scenario in required_scenarios - ) - ): - raise ConformanceError(f"experiment {name} runner has invalid required scenarios") - runtime = runner.get("runtime") - if runtime is not None: - if not isinstance(runtime, dict) or set(runtime) != { - "cache_backend", - "database_backend", - "kind", - "namespace_environment", - "network_scope", - "queue_backend", - "server_url_environment", - "token_environment", - }: - raise ConformanceError(f"experiment {name} runner has an invalid runtime dependency") - if runtime["kind"] != "standalone-server": - raise ConformanceError(f"experiment {name} runner has an unsupported runtime dependency") - if ( - runtime["database_backend"] != "mysql" - or runtime["cache_backend"] != "redis" - or runtime["queue_backend"] != "redis" - or runtime["network_scope"] != "private" - ): - raise ConformanceError(f"experiment {name} runner has an unsupported runtime topology") - environment_names = [ - runtime["namespace_environment"], - runtime["server_url_environment"], - runtime["token_environment"], - ] - if len(set(environment_names)) != len(environment_names) or any( - not isinstance(value, str) or not re.fullmatch(r"DW_[A-Z0-9_]{1,95}", value) - for value in environment_names - ): - raise ConformanceError(f"experiment {name} runner has invalid runtime environment bindings") - runner_distributions = {distribution for runner in runners for distribution in runner["required_distributions"]} - if runner_distributions != set(required_distributions): - raise ConformanceError(f"experiment {name} runners do not cover its required distributions") - covered_distributions = { - distribution - for specification in experiments.values() - for distribution in specification["required_distributions"] - } - if covered_distributions != set(DISTRIBUTIONS): - raise ConformanceError("beta conformance contract does not execute every required distribution") - - -def load_contract(path: Path) -> dict[str, Any]: - contract = load_json(path, limit=256 * 1024) - validate_contract(contract) - return contract - - -def artifact_version_components(required_distributions: list[str]) -> list[str]: - """Return the candidate components represented by distribution assignments.""" - return list(dict.fromkeys(DISTRIBUTIONS[distribution][0] for distribution in required_distributions)) - - -def runner_required_artifact_versions( - runner: dict[str, Any], required_distributions: list[str] | None = None -) -> list[str]: - """Return candidate components versioned by an executed distribution assignment.""" - required = artifact_version_components(required_distributions or runner["required_distributions"]) - runtime = runner.get("runtime") - if isinstance(runtime, dict) and runtime.get("kind") == "standalone-server" and "server" not in required: - required.append("server") - return required - - -def git(repository: Path, *arguments: str) -> bytes: - process = subprocess.run( - ["git", "-C", str(repository), *arguments], - check=False, - capture_output=True, - ) - if process.returncode: - detail = process.stderr.decode(errors="replace").strip() - raise ConformanceError(f"git {' '.join(arguments)} failed: {detail}") - return process.stdout - - -def read_candidate_record(repository: Path, manifest: dict[str, Any]) -> tuple[str, dict[str, Any]]: - record_ref = f"beta-candidate/{manifest['candidate']}" - record_commit = git(repository, "rev-parse", f"{record_ref}^{{commit}}").decode().strip() - if not COMMIT_PATTERN.fullmatch(record_commit): - raise ConformanceError(f"candidate record {record_ref} does not resolve to a full Git commit") - try: - recorded_manifest = json.loads(git(repository, "show", f"{record_ref}:candidate.json")) - verification = json.loads(git(repository, "show", f"{record_ref}:verification.json")) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise ConformanceError(f"candidate record {record_ref} contains invalid JSON") from error - if canonical_json(recorded_manifest) != canonical_json(manifest): - raise ConformanceError(f"candidate record {record_ref} does not contain the requested immutable tuple") - try: - validate_verification(verification, manifest) - except CandidateError as error: - raise ConformanceError(f"candidate record {record_ref} has invalid verification: {error}") from error - return record_commit, verification - - -def distribution_locator(name: str, version: str) -> str: - _component_name, component = DISTRIBUTIONS[name] - return f"{component.distribution}:{component.package}@{version}" - - -def distribution_version(components: dict[str, Any], name: str) -> str: - component_name, _component = DISTRIBUTIONS[name] - return components[component_name]["version"] - - -def pypi_release_identity(version: str) -> tuple[str, str, str, str | None, str | None] | None: - stable = re.fullmatch(r"(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)", version) - if stable: - major, minor, patch = stable.groups() - return major, minor, patch, None, None - semver = PYPI_SEMVER_PRERELEASE_PATTERN.fullmatch(version) - if semver: - major, minor, patch, prerelease, ordinal = semver.groups() - phase = {"alpha": "a", "beta": "b", "rc": "rc"}[prerelease] - return major, minor, patch, phase, ordinal - native = PYPI_NATIVE_PRERELEASE_PATTERN.fullmatch(version) - if native: - major, minor, patch, phase, ordinal = native.groups() - return major, minor, patch, phase, ordinal - return None - - -def registry_versions_equivalent(name: str, observed: str, expected: str) -> bool: - """Compare exact candidate versions with the canonical spelling of their registry.""" - if observed == expected: - return True - if name != "sdk-python": - return False - observed_identity = pypi_release_identity(observed) - return observed_identity is not None and observed_identity == pypi_release_identity(expected) - - -def distribution_artifact(name: Any, sha256: Any) -> dict[str, str]: - if not isinstance(name, str) or not name or len(name) > 256: - raise ConformanceError("candidate verification has an invalid distribution artifact name") - if not isinstance(sha256, str) or not DIGEST_PATTERN.fullmatch(sha256): - raise ConformanceError(f"candidate verification distribution artifact {name} has no SHA-256 identity") - return {"name": name, "sha256": sha256} - - -def normalized_distribution_identity(name: str, version: str, artifacts: list[dict[str, str]]) -> dict[str, Any]: - ordered = sorted(artifacts, key=lambda artifact: artifact["name"]) - if not ordered or len(ordered) != len({artifact["name"] for artifact in ordered}): - raise ConformanceError(f"candidate verification has invalid {name} distribution artifacts") - return { - "kind": DISTRIBUTIONS[name][1].distribution, - "locator": distribution_locator(name, version), - "artifacts": ordered, - } - - -def normalize_distribution_identities( - verification: dict[str, Any], manifest: dict[str, Any] -) -> dict[str, dict[str, Any]]: - identities: dict[str, dict[str, Any]] = {} - for name, (component_name, component) in DISTRIBUTIONS.items(): - version = manifest["components"][component_name]["version"] - component_verification = verification["components"][component_name] - if name == "waterline": - distributions = component_verification.get("distributions") - distribution = distributions.get("embedded") if isinstance(distributions, dict) else None - elif name == WATERLINE_SERVICE_DISTRIBUTION: - distributions = component_verification.get("distributions") - distribution = distributions.get("service") if isinstance(distributions, dict) else None - else: - distribution = component_verification.get("distribution") - if not isinstance(distribution, dict) or distribution.get("kind") != component.distribution: - raise ConformanceError(f"candidate verification has no exact {name} distribution identity") - if component.distribution == "composer": - dist = distribution.get("dist") - artifacts = [ - distribution_artifact(component.package, dist.get("sha256") if isinstance(dist, dict) else None) - ] - elif component.distribution == "github-release": - raw_assets = distribution.get("assets") - if not isinstance(raw_assets, list): - raise ConformanceError("candidate verification has no CLI release-asset identities") - artifacts = [ - distribution_artifact(asset.get("name"), asset.get("sha256")) - for asset in raw_assets - if isinstance(asset, dict) - ] - if {artifact["name"] for artifact in artifacts} != CLI_ASSETS: - raise ConformanceError("candidate verification does not identify every required CLI release asset") - elif component.distribution == "pypi": - raw_files = distribution.get("files") - if not isinstance(raw_files, list): - raise ConformanceError("candidate verification has no PyPI file identities") - artifacts = [ - distribution_artifact(item.get("filename"), item.get("sha256")) - for item in raw_files - if isinstance(item, dict) - ] - elif component.distribution == "crates.io": - archive = distribution.get("archive") - artifacts = [ - distribution_artifact( - f"{component.package}-{version}.crate", - archive.get("sha256") if isinstance(archive, dict) else None, - ) - ] - elif component.distribution == "oci": - digest = distribution.get("manifest_digest") - artifacts = [ - distribution_artifact("manifest", digest.removeprefix("sha256:") if isinstance(digest, str) else None) - ] - else: - raise AssertionError(f"unsupported distribution kind: {component.distribution}") - identities[name] = normalized_distribution_identity(name, version, artifacts) - return identities - - -def validate_distribution_identity(name: str, identity: Any, components: dict[str, Any]) -> None: - expected_locator = distribution_locator(name, distribution_version(components, name)) - if ( - not isinstance(identity, dict) - or set(identity) != {"kind", "locator", "artifacts"} - or identity["kind"] != DISTRIBUTIONS[name][1].distribution - or identity["locator"] != expected_locator - ): - raise ConformanceError(f"distribution identity for {name} has an invalid locator") - artifacts = identity["artifacts"] - if not isinstance(artifacts, list) or not 1 <= len(artifacts) <= 128: - raise ConformanceError(f"distribution identity for {name} has invalid artifacts") - names = [] - for artifact in artifacts: - if ( - not isinstance(artifact, dict) - or set(artifact) != {"name", "sha256"} - or not isinstance(artifact["name"], str) - or not artifact["name"] - or len(artifact["name"]) > 256 - or not DIGEST_PATTERN.fullmatch(str(artifact["sha256"])) - ): - raise ConformanceError(f"distribution identity for {name} has an invalid artifact digest") - names.append(artifact["name"]) - if names != sorted(names) or len(names) != len(set(names)): - raise ConformanceError(f"distribution identity for {name} artifacts are not uniquely normalized") - - -def validate_partial_distribution_identities(identities: Any, components: dict[str, Any]) -> None: - if not isinstance(identities, dict) or not set(identities).issubset(DISTRIBUTIONS): - raise ConformanceError("executed distribution identities name an unknown component") - for name, identity in identities.items(): - validate_distribution_identity(name, identity, components) - - -def validate_distribution_identities(identities: Any, components: dict[str, Any]) -> None: - if not isinstance(identities, dict) or set(identities) != set(DISTRIBUTIONS): - raise ConformanceError("distribution identities do not bind every required distribution") - validate_partial_distribution_identities(identities, components) - - -def validate_runtime_dependencies(dependencies: Any) -> None: - if not isinstance(dependencies, dict) or set(dependencies) != set(RUNTIME_DEPENDENCY_SELECTORS): - raise ConformanceError("runtime dependencies do not bind the declared MySQL and Redis images") - for name, selector in RUNTIME_DEPENDENCY_SELECTORS.items(): - dependency = dependencies[name] - repository = selector.rsplit(":", 1)[0] - digest = dependency.get("manifest_digest") if isinstance(dependency, dict) else None - if ( - not isinstance(dependency, dict) - or set(dependency) != {"selector", "image", "manifest_digest"} - or dependency["selector"] != selector - or not isinstance(digest, str) - or not OCI_DIGEST_PATTERN.fullmatch(digest) - or dependency["image"] != f"{repository}@{digest}" - ): - raise ConformanceError(f"runtime dependency {name} has no exact OCI manifest binding") - - -def normalized_oci_repository(value: str) -> str: - repository = value.removeprefix("docker.io/") - if "/" not in repository: - repository = f"library/{repository}" - return repository - - -def resolve_runtime_dependencies(contract: dict[str, Any], *, docker: str = "docker") -> dict[str, dict[str, str]]: - """Resolve declared selectors once so isolated jobs consume immutable references.""" - validate_contract(contract) - dependencies: dict[str, dict[str, str]] = {} - for name, selector in contract["runtime_dependencies"].items(): - docker_runtime_command([docker, "pull", selector]) - inspection = docker_runtime_command([docker, "image", "inspect", "--format", "{{json .RepoDigests}}", selector]) - try: - references = json.loads(inspection.stdout) - except (json.JSONDecodeError, TypeError) as error: - raise ConformanceError(f"runtime dependency {name} has invalid Docker digest evidence") from error - if not isinstance(references, list): - raise ConformanceError(f"runtime dependency {name} has invalid Docker digest evidence") - expected_repository = normalized_oci_repository(selector.rsplit(":", 1)[0]) - digests = { - digest - for reference in references - if isinstance(reference, str) - for repository, separator, digest in [reference.rpartition("@")] - if separator - and normalized_oci_repository(repository) == expected_repository - and OCI_DIGEST_PATTERN.fullmatch(digest) - } - if len(digests) != 1: - raise ConformanceError(f"runtime dependency {name} did not resolve to one immutable OCI manifest digest") - digest = digests.pop() - repository = selector.rsplit(":", 1)[0] - dependencies[name] = { - "selector": selector, - "image": f"{repository}@{digest}", - "manifest_digest": digest, - } - validate_runtime_dependencies(dependencies) - return dependencies - - -def prepare_plan( - repository: Path, - manifest: dict[str, Any], - contract: dict[str, Any], - runner_revision: str, - runtime_dependencies: dict[str, Any], -) -> dict[str, Any]: - validate_contract(contract) - validate_runtime_dependencies(runtime_dependencies) - if not COMMIT_PATTERN.fullmatch(runner_revision): - raise ConformanceError("runner revision must be a full lowercase Git commit") - git(repository, "cat-file", "-e", f"{runner_revision}^{{commit}}") - record_commit, verification = read_candidate_record(repository, manifest) - distribution_identities = normalize_distribution_identities(verification, manifest) - server_distribution = verification["components"]["server"].get("distribution") - if not isinstance(server_distribution, dict): - raise ConformanceError("candidate verification has no server distribution identity") - image = server_distribution.get("image") - image_digest = server_distribution.get("manifest_digest") - expected_tag = f"docker.io/durableworkflow/server:{manifest['components']['server']['version']}" - if image != expected_tag or not isinstance(image_digest, str) or not OCI_DIGEST_PATTERN.fullmatch(image_digest): - raise ConformanceError("candidate verification has no exact matching server image digest") - waterline_distributions = verification["components"]["waterline"].get("distributions") - waterline_service = waterline_distributions.get("service") if isinstance(waterline_distributions, dict) else None - waterline_image = waterline_service.get("image") if isinstance(waterline_service, dict) else None - waterline_digest = waterline_service.get("manifest_digest") if isinstance(waterline_service, dict) else None - expected_waterline_tag = f"docker.io/durableworkflow/waterline:{manifest['components']['waterline']['version']}" - if ( - waterline_image != expected_waterline_tag - or not isinstance(waterline_digest, str) - or not OCI_DIGEST_PATTERN.fullmatch(waterline_digest) - ): - raise ConformanceError("candidate verification has no exact matching Waterline service image digest") - components = manifest["components"] - plan = { - "schema": PLAN_SCHEMA, - "candidate": { - "name": manifest["candidate"], - "manifest_sha256": manifest_digest(manifest), - "verification_sha256": sha256_bytes(canonical_json(verification)), - "record_ref": f"beta-candidate/{manifest['candidate']}", - "record_commit": record_commit, - }, - "artifact_tuple": components, - "source_identities": {name: identity["commit"] for name, identity in components.items()}, - "distribution_identities": distribution_identities, - "runtime_dependencies": runtime_dependencies, - "runner": { - "repository": "durable-workflow/.github", - "revision": runner_revision, - "contract_sha256": sha256_bytes(canonical_json(contract)), - }, - "server_runner": { - "image": f"docker.io/durableworkflow/server@{image_digest}", - "manifest_digest": image_digest, - "source_commit": components["server"]["commit"], - }, - "waterline_service_runner": { - "image": f"docker.io/durableworkflow/waterline@{waterline_digest}", - "manifest_digest": waterline_digest, - "source_commit": components["waterline"]["commit"], - }, - "experiments": list(EXPERIMENTS), - } - validate_plan(plan) - return plan - - -def validate_plan(plan: Any) -> None: - _validate_plan(plan, PLAN_SCHEMA, set(DISTRIBUTIONS), require_waterline_service=True) - - -def validate_recorded_plan(plan: Any) -> None: - schema = plan.get("schema") if isinstance(plan, dict) else None - if schema == PLAN_SCHEMA: - validate_plan(plan) - return - if schema == LEGACY_PLAN_SCHEMA: - _validate_plan(plan, LEGACY_PLAN_SCHEMA, set(COMPONENTS), require_waterline_service=False) - return - raise ConformanceError("recorded beta conformance plan uses an unsupported schema") - - -def _validate_plan( - plan: Any, - schema: str, - required_distributions: set[str], - *, - require_waterline_service: bool, -) -> None: - required = { - "schema", - "candidate", - "artifact_tuple", - "source_identities", - "distribution_identities", - "runtime_dependencies", - "runner", - "server_runner", - "experiments", - } - if require_waterline_service: - required.add("waterline_service_runner") - if not isinstance(plan, dict) or set(plan) != required or plan.get("schema") != schema: - raise ConformanceError("beta conformance plan has an invalid top-level shape") - components = plan["artifact_tuple"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise ConformanceError("beta conformance plan does not bind the exact seven-artifact tuple") - for name, identity in components.items(): - if not isinstance(identity, dict) or set(identity) != {"version", "commit"}: - raise ConformanceError(f"beta conformance plan has an invalid {name} identity") - if ( - not isinstance(identity["version"], str) - or not VERSION_PATTERN.fullmatch(identity["version"]) - or not COMMIT_PATTERN.fullmatch(str(identity["commit"])) - ): - raise ConformanceError(f"beta conformance plan has an invalid {name} version or commit") - sources = plan["source_identities"] - if not isinstance(sources, dict) or sources != {name: item["commit"] for name, item in components.items()}: - raise ConformanceError("beta conformance plan source identities do not match the artifact tuple") - if ( - not isinstance(plan["distribution_identities"], dict) - or set(plan["distribution_identities"]) != required_distributions - ): - raise ConformanceError("distribution identities do not bind every required distribution") - validate_partial_distribution_identities(plan["distribution_identities"], components) - validate_runtime_dependencies(plan["runtime_dependencies"]) - candidate = plan["candidate"] - if ( - not isinstance(candidate, dict) - or set(candidate) != {"name", "manifest_sha256", "verification_sha256", "record_ref", "record_commit"} - or not isinstance(candidate["name"], str) - or not CANDIDATE_PATTERN.fullmatch(candidate["name"]) - or not DIGEST_PATTERN.fullmatch(str(candidate["manifest_sha256"])) - or not DIGEST_PATTERN.fullmatch(str(candidate["verification_sha256"])) - or not COMMIT_PATTERN.fullmatch(str(candidate["record_commit"])) - or candidate["record_ref"] != f"beta-candidate/{candidate['name']}" - ): - raise ConformanceError("beta conformance plan has an invalid candidate binding") - runner = plan["runner"] - if ( - not isinstance(runner, dict) - or set(runner) != {"repository", "revision", "contract_sha256"} - or runner["repository"] != "durable-workflow/.github" - or not COMMIT_PATTERN.fullmatch(str(runner["revision"])) - or not DIGEST_PATTERN.fullmatch(str(runner["contract_sha256"])) - ): - raise ConformanceError("beta conformance plan has an invalid runner binding") - server_runner = plan["server_runner"] - digest = server_runner.get("manifest_digest") if isinstance(server_runner, dict) else None - if ( - not isinstance(server_runner, dict) - or set(server_runner) != {"image", "manifest_digest", "source_commit"} - or not isinstance(digest, str) - or not OCI_DIGEST_PATTERN.fullmatch(digest) - or server_runner["image"] != f"docker.io/durableworkflow/server@{digest}" - or server_runner["source_commit"] != components["server"]["commit"] - ): - raise ConformanceError("beta conformance plan has an invalid published server runner binding") - if require_waterline_service: - waterline_runner = plan["waterline_service_runner"] - waterline_digest = waterline_runner.get("manifest_digest") if isinstance(waterline_runner, dict) else None - if ( - not isinstance(waterline_runner, dict) - or set(waterline_runner) != {"image", "manifest_digest", "source_commit"} - or not isinstance(waterline_digest, str) - or not OCI_DIGEST_PATTERN.fullmatch(waterline_digest) - or waterline_runner["image"] != f"docker.io/durableworkflow/waterline@{waterline_digest}" - or waterline_runner["source_commit"] != components["waterline"]["commit"] - ): - raise ConformanceError( - "beta conformance plan has an invalid published Waterline service runner binding" - ) - if plan["experiments"] != list(EXPERIMENTS): - raise ConformanceError("beta conformance plan does not select the complete experiment set") - - -def restore_plan( - plan: dict[str, Any], - manifest: dict[str, Any], - contract: dict[str, Any], - runner_revision: str, -) -> dict[str, Any]: - """Restore a first-attempt plan without resolving mutable inputs again.""" - validate_plan(plan) - validate_contract(contract) - if not COMMIT_PATTERN.fullmatch(runner_revision) or plan["runner"]["revision"] != runner_revision: - raise ConformanceError("restored beta conformance plan does not bind this workflow revision") - if plan["runner"]["contract_sha256"] != sha256_bytes(canonical_json(contract)): - raise ConformanceError("restored beta conformance plan does not bind this contract") - if ( - plan["candidate"]["name"] != manifest["candidate"] - or plan["candidate"]["manifest_sha256"] != manifest_digest(manifest) - or plan["artifact_tuple"] != manifest["components"] - ): - raise ConformanceError("restored beta conformance plan does not bind the requested candidate") - return plan - - -def plan_github_outputs(plan: dict[str, Any]) -> dict[str, str]: - validate_plan(plan) - return { - "candidate": plan["candidate"]["name"], - "experiments": json.dumps(plan["experiments"], separators=(",", ":")), - "manifest_sha256": plan["candidate"]["manifest_sha256"], - } - - -def load_plan(path: Path) -> dict[str, Any]: - plan = load_json(path, limit=256 * 1024) - validate_plan(plan) - return plan - - -def run_checked(command: list[str], *, capture: bool = False) -> subprocess.CompletedProcess[str]: - process = subprocess.run(command, check=False, text=True, capture_output=capture) - if process.returncode: - detail = (process.stderr or process.stdout or "").strip() if capture else "" - raise ConformanceError(f"command failed ({process.returncode}): {' '.join(command)}: {detail}") - return process - - -def extract_runner(plan: dict[str, Any], output: Path, extraction_record: Path, docker: str = "docker") -> None: - validate_plan(plan) - if output.exists() and any(output.iterdir()): - raise ConformanceError(f"published runner output directory is not empty: {output}") - output.parent.mkdir(parents=True, exist_ok=True) - temporary = Path(tempfile.mkdtemp(prefix="beta-conformance-image-", dir=output.parent)) - container_id = "" - image = plan["server_runner"]["image"] - try: - run_checked([docker, "pull", image], capture=True) - inspection = run_checked( - [docker, "image", "inspect", "--format", "{{json .RepoDigests}}", image], capture=True - ).stdout - if plan["server_runner"]["manifest_digest"] not in inspection: - raise ConformanceError("pulled server image inspection does not contain the candidate manifest digest") - container_id = run_checked([docker, "create", image], capture=True).stdout.strip() - if not container_id: - raise ConformanceError("docker create returned no container identity") - extracted = temporary / "app" - extracted.mkdir() - run_checked([docker, "cp", f"{container_id}:/app/.", str(extracted)], capture=True) - if output.exists(): - output.rmdir() - extracted.rename(output) - write_json( - extraction_record, - { - "schema": "durable-workflow.beta-conformance.server-runner-extraction/v1", - "image": image, - "manifest_digest": plan["server_runner"]["manifest_digest"], - "source_commit": plan["server_runner"]["source_commit"], - "local_product_source_checkout_used": False, - }, - ) - finally: - if container_id: - subprocess.run([docker, "rm", "-f", container_id], check=False, capture_output=True) - shutil.rmtree(temporary, ignore_errors=True) - - -def tail_and_digest(path: Path) -> tuple[str, str]: - digest = sha256_file(path) - with path.open("rb") as handle: - size = path.stat().st_size - if size > DIAGNOSTIC_LIMIT: - handle.seek(-DIAGNOSTIC_LIMIT, os.SEEK_END) - value = handle.read(DIAGNOSTIC_LIMIT).decode(errors="replace") - return sanitized_evidence_text(value, DIAGNOSTIC_LIMIT), digest - - -def bounded_text(value: Any, limit: int = FINDING_TEXT_LIMIT) -> str: - text = str(value).replace("\x00", "") - return text if len(text) <= limit else text[: limit - 1] + "…" - - -def evidence_value_parts(value: str) -> tuple[str, str, str]: - for quote in (r"\"", r"\'", '"', "'"): - if value.startswith(quote) and value.endswith(quote) and len(value) >= len(quote) * 2: - return quote, value[len(quote) : -len(quote)], quote - return "", value, "" - - -def quoted_evidence_value_end(text: str, start: int) -> int | None: - escaped_wrapper = text.startswith((r"\"", r"\'"), start) - if escaped_wrapper: - quote = text[start + 1] - cursor = start + 2 - elif start < len(text) and text[start] in {'"', "'"}: - quote = text[start] - cursor = start + 1 - else: - return None - - while cursor < len(text) and text[cursor] not in "\r\n": - if text[cursor] == quote: - slash_start = cursor - while slash_start > start and text[slash_start - 1] == "\\": - slash_start -= 1 - slash_count = cursor - slash_start - if (escaped_wrapper and slash_count == 1) or (not escaped_wrapper and slash_count % 2 == 0): - end = cursor + 1 - return end if end == len(text) or text[end] in EVIDENCE_QUOTED_TERMINATORS else len(text) - cursor += 1 - return len(text) - - -def evidence_value_end(text: str, start: int) -> int: - if "\n" in text[start:] or "\r" in text[start:]: - return len(text) - quoted_end = quoted_evidence_value_end(text, start) - if quoted_end is not None: - return quoted_end - - # Whitespace and punctuation are valid secret characters in arbitrary log - # scalars, so an unquoted value has no trustworthy boundary. Consume its - # bounded remainder, including any text after a pre-redacted prefix. - return len(text) - - -def evidence_value_spans(text: str) -> Iterator[tuple[int, int, bool]]: - search_from = 0 - while match := EVIDENCE_ASSIGNMENT_PREFIX.search(text, search_from): - if SENSITIVE_EVIDENCE_KEY.search(match.group("key")) is None: - search_from = match.end() - continue - start = match.end() - end = evidence_value_end(text, start) - if end > start: - authorization = "authorization" in match.group("key").lower() - yield start, end, authorization - search_from = end - else: - search_from = start + 1 - - -def redacted_evidence_value(value: str, preserve_bearer: bool = False) -> str: - opening, inner, closing = evidence_value_parts(value) - stripped = inner.strip() - if re.fullmatch(r"(?:bearer\s+)?\[REDACTED\]", stripped, flags=re.IGNORECASE): - return value - bearer = re.match(r"\s*(bearer\s+)", inner, flags=re.IGNORECASE) if preserve_bearer else None - replacement = f"{bearer.group(1)}[REDACTED]" if bearer else "[REDACTED]" - return f"{opening}{replacement}{closing}" - - -def redact_evidence_assignments(text: str) -> str: - fragments: list[str] = [] - cursor = 0 - for start, end, authorization in evidence_value_spans(text): - fragments.append(text[cursor:start]) - fragments.append(redacted_evidence_value(text[start:end], preserve_bearer=authorization)) - cursor = end - fragments.append(text[cursor:]) - return "".join(fragments) - - -def contains_sensitive_evidence_text(value: str) -> bool: - if BETA_TOKEN_EVIDENCE.search(value) or CREDENTIAL_URL_EVIDENCE.search(value): - return True - return any( - not re.fullmatch( - r"(?:bearer\s+)?\[REDACTED\]" if authorization else r"\[REDACTED\]", - evidence_value_parts(value[start:end])[1].strip(), - flags=re.IGNORECASE, - ) - for start, end, authorization in evidence_value_spans(value) - ) - - -def sanitized_evidence_text(value: Any, limit: int = 512) -> str: - text = str(value).replace("\x00", "") - text = redact_evidence_assignments(text) - text = BETA_TOKEN_EVIDENCE.sub("[REDACTED]", text) - text = CREDENTIAL_URL_EVIDENCE.sub( - lambda match: f"{match.group(0).split('://', 1)[0]}://[REDACTED]@", - text, - ) - return bounded_text(text, limit) - - -def validate_public_evidence_strings(value: Any) -> None: - """Reject credential-shaped text anywhere in a public JSON asset.""" - - def has_sensitive_string(entry: Any) -> bool: - if isinstance(entry, str): - return contains_sensitive_evidence_text(entry) - if isinstance(entry, list): - return any(has_sensitive_string(item) for item in entry) - if isinstance(entry, dict): - return any( - contains_sensitive_evidence_text(str(key)) - or ( - SENSITIVE_EVIDENCE_KEY.search(str(key)) is not None - and nested not in ("[REDACTED]", "Bearer [REDACTED]", "bearer [REDACTED]") - ) - or has_sensitive_string(nested) - for key, nested in entry.items() - ) - return False - - if has_sensitive_string(value): - raise ConformanceError("public conformance evidence contains unsanitized sensitive text") - - -def bounded_sanitized_evidence(value: Any, limit: int = NATIVE_FAILURE_COMPONENT_LIMIT) -> Any: - def sanitize(entry: Any, depth: int = 0) -> Any: - if entry is None or isinstance(entry, bool | int | float): - return entry - if isinstance(entry, str): - return sanitized_evidence_text(entry) - if depth >= 7: - return "[depth limit reached]" - if isinstance(entry, list): - return [sanitize(item, depth + 1) for item in entry[:16]] - if isinstance(entry, dict): - result: dict[str, Any] = {} - for key, nested in list(entry.items())[:32]: - safe_key = sanitized_evidence_text(key, 128) - result[safe_key] = ( - "[REDACTED]" if SENSITIVE_EVIDENCE_KEY.search(safe_key) else sanitize(nested, depth + 1) - ) - return result - return sanitized_evidence_text(entry) - - sanitized = sanitize(value) - if len(canonical_json(sanitized)) <= limit: - return sanitized - - serialized = json.dumps(sanitized, sort_keys=True, ensure_ascii=True, separators=(",", ":")) - low = 0 - high = len(serialized) - bounded: dict[str, Any] = {"_truncated": True, "bounded_json_excerpt": ""} - while low <= high: - middle = (low + high) // 2 - candidate = { - "_truncated": True, - "bounded_json_excerpt": sanitized_evidence_text(serialized[:middle], middle), - } - if len(canonical_json(candidate)) <= limit: - bounded = candidate - low = middle + 1 - else: - high = middle - 1 - return bounded - - -def summarize_native_failure_projection(native: dict[str, Any]) -> dict[str, Any]: - projection: dict[str, Any] = { - "max_bytes": NATIVE_FAILURE_PROJECTION_LIMIT, - "component_max_bytes": NATIVE_FAILURE_COMPONENT_LIMIT, - "truncated": False, - "scenarios": [], - } - raw_scenarios = native.get("scenario_results", native.get("scenarioResults", {})) - if isinstance(raw_scenarios, dict): - items = list(raw_scenarios.items())[:128] - elif isinstance(raw_scenarios, list): - items = [(str(index), value) for index, value in enumerate(raw_scenarios[:128])] - else: - return projection - - for raw_id, value in items: - if isinstance(value, dict): - scenario_id = value.get("scenario_id", value.get("id", raw_id)) - status = value.get("status", value.get("outcome", "unknown")) - observed = value.get("observed_outputs", value.get("observedOutputs", {})) - linked_findings = value.get("linked_findings", value.get("linkedFindings")) - else: - scenario_id = raw_id - status = value - observed = {} - linked_findings = None - normalized_status = sanitized_evidence_text(status, 64) - if normalized_status in PASS_OUTCOMES: - continue - if not isinstance(observed, dict): - observed = {} - scenario = { - "id": sanitized_evidence_text(scenario_id, 128), - "status": normalized_status, - "failure_stage": ( - sanitized_evidence_text(observed["failure_stage"], 128) - if isinstance(observed.get("failure_stage"), str) - else None - ), - "failure_classification": ( - sanitized_evidence_text(observed["failure_classification"], 128) - if isinstance(observed.get("failure_classification"), str) - else None - ), - "failure_owner": ( - sanitized_evidence_text(observed["failure_owner"], 128) - if isinstance(observed.get("failure_owner"), str) - else None - ), - "worker_evidence": bounded_sanitized_evidence(observed.get("worker_evidence")), - "server_evidence": bounded_sanitized_evidence(observed.get("server_evidence")), - "linked_findings": bounded_sanitized_evidence(linked_findings), - } - candidate = {**projection, "scenarios": [*projection["scenarios"], scenario]} - if len(canonical_json(candidate)) > NATIVE_FAILURE_PROJECTION_LIMIT: - projection["truncated"] = True - break - projection["scenarios"].append(scenario) - return projection - - -def native_failure_projection_error(projection: Any) -> str: - if not isinstance(projection, dict) or set(projection) != { - "max_bytes", - "component_max_bytes", - "truncated", - "scenarios", - }: - return "experiment result has an invalid native failure projection" - if ( - projection["max_bytes"] != NATIVE_FAILURE_PROJECTION_LIMIT - or projection["component_max_bytes"] != NATIVE_FAILURE_COMPONENT_LIMIT - or not isinstance(projection["truncated"], bool) - or not isinstance(projection["scenarios"], list) - or len(projection["scenarios"]) > 128 - or len(canonical_json(projection)) > NATIVE_FAILURE_PROJECTION_LIMIT - ): - return "experiment result has an unbounded native failure projection" - - def has_secret(value: Any) -> bool: - if isinstance(value, str): - return contains_sensitive_evidence_text(value) - if isinstance(value, list): - return any(has_secret(entry) for entry in value) - if isinstance(value, dict): - return any( - contains_sensitive_evidence_text(str(key)) - or (SENSITIVE_EVIDENCE_KEY.search(str(key)) is not None and nested != "[REDACTED]") - or has_secret(nested) - for key, nested in value.items() - ) - return False - - for scenario in projection["scenarios"]: - if not isinstance(scenario, dict) or set(scenario) != { - "id", - "status", - "failure_stage", - "failure_classification", - "failure_owner", - "worker_evidence", - "server_evidence", - "linked_findings", - }: - return "experiment result has a malformed native failure scenario" - if ( - not isinstance(scenario["id"], str) - or not scenario["id"] - or len(scenario["id"]) > 128 - or not isinstance(scenario["status"], str) - or not scenario["status"] - or len(scenario["status"]) > 64 - or scenario["status"] in PASS_OUTCOMES - or any( - value is not None and (not isinstance(value, str) or len(value) > 128) - for value in ( - scenario["failure_stage"], - scenario["failure_classification"], - scenario["failure_owner"], - ) - ) - ): - return "experiment result has invalid native failure attribution" - if any( - has_secret(value) - for value in ( - scenario["id"], - scenario["status"], - scenario["failure_stage"], - scenario["failure_classification"], - scenario["failure_owner"], - ) - ): - return "experiment result has unsanitized native failure attribution" - for field in ("worker_evidence", "server_evidence", "linked_findings"): - if len(canonical_json(scenario[field])) > NATIVE_FAILURE_COMPONENT_LIMIT: - return "experiment result has an unbounded native failure evidence component" - if has_secret(scenario[field]): - return "experiment result has unsanitized native failure evidence" - return "" - - -def summarize_findings(native: Any) -> list[dict[str, str]]: - if not isinstance(native, dict): - return [] - candidates = native.get("findings") - if not isinstance(candidates, list): - candidates = [] - summaries: list[dict[str, str]] = [] - for finding in candidates[:FINDING_LIMIT]: - if isinstance(finding, dict): - owner = next( - ( - finding.get(key) - for key in ("owning_contract", "owning_surface", "owner", "surface") - if finding.get(key) - ), - "unspecified", - ) - summary = next( - (finding.get(key) for key in ("summary", "title", "reason", "message", "type") if finding.get(key)), - "native conformance finding", - ) - kind = finding.get("type") or finding.get("id") or "finding" - else: - owner = "unspecified" - summary = finding - kind = "finding" - summaries.append( - { - "type": sanitized_evidence_text(kind, 128), - "owning_contract": sanitized_evidence_text(owner, 128), - "summary": sanitized_evidence_text(summary, FINDING_TEXT_LIMIT), - } - ) - return summaries - - -def native_state(native: Any) -> tuple[str | None, bool, list[dict[str, str]]]: - if not isinstance(native, dict): - return None, False, [] - raw_outcome = native.get("outcome", native.get("status")) - outcome = sanitized_evidence_text(str(raw_outcome).lower(), 128) if raw_outcome is not None else None - runner_blocked = native.get("runner_blocked") is True or native.get("runnerBlocked") is True - return outcome, runner_blocked, summarize_findings(native) - - -def native_distribution_identity_structure_error(name: str, identity: Any) -> str: - if not isinstance(identity, dict) or set(identity) != {"kind", "locator", "artifacts"}: - return f"published runner result has a malformed {name} distribution identity body" - _component_name, component = DISTRIBUTIONS[name] - locator_prefix = f"{component.distribution}:{component.package}@" - locator_version = ( - identity["locator"][len(locator_prefix) :] - if isinstance(identity.get("locator"), str) and identity["locator"].startswith(locator_prefix) - else "" - ) - if ( - not isinstance(identity["kind"], str) - or not identity["kind"] - or len(identity["kind"]) > 64 - or identity["kind"] != component.distribution - or not isinstance(identity["locator"], str) - or not identity["locator"] - or len(identity["locator"]) > 256 - or not identity["locator"].startswith(locator_prefix) - or ( - VERSION_PATTERN.fullmatch(locator_version) is None - and (name != "sdk-python" or PYPI_NATIVE_PRERELEASE_PATTERN.fullmatch(locator_version) is None) - ) - ): - return f"published runner result has a malformed {name} distribution identity locator" - artifacts = identity["artifacts"] - if not isinstance(artifacts, list) or not 1 <= len(artifacts) <= 128: - return f"published runner result has malformed {name} distribution identity artifacts" - artifact_names: list[str] = [] - for artifact in artifacts: - if ( - not isinstance(artifact, dict) - or set(artifact) != {"name", "sha256"} - or not isinstance(artifact["name"], str) - or not artifact["name"] - or len(artifact["name"]) > 256 - or not isinstance(artifact["sha256"], str) - or not DIGEST_PATTERN.fullmatch(artifact["sha256"]) - ): - return f"published runner result has a malformed {name} distribution artifact identity" - artifact_names.append(artifact["name"]) - if artifact_names != sorted(artifact_names) or len(artifact_names) != len(set(artifact_names)): - return f"published runner result has non-normalized {name} distribution artifact identities" - return "" - - -def native_result_completeness_error( - native: Any, - required_distributions: list[str], - runner: dict[str, Any], -) -> str: - if not isinstance(native, dict): - return "published runner result must be a JSON object" - required_fields = runner.get("required_result_fields", []) - missing_fields = [field for field in required_fields if field not in native] - if missing_fields: - return f"published runner result is missing required fields: {', '.join(missing_fields)}" - result_schema = runner.get("result_schema") - if result_schema is not None and native.get("schema") != result_schema: - return "published runner result does not use its declared schema" - for field in ("started_at", "finished_at"): - if field in required_fields and (not isinstance(native[field], str) or not native[field].strip()): - return f"published runner result has an invalid {field} value" - outcome = native.get("outcome", native.get("status")) - if not isinstance(outcome, str) or not outcome.strip(): - return "published runner result does not declare an outcome" - if "runner_blocked" in required_fields and not isinstance(native["runner_blocked"], bool): - return "published runner result does not declare a boolean runner_blocked value" - required_artifact_versions = set(runner_required_artifact_versions(runner, required_distributions)) - versions = native.get("artifact_versions", native.get("artifactVersions")) - if not isinstance(versions, dict) or any( - name not in versions or not isinstance(versions[name], str) or not versions[name] - for name in required_artifact_versions - ): - return "published runner result does not retain every required artifact version" - extra_versions = set(versions) - required_artifact_versions - if extra_versions: - return ( - "published runner result retains artifact versions outside its required distributions: " - f"{', '.join(sorted(extra_versions))}" - ) - identities = native.get( - "executed_distribution_identities", - native.get("executedDistributionIdentities"), - ) - if not isinstance(identities, dict) or any(name not in identities for name in required_distributions): - return "published runner result does not retain every required distribution identity" - extra_identities = set(identities) - set(required_distributions) - if extra_identities: - return ( - "published runner result retains distribution identities outside its required distributions: " - f"{', '.join(sorted(extra_identities))}" - ) - for name, identity in identities.items(): - identity_error = native_distribution_identity_structure_error(name, identity) - if identity_error: - return identity_error - if "runtime_matrix" in required_fields and not isinstance(native["runtime_matrix"], dict): - return "published runner result does not retain a runtime matrix" - scenarios = native.get("scenario_results", native.get("scenarioResults")) - if not isinstance(scenarios, dict | list) or not scenarios: - return "published runner result does not retain scenario statuses" - required_scenarios = runner.get("required_scenarios", []) - if required_scenarios: - if not isinstance(scenarios, dict): - return "published runner result does not retain keyed required scenario statuses" - missing_scenarios = [scenario for scenario in required_scenarios if scenario not in scenarios] - if missing_scenarios: - return f"published runner result is missing required scenarios: {', '.join(missing_scenarios)}" - for scenario in required_scenarios: - cell = scenarios[scenario] - if ( - not isinstance(cell, dict) - or cell.get("scenario_id") != scenario - or not isinstance(cell.get("status"), str) - or cell["status"] not in NATIVE_SCENARIO_STATUSES - ): - return f"published runner result has a malformed {scenario} scenario status" - if outcome.lower() in PASS_OUTCOMES and any( - scenarios[scenario]["status"] != "pass" for scenario in required_scenarios - ): - return "published runner result declares a passing outcome with non-passing required scenarios" - if not isinstance(native.get("findings"), list): - return "published runner result does not retain a findings list" - if "finding_links" in required_fields and not isinstance(native["finding_links"], dict): - return "published runner result does not retain finding links" - return "" - - -def summarize_executed_distribution_identities(native: dict[str, Any]) -> dict[str, Any]: - raw = native.get("executed_distribution_identities", native.get("executedDistributionIdentities", {})) - if not isinstance(raw, dict): - return {} - identities: dict[str, Any] = {} - for name, identity in raw.items(): - if name not in DISTRIBUTIONS or not isinstance(identity, dict): - continue - kind = identity.get("kind") - locator = identity.get("locator") - artifacts = identity.get("artifacts") - if ( - kind != DISTRIBUTIONS[name][1].distribution - or not isinstance(locator, str) - or not locator - or len(locator) > 256 - or not isinstance(artifacts, list) - ): - continue - normalized_artifacts = [] - for artifact in artifacts: - if ( - not isinstance(artifact, dict) - or set(artifact) != {"name", "sha256"} - or not isinstance(artifact["name"], str) - or not artifact["name"] - or len(artifact["name"]) > 256 - or not isinstance(artifact["sha256"], str) - or not DIGEST_PATTERN.fullmatch(artifact["sha256"]) - ): - normalized_artifacts = [] - break - normalized_artifacts.append( - {"name": sanitized_evidence_text(artifact["name"], 256), "sha256": artifact["sha256"]} - ) - normalized_artifacts.sort(key=lambda artifact: artifact["name"]) - if normalized_artifacts and len(normalized_artifacts) == len( - {artifact["name"] for artifact in normalized_artifacts} - ): - normalized = {"kind": kind, "locator": locator, "artifacts": normalized_artifacts} - if native_distribution_identity_structure_error(name, normalized): - continue - identities[name] = normalized - return identities - - -def summarize_native_result(native: Any) -> dict[str, Any] | None: - if not isinstance(native, dict): - return None - versions = native.get("artifact_versions", native.get("artifactVersions", {})) - if not isinstance(versions, dict): - versions = {} - bounded_versions = { - name: sanitized_evidence_text(version, 128) - for name, version in versions.items() - if name in DISTRIBUTIONS and isinstance(version, str) - } - raw_scenarios = native.get("scenario_results", {}) - scenarios: list[dict[str, str]] = [] - if isinstance(raw_scenarios, dict): - items = raw_scenarios.items() - elif isinstance(raw_scenarios, list): - items = ((str(index), item) for index, item in enumerate(raw_scenarios)) - else: - items = iter(()) - for scenario_id, value in list(items)[:128]: - if isinstance(value, dict): - scenario_id = value.get("scenario_id", value.get("id", scenario_id)) - status = value.get("status", value.get("outcome", "unknown")) - else: - status = value - scenarios.append( - { - "id": sanitized_evidence_text(scenario_id, 128), - "status": sanitized_evidence_text(status, 64), - } - ) - schema = native.get("schema") - source_values = [ - native.get("local_product_source_checkouts_used"), - native.get("local_product_source_checkout_used"), - native.get("local_product_sources_used"), - ] - source_policy = native.get("source_policy") - if isinstance(source_policy, dict): - source_values.append(source_policy.get("local_product_sources_used")) - local_source_used = True if True in source_values else (False if False in source_values else None) - return { - "schema": sanitized_evidence_text(schema, 256) if isinstance(schema, str) else None, - "artifact_versions": bounded_versions, - "executed_distribution_identities": summarize_executed_distribution_identities(native), - "scenario_statuses": scenarios, - "failure_projection": summarize_native_failure_projection(native), - "local_product_source_checkout_used": local_source_used, - } - - -def inject_distribution_identity_mismatch( - native: Any, plan: dict[str, Any], required_distributions: list[str] -) -> tuple[str, str]: - if not isinstance(native, dict): - raise ConformanceError("distribution identity failure injection requires a native result object") - raw = native.get("executed_distribution_identities", native.get("executedDistributionIdentities")) - if not isinstance(raw, dict): - raise ConformanceError("distribution identity failure injection requires executed distribution evidence") - for name in sorted(required_distributions): - identity = raw.get(name) - expected_identity = plan["distribution_identities"][name] - if not isinstance(identity, dict) or not isinstance(identity.get("artifacts"), list): - continue - expected_artifacts = {artifact["name"]: artifact["sha256"] for artifact in expected_identity["artifacts"]} - artifacts = sorted( - (artifact for artifact in identity["artifacts"] if isinstance(artifact, dict)), - key=lambda artifact: str(artifact.get("name", "")), - ) - for artifact in artifacts: - artifact_name = artifact.get("name") - if artifact_name not in expected_artifacts: - continue - expected_digest = expected_artifacts[artifact_name] - artifact["sha256"] = "0" * 64 if expected_digest != "0" * 64 else "f" * 64 - return name, artifact_name - raise ConformanceError("distribution identity failure injection found no required executed artifact identity") - - -def is_classified_transient(text: str) -> bool: - return any(pattern.search(text) for pattern in TRANSIENT_PATTERNS) - - -def classify_attempt( - *, - returncode: int, - timed_out: bool, - native_outcome: str | None, - runner_blocked: bool, - native_result_rejected: bool, - diagnostic_text: str, -) -> tuple[str, bool]: - if native_result_rejected: - return "infrastructure_failure", False - if native_outcome is not None and native_outcome not in PASS_OUTCOMES and not runner_blocked: - return "product_failure", False - if timed_out: - return "product_failure", False - transient = is_classified_transient(diagnostic_text) - if runner_blocked: - return "infrastructure_failure", transient - if returncode == 0 and native_outcome in PASS_OUTCOMES: - return "passed", False - if returncode == 75 or transient: - return "infrastructure_failure", True - return "product_failure", False - - -def execute_command( - command: list[str], - *, - cwd: Path, - environment: dict[str, str], - timeout_seconds: int, - stdout_path: Path, - stderr_path: Path, -) -> tuple[int, bool]: - timed_out = False - with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: - process = subprocess.Popen( - command, - cwd=cwd, - env=environment, - stdout=stdout, - stderr=stderr, - start_new_session=True, - ) - try: - returncode = process.wait(timeout=timeout_seconds) - except subprocess.TimeoutExpired: - timed_out = True - os.killpg(process.pid, signal.SIGTERM) - try: - returncode = process.wait(timeout=10) - except subprocess.TimeoutExpired: - os.killpg(process.pid, signal.SIGKILL) - returncode = process.wait() - return returncode, timed_out - - -def runner_command(path: Path, result_dir: Path) -> list[str]: - interpreters = { - ".sh": "bash", - ".mjs": "node", - ".py": sys.executable, - } - try: - interpreter = interpreters[path.suffix] - except KeyError as error: - raise ConformanceError(f"unsupported conformance runner type: {path.suffix}") from error - return [interpreter, str(path), "--result-dir", str(result_dir)] - - -def artifact_environment(plan: dict[str, Any], scratch: Path) -> dict[str, str]: - versions = {name: identity["version"] for name, identity in plan["artifact_tuple"].items()} - return { - **os.environ, - "DW_CANDIDATE_VERIFICATION_SHA256": plan["candidate"]["verification_sha256"], - "DW_SERVER_IMAGE": plan["server_runner"]["image"], - "DW_SERVER_VERSION": versions["server"], - "DW_CLI_VERSION": versions["cli"], - "DW_PHP_SDK_VERSION": versions["sdk-php"], - "DW_PYTHON_SDK_VERSION": versions["sdk-python"], - "DW_RUST_SDK_VERSION": versions["sdk-rust"], - "DW_WORKFLOW_PHP_VERSION": versions["workflow"], - "DW_WATERLINE_VERSION": versions["waterline"], - "DW_WATERLINE_SERVICE_IMAGE": plan["waterline_service_runner"]["image"], - "DW_CONFORMANCE_TMPDIR": str(scratch), - } - - -def docker_runtime_command( - command: list[str], *, timeout_seconds: int = 180, check: bool = True -) -> subprocess.CompletedProcess[str]: - try: - process = subprocess.run( - command, - check=False, - capture_output=True, - text=True, - timeout=timeout_seconds, - ) - except FileNotFoundError as error: - raise ConformanceError("Docker is required by the declared standalone-server runtime") from error - except subprocess.TimeoutExpired as error: - raise ConformanceError("standalone-server Docker command exceeded its bounded deadline") from error - if check and process.returncode: - detail = bounded_text((process.stderr or process.stdout).strip(), DIAGNOSTIC_LIMIT) - action = command[1] if len(command) > 1 else "command" - raise ConformanceError(f"standalone-server Docker {action} failed: {detail}") - return process - - -def wait_for_server_ready(server_url: str, container_name: str, *, docker: str = "docker") -> None: - deadline = time.monotonic() + 120 - last_error = "server did not answer its readiness endpoint" - while time.monotonic() < deadline: - try: - with urllib.request.urlopen(f"{server_url}/api/ready", timeout=3) as response: - if response.status < 500: - return - last_error = f"readiness endpoint returned HTTP {response.status}" - except Exception as error: # Network error details are retained only after the bounded wait. - last_error = f"{type(error).__name__}: {error}" - time.sleep(2) - logs = docker_runtime_command( - [docker, "logs", "--tail", "80", container_name], - timeout_seconds=30, - check=False, - ) - detail = bounded_text(f"{last_error}\n{logs.stdout}\n{logs.stderr}".strip(), DIAGNOSTIC_LIMIT) - raise ConformanceError(f"exact candidate standalone server did not become ready: {detail}") - - -def require_running_container(container_name: str, *, docker: str = "docker") -> None: - state = docker_runtime_command( - [docker, "inspect", "--format", "{{.State.Running}}", container_name], - timeout_seconds=30, - ) - if state.stdout.strip() == "true": - return - logs = docker_runtime_command( - [docker, "logs", "--tail", "80", container_name], - timeout_seconds=30, - check=False, - ) - detail = bounded_text(f"{logs.stdout}\n{logs.stderr}".strip(), DIAGNOSTIC_LIMIT) - raise ConformanceError(f"exact candidate standalone server process {container_name} exited: {detail}") - - -def wait_for_healthy_container(container_name: str, *, docker: str = "docker") -> None: - deadline = time.monotonic() + 120 - last_status = "starting" - while time.monotonic() < deadline: - state = docker_runtime_command( - [docker, "inspect", "--format", "{{.State.Health.Status}}", container_name], - timeout_seconds=30, - check=False, - ) - if state.returncode == 0: - last_status = state.stdout.strip() - if last_status == "healthy": - return - if last_status == "unhealthy": - break - else: - last_status = bounded_text((state.stderr or state.stdout).strip(), 512) - time.sleep(2) - logs = docker_runtime_command( - [docker, "logs", "--tail", "80", container_name], - timeout_seconds=30, - check=False, - ) - detail = bounded_text(f"health={last_status}\n{logs.stdout}\n{logs.stderr}".strip(), DIAGNOSTIC_LIMIT) - raise ConformanceError(f"standalone-server dependency {container_name} did not become healthy: {detail}") - - -def cleanup_docker_runtime(command: list[str]) -> None: - with contextlib.suppress(ConformanceError): - docker_runtime_command(command, timeout_seconds=30, check=False) - - -@contextlib.contextmanager -def standalone_server_runtime( - plan: dict[str, Any], runner: dict[str, Any], scratch: Path, *, docker: str = "docker" -) -> Iterator[dict[str, str]]: - runtime = runner["runtime"] - identity_seed = f"{scratch.resolve()}:{os.getpid()}:{time.time_ns()}".encode() - suffix = sha256_bytes(identity_seed)[:12] - prefix = f"dw-beta-{runner['id']}-{suffix}" - network = f"{prefix}-network" - container_names = [ - f"{prefix}-bootstrap", - f"{prefix}-http", - f"{prefix}-queue", - f"{prefix}-scheduler", - f"{prefix}-mysql", - f"{prefix}-redis", - ] - bootstrap_name, http_name, queue_name, scheduler_name, mysql_name, redis_name = container_names - image = plan["server_runner"]["image"] - mysql_image = plan["runtime_dependencies"]["mysql"]["image"] - redis_image = plan["runtime_dependencies"]["redis"]["image"] - server_version = plan["artifact_tuple"]["server"]["version"] - manifest_digest = plan["candidate"]["manifest_sha256"] - runtime_token = f"beta-{manifest_digest[:32]}" - runtime_key = ( - "base64:" + base64.b64encode(hashlib.sha256(f"{manifest_digest}:{runner['id']}".encode()).digest()).decode() - ) - labels = ["--label", f"dev.durable-workflow.beta-conformance={manifest_digest}"] - database_name = "durable_workflow" - database_user = "durable_workflow" - database_password = sha256_bytes(f"{manifest_digest}:{runner['id']}:mysql".encode())[:32] - database_root_password = sha256_bytes(f"{manifest_digest}:{runner['id']}:mysql-root".encode())[:32] - shared_environment = [ - "-e", - f"APP_VERSION={server_version}", - "-e", - f"DW_SERVER_KEY={runtime_key}", - "-e", - "DW_AUTH_DRIVER=token", - "-e", - f"DW_AUTH_TOKEN={runtime_token}", - "-e", - "DW_WORKER_POLL_TIMEOUT=1", - "-e", - "DW_WORKER_POLL_INTERVAL_MS=100", - "-e", - "DW_QUERY_TASK_TIMEOUT=3", - "-e", - "DB_CONNECTION=mysql", - "-e", - f"DB_HOST={mysql_name}", - "-e", - "DB_PORT=3306", - "-e", - f"DB_DATABASE={database_name}", - "-e", - f"DB_USERNAME={database_user}", - "-e", - f"DB_PASSWORD={database_password}", - "-e", - "QUEUE_CONNECTION=redis", - "-e", - "CACHE_STORE=redis", - "-e", - f"REDIS_HOST={redis_name}", - "-e", - "REDIS_PORT=6379", - ] - - try: - docker_runtime_command([docker, "network", "create", *labels, network]) - docker_runtime_command( - [ - docker, - "run", - "-d", - "--rm", - "--name", - mysql_name, - *labels, - "--network", - network, - "-e", - f"MYSQL_DATABASE={database_name}", - "-e", - f"MYSQL_USER={database_user}", - "-e", - f"MYSQL_PASSWORD={database_password}", - "-e", - f"MYSQL_ROOT_PASSWORD={database_root_password}", - "--health-cmd", - 'mysqladmin ping -h 127.0.0.1 -uroot --password="$MYSQL_ROOT_PASSWORD"', - "--health-interval", - "2s", - "--health-timeout", - "2s", - "--health-retries", - "60", - mysql_image, - ] - ) - docker_runtime_command( - [ - docker, - "run", - "-d", - "--rm", - "--name", - redis_name, - *labels, - "--network", - network, - "--health-cmd", - "redis-cli ping", - "--health-interval", - "2s", - "--health-timeout", - "2s", - "--health-retries", - "60", - redis_image, - ] - ) - wait_for_healthy_container(mysql_name, docker=docker) - wait_for_healthy_container(redis_name, docker=docker) - docker_runtime_command( - [ - docker, - "run", - "--rm", - "--name", - bootstrap_name, - *labels, - "--network", - network, - *shared_environment, - image, - "server-bootstrap", - ] - ) - docker_runtime_command( - [ - docker, - "run", - "-d", - "--rm", - "--name", - http_name, - *labels, - "-p", - "127.0.0.1::8080", - "--network", - network, - *shared_environment, - "-e", - "DW_SERVER_TOPOLOGY_SHAPE=standalone_server", - "-e", - "DW_SERVER_PROCESS_CLASS=server_http_node", - image, - ] - ) - docker_runtime_command( - [ - docker, - "run", - "-d", - "--rm", - "--name", - queue_name, - *labels, - "--network", - network, - *shared_environment, - "-e", - "DW_SERVER_TOPOLOGY_SHAPE=standalone_server", - "-e", - "DW_SERVER_PROCESS_CLASS=worker_node", - image, - "php", - "artisan", - "queue:work", - "--sleep=1", - "--tries=3", - "--max-time=5400", - ] - ) - docker_runtime_command( - [ - docker, - "run", - "-d", - "--rm", - "--init", - "--name", - scheduler_name, - *labels, - "--network", - network, - *shared_environment, - "-e", - "DW_SERVER_TOPOLOGY_SHAPE=standalone_server", - "-e", - "DW_SERVER_PROCESS_CLASS=scheduler_node", - image, - "sh", - "-c", - "while true; do php artisan schedule:evaluate --limit=100 --json; " - "php artisan activity:timeout-enforce --limit=100; sleep 1; done", - ] - ) - port_result = docker_runtime_command([docker, "port", http_name, "8080/tcp"], timeout_seconds=30) - port_match = re.search(r"127\.0\.0\.1:(\d+)\s*$", port_result.stdout) - if port_match is None: - raise ConformanceError("standalone-server Docker runtime did not publish a loopback HTTP port") - server_url = f"http://127.0.0.1:{port_match.group(1)}" - wait_for_server_ready(server_url, http_name, docker=docker) - for container_name in (mysql_name, redis_name, http_name, queue_name, scheduler_name): - require_running_container(container_name, docker=docker) - runner_server_url = f"http://{http_name}:8080" if runner["id"] == "waterline-service" else server_url - environment = { - runtime["server_url_environment"]: runner_server_url, - runtime["namespace_environment"]: "default", - runtime["token_environment"]: runtime_token, - } - if runner["id"] == "waterline-service": - environment["DW_WATERLINE_SERVICE_DOCKER_NETWORK"] = network - yield environment - for container_name in (mysql_name, redis_name, http_name, queue_name, scheduler_name): - require_running_container(container_name, docker=docker) - finally: - for container_name in reversed(container_names): - cleanup_docker_runtime([docker, "rm", "--force", container_name]) - cleanup_docker_runtime([docker, "network", "rm", network]) - - -@contextlib.contextmanager -def runner_runtime_environment(plan: dict[str, Any], runner: dict[str, Any], scratch: Path) -> Iterator[dict[str, str]]: - runtime = runner.get("runtime") - if runtime is None: - yield {} - return - if runtime["kind"] != "standalone-server": - raise ConformanceError(f"unsupported runner runtime: {runtime['kind']}") - with standalone_server_runtime(plan, runner, scratch) as environment: - yield environment - - -def failure_fingerprint( - plan: dict[str, Any], experiment: str, classification: str, owner: str, diagnostics: list[dict[str, Any]] -) -> str | None: - if classification == "passed": - return None - stable = { - "candidate_manifest_sha256": plan["candidate"]["manifest_sha256"], - "candidate_verification_sha256": plan["candidate"]["verification_sha256"], - "contract_sha256": plan["runner"]["contract_sha256"], - "runtime_dependencies": plan["runtime_dependencies"], - "experiment": experiment, - "classification": classification, - "owning_contract": owner, - "findings": [diagnostic.get("findings", []) for diagnostic in diagnostics], - "timed_out": [diagnostic.get("timed_out", False) for diagnostic in diagnostics], - "native_outcomes": [diagnostic.get("native_outcome") for diagnostic in diagnostics], - } - return sha256_bytes(canonical_json(stable)) - - -def injected_failure_result( - plan: dict[str, Any], - experiment: str, - owner: str, - required_clients: list[str], - required_distributions: list[str], - started_at: str, -) -> dict[str, Any]: - raw_stderr = f"deterministic synthetic sanitizer canary: Authorization: Bearer {SYNTHETIC_CREDENTIAL_CANARY}" - diagnostic = { - "runner": "injected-product-failure", - "attempt": 1, - "exit_code": 1, - "timed_out": False, - "native_outcome": "fail", - "runner_blocked": False, - "stdout_tail": "", - "stdout_sha256": sha256_bytes(b""), - "stderr_tail": sanitized_evidence_text(raw_stderr, DIAGNOSTIC_LIMIT), - "stderr_sha256": sha256_bytes(raw_stderr.encode()), - "native_result_size_bytes": None, - "native_result_sha256": None, - "native_result_prefix_sha256": None, - "native_result_prefix_bytes": None, - "native_summary": None, - "findings": [ - { - "type": "injected_product_failure", - "owning_contract": owner, - "summary": "Deterministic product failure injected before experiment execution.", - } - ], - } - return experiment_result( - plan, - experiment, - owner, - required_clients, - required_distributions, - started_at, - "product_failure", - 1, - [diagnostic], - ) - - -def experiment_result( - plan: dict[str, Any], - experiment: str, - owner: str, - required_clients: list[str], - required_distributions: list[str], - started_at: str, - classification: str, - attempts: int, - diagnostics: list[dict[str, Any]], -) -> dict[str, Any]: - result = { - "schema": EXPERIMENT_RESULT_SCHEMA, - "experiment": experiment, - "candidate": plan["candidate"], - "artifact_tuple": plan["artifact_tuple"], - "source_identities": plan["source_identities"], - "distribution_identities": plan["distribution_identities"], - "runtime_dependencies": plan["runtime_dependencies"], - "runner": plan["runner"], - "server_runner": plan["server_runner"], - "waterline_service_runner": plan["waterline_service_runner"], - "owning_contract": owner, - "required_clients": required_clients, - "required_distributions": required_distributions, - "source_policy": { - "product_artifacts": "published_only", - "orchestration_source": "bound_control_plane_and_exact_candidate_images", - "local_product_source_checkout_used": False, - }, - "started_at": started_at, - "finished_at": now(), - "outcome": "pass" if classification == "passed" else "fail", - "classification": classification, - "failure_fingerprint": failure_fingerprint(plan, experiment, classification, owner, diagnostics), - "retry": { - "attempts": attempts, - "maximum_infrastructure_attempts": MAX_INFRASTRUCTURE_ATTEMPTS, - "semantic_failures_retryable": False, - }, - "diagnostics": diagnostics, - } - validate_experiment_result(result, plan) - return result - - -def artifact_binding_failures( - plan: dict[str, Any], required_distributions: list[str], diagnostics: list[dict[str, Any]] -) -> list[str]: - observed_versions: dict[str, set[str]] = {} - observed_identities: dict[str, list[dict[str, Any]]] = {} - failures: list[str] = [] - for diagnostic in diagnostics: - summary = diagnostic.get("native_summary") - if not isinstance(summary, dict): - continue - if summary.get("local_product_source_checkout_used") is True: - failures.append("native evidence reports a local product source checkout") - versions = summary.get("artifact_versions") - if isinstance(versions, dict): - for name, version in versions.items(): - observed_versions.setdefault(name, set()).add(str(version)) - identities = summary.get("executed_distribution_identities") - if isinstance(identities, dict): - for name, identity in identities.items(): - observed_identities.setdefault(name, []).append(identity) - for name, versions in observed_versions.items(): - expected = distribution_version(plan["artifact_tuple"], name) - if any(not registry_versions_equivalent(name, version, expected) for version in versions): - failures.append(f"{name} native evidence reports {sorted(versions)}, expected exact version {expected}") - for name, identities in observed_identities.items(): - expected = plan["distribution_identities"][name] - expected_artifacts = {artifact["name"]: artifact["sha256"] for artifact in expected["artifacts"]} - for identity in identities: - if native_distribution_identity_structure_error(name, identity): - failures.append(f"{name} native evidence has an invalid executed distribution identity") - continue - locator_prefix = f"{identity['kind']}:{DISTRIBUTIONS[name][1].package}@" - observed_version = identity["locator"].removeprefix(locator_prefix) - expected_version = distribution_version(plan["artifact_tuple"], name) - if identity["kind"] != expected["kind"] or not registry_versions_equivalent( - name, - observed_version, - expected_version, - ): - failures.append(f"{name} native evidence reports a different distribution locator") - continue - for artifact in identity["artifacts"]: - expected_sha256 = expected_artifacts.get(artifact["name"]) - if expected_sha256 is None: - failures.append( - f"{name} native evidence reports unknown executed distribution artifact {artifact['name']}" - ) - elif artifact["sha256"] != expected_sha256: - failures.append( - f"{name} executed distribution artifact {artifact['name']} does not match the candidate digest" - ) - for name in artifact_version_components(required_distributions): - if name not in observed_versions: - failures.append(f"native evidence does not report the exact {name} artifact version") - for name in required_distributions: - if name not in observed_identities: - failures.append(f"native evidence does not report the executed {name} distribution identity") - return list(dict.fromkeys(failures)) - - -def run_experiment( - plan: dict[str, Any], - contract: dict[str, Any], - experiment: str, - artifact_root: Path, - result_dir: Path, - *, - inject_product_failure: bool = False, - inject_identity_failure: bool = False, -) -> dict[str, Any]: - validate_plan(plan) - validate_contract(contract) - if sha256_bytes(canonical_json(contract)) != plan["runner"]["contract_sha256"]: - raise ConformanceError("execution contract does not match the runner revision bound into the plan") - if experiment not in EXPERIMENTS: - raise ConformanceError(f"unknown beta conformance experiment: {experiment}") - if inject_product_failure and inject_identity_failure: - raise ConformanceError("product and distribution identity failures cannot both be injected") - specification = contract["experiments"][experiment] - owner = specification["owning_contract"] - started_at = now() - result_dir.mkdir(parents=True, exist_ok=True) - if inject_product_failure: - result = injected_failure_result( - plan, - experiment, - owner, - specification["required_clients"], - specification["required_distributions"], - started_at, - ) - write_json(result_dir / "experiment-result.json", result) - return result - - diagnostics: list[dict[str, Any]] = [] - final_classification = "passed" - maximum_attempts_used = 1 - identity_failure_injected = False - for runner in specification["runners"]: - runner_root = artifact_root if runner["source"] == "server-image" else Path(__file__).resolve().parent.parent - runner_path = runner_root / safe_relative_path(runner["path"]) - if not runner_path.is_file(): - diagnostic = { - "runner": runner["id"], - "attempt": 1, - "exit_code": 127, - "timed_out": False, - "native_outcome": None, - "runner_blocked": False, - "stdout_tail": "", - "stdout_sha256": sha256_bytes(b""), - "stderr_tail": bounded_text(f"published server image is missing {runner['path']}", DIAGNOSTIC_LIMIT), - "stderr_sha256": sha256_bytes(f"published server image is missing {runner['path']}".encode()), - "native_result_size_bytes": None, - "native_result_sha256": None, - "native_result_prefix_sha256": None, - "native_result_prefix_bytes": None, - "native_summary": None, - "findings": [ - { - "type": "published_runner_missing", - "owning_contract": owner, - "summary": bounded_text(f"Published server image is missing {runner['path']}"), - } - ], - } - diagnostics.append(diagnostic) - final_classification = "product_failure" - break - - native_dir = result_dir / "native" / runner["id"] - native_dir.mkdir(parents=True, exist_ok=True) - scratch = result_dir / "scratch" / runner["id"] - scratch.mkdir(parents=True, exist_ok=True) - runner_classification = "passed" - for attempt in range(1, MAX_INFRASTRUCTURE_ATTEMPTS + 1): - maximum_attempts_used = max(maximum_attempts_used, attempt) - stdout_path = result_dir / f"{runner['id']}-attempt-{attempt}.stdout.log" - stderr_path = result_dir / f"{runner['id']}-attempt-{attempt}.stderr.log" - runtime_blocked = False - runtime_error = "" - try: - with runner_runtime_environment(plan, runner, scratch) as runtime_environment: - environment = {**artifact_environment(plan, scratch), **runtime_environment} - returncode, timed_out = execute_command( - runner_command(runner_path, native_dir), - cwd=artifact_root, - environment=environment, - timeout_seconds=specification["timeout_seconds"], - stdout_path=stdout_path, - stderr_path=stderr_path, - ) - except ConformanceError as error: - runtime_blocked = True - runtime_error = sanitized_evidence_text(error, DIAGNOSTIC_LIMIT) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text(runtime_error, encoding="utf-8") - returncode = 1 - timed_out = False - stdout_tail, stdout_digest = tail_and_digest(stdout_path) - stderr_tail, stderr_digest = tail_and_digest(stderr_path) - native_path = native_dir / runner["result"] - native: Any = None - native_size = None - native_digest = None - native_prefix_digest = None - native_prefix_bytes = None - native_result_error = "" - native_result_rejected = False - injected_identity: tuple[str, str] | None = None - if native_path.is_file(): - ( - native, - native_size, - native_digest, - native_prefix_digest, - native_prefix_bytes, - native_result_status, - ) = load_native_result(native_path) - if native_result_status == "oversized": - native_result_error = ( - f"published runner result size {native_size} exceeds the " - f"{NATIVE_RESULT_LIMIT}-byte portable evidence limit" - ) - native_result_rejected = True - elif native_result_status == "invalid_json": - native_result_error = "published runner result is not valid JSON" - native_result_rejected = True - elif native_result_status == "unreadable": - native_result_error = "published runner result could not be read" - if native_result_error: - stderr_tail = sanitized_evidence_text( - f"{stderr_tail}\n{native_result_error}", - DIAGNOSTIC_LIMIT, - ) - else: - native_outcome, _, _ = native_state(native) - native_result_error = native_result_completeness_error( - native, - runner["required_distributions"], - runner, - ) - native_result_rejected = bool(native_result_error) - if native_result_error: - stderr_tail = sanitized_evidence_text( - f"{stderr_tail}\n{native_result_error}", - DIAGNOSTIC_LIMIT, - ) - elif ( - inject_identity_failure - and not identity_failure_injected - and returncode == 0 - and not timed_out - and native_outcome in PASS_OUTCOMES - ): - injected_identity = inject_distribution_identity_mismatch( - native, plan, runner["required_distributions"] - ) - write_json(native_path, native) - identity_failure_injected = True - ( - native, - native_size, - native_digest, - native_prefix_digest, - native_prefix_bytes, - rewritten_native_status, - ) = load_native_result(native_path) - if rewritten_native_status == "oversized": - native_result_error = ( - f"published runner result size {native_size} exceeds the " - f"{NATIVE_RESULT_LIMIT}-byte portable evidence limit" - ) - native_result_rejected = True - elif rewritten_native_status == "invalid_json": - native_result_error = "published runner result is not valid JSON" - native_result_rejected = True - elif rewritten_native_status == "unreadable": - native_result_error = "published runner result could not be read" - if native_result_error: - stderr_tail = sanitized_evidence_text( - f"{stderr_tail}\n{native_result_error}", - DIAGNOSTIC_LIMIT, - ) - else: - if not runtime_blocked: - native_result_error = "published runner did not emit its declared native result" - stderr_tail = sanitized_evidence_text( - f"{stderr_tail}\n{native_result_error}", - DIAGNOSTIC_LIMIT, - ) - native_outcome, runner_blocked, findings = native_state(native) - runner_blocked = runner_blocked or runtime_blocked or bool(native_result_error) - if runtime_blocked: - findings.insert( - 0, - { - "type": "declared_runtime_unavailable", - "owning_contract": owner, - "summary": sanitized_evidence_text( - f"Published runner {runner['id']} could not start its declared runtime: {runtime_error}", - FINDING_TEXT_LIMIT, - ), - }, - ) - findings = findings[:FINDING_LIMIT] - if native_result_error: - findings.insert( - 0, - { - "type": "native_result_unreadable", - "owning_contract": owner, - "summary": sanitized_evidence_text( - f"Published runner {runner['id']} emitted evidence the portable wrapper could not read: " - f"{native_result_error}.", - FINDING_TEXT_LIMIT, - ), - }, - ) - findings = findings[:FINDING_LIMIT] - if injected_identity is not None: - component, artifact_name = injected_identity - findings.insert( - 0, - { - "type": "injected_distribution_identity_mismatch", - "owning_contract": owner, - "summary": sanitized_evidence_text( - f"Injected a same-version digest mismatch for {component} artifact {artifact_name}.", - FINDING_TEXT_LIMIT, - ), - }, - ) - findings = findings[:FINDING_LIMIT] - classification, retryable = classify_attempt( - returncode=returncode, - timed_out=timed_out, - native_outcome=native_outcome, - runner_blocked=runner_blocked, - native_result_rejected=native_result_rejected, - diagnostic_text=f"{stdout_tail}\n{stderr_tail}", - ) - if classification != "passed" and not findings: - findings = [ - { - "type": "experiment_execution_failure", - "owning_contract": owner, - "summary": sanitized_evidence_text( - "Experiment timed out." - if timed_out - else f"Published runner {runner['id']} exited with status {returncode}.", - FINDING_TEXT_LIMIT, - ), - } - ] - diagnostics.append( - { - "runner": runner["id"], - "attempt": attempt, - "exit_code": returncode, - "timed_out": timed_out, - "native_outcome": native_outcome, - "runner_blocked": runner_blocked, - "stdout_tail": stdout_tail, - "stdout_sha256": stdout_digest, - "stderr_tail": stderr_tail, - "stderr_sha256": stderr_digest, - "native_result_size_bytes": native_size, - "native_result_sha256": native_digest, - "native_result_prefix_sha256": native_prefix_digest, - "native_result_prefix_bytes": native_prefix_bytes, - "native_summary": summarize_native_result(native), - "findings": findings, - } - ) - runner_classification = classification - if classification == "passed": - break - if not retryable or attempt == MAX_INFRASTRUCTURE_ATTEMPTS: - break - time.sleep(attempt) - final_classification = runner_classification - if final_classification != "passed": - break - - if final_classification == "passed": - binding_failures = artifact_binding_failures(plan, specification["required_distributions"], diagnostics) - if binding_failures: - message = "\n".join(binding_failures) - diagnostics.append( - { - "runner": "artifact-binding", - "attempt": 1, - "exit_code": 1, - "timed_out": False, - "native_outcome": "fail", - "runner_blocked": False, - "stdout_tail": "", - "stdout_sha256": sha256_bytes(b""), - "stderr_tail": sanitized_evidence_text(message, DIAGNOSTIC_LIMIT), - "stderr_sha256": sha256_bytes(message.encode()), - "native_result_size_bytes": None, - "native_result_sha256": None, - "native_result_prefix_sha256": None, - "native_result_prefix_bytes": None, - "native_summary": None, - "findings": [ - { - "type": "exact_artifact_binding_failure", - "owning_contract": owner, - "summary": sanitized_evidence_text(failure, FINDING_TEXT_LIMIT), - } - for failure in binding_failures[:FINDING_LIMIT] - ], - } - ) - final_classification = "product_failure" - - result = experiment_result( - plan, - experiment, - owner, - specification["required_clients"], - specification["required_distributions"], - started_at, - final_classification, - maximum_attempts_used, - diagnostics, - ) - write_json(result_dir / "experiment-result.json", result) - return result - - -def retained_attempt_classification(diagnostic: dict[str, Any]) -> tuple[str, bool]: - native_size = diagnostic["native_result_size_bytes"] - native_digest = diagnostic["native_result_sha256"] - native_summary = diagnostic["native_summary"] - native_result_rejected = (isinstance(native_size, int) and native_size > NATIVE_RESULT_LIMIT) or ( - native_digest is not None and native_summary is None - ) - return classify_attempt( - returncode=diagnostic["exit_code"], - timed_out=diagnostic["timed_out"], - native_outcome=diagnostic["native_outcome"], - runner_blocked=diagnostic["runner_blocked"], - native_result_rejected=native_result_rejected, - diagnostic_text=f"{diagnostic['stdout_tail']}\n{diagnostic['stderr_tail']}", - ) - - -def validate_retained_runner_summary( - diagnostic: dict[str, Any], - runner: dict[str, Any], - plan: dict[str, Any], - *, - terminal_pass: bool, - require_contract_summary: bool, - require_binding: bool, -) -> None: - summary = diagnostic["native_summary"] - if summary is None: - if terminal_pass: - raise ConformanceError(f"passing runner {runner['id']} does not retain a native summary") - return - required_distributions = set(runner["required_distributions"]) - required_artifact_versions = set(runner_required_artifact_versions(runner)) - reported_versions = set(summary["artifact_versions"]) - if reported_versions - required_artifact_versions: - raise ConformanceError(f"runner {runner['id']} retains artifact versions outside its exact assignment") - reported_identities = set(summary["executed_distribution_identities"]) - if reported_identities - required_distributions: - raise ConformanceError(f"runner {runner['id']} retains distribution identities outside its exact assignment") - if require_contract_summary and ( - reported_versions != required_artifact_versions or reported_identities != required_distributions - ): - raise ConformanceError(f"runner {runner['id']} does not retain its exact distribution assignment") - if not require_contract_summary: - return - result_schema = runner.get("result_schema") - if result_schema is not None and summary["schema"] != result_schema: - raise ConformanceError(f"runner {runner['id']} does not retain its declared schema") - required_scenarios = runner.get("required_scenarios", []) - if required_scenarios: - scenario_ids = [cell["id"] for cell in summary["scenario_statuses"]] - if len(scenario_ids) != len(set(scenario_ids)) or set(scenario_ids) != set(required_scenarios): - raise ConformanceError(f"runner {runner['id']} does not retain exactly its declared scenario cells") - if terminal_pass and any(cell["status"] != "pass" for cell in summary["scenario_statuses"]): - raise ConformanceError(f"passing runner {runner['id']} retains a non-passing declared scenario cell") - if require_binding and artifact_binding_failures(plan, runner["required_distributions"], [diagnostic]): - raise ConformanceError(f"passing runner {runner['id']} has incomplete or mismatched native artifact evidence") - - -def validate_retained_attempt_lifecycle(result: dict[str, Any], plan: dict[str, Any], contract: dict[str, Any]) -> None: - experiment = result["experiment"] - specification = contract["experiments"][experiment] - if result["owning_contract"] != specification["owning_contract"]: - raise ConformanceError(f"experiment result {experiment} names a different owning contract") - if result["required_clients"] != specification["required_clients"]: - raise ConformanceError(f"experiment result {experiment} names different required clients") - if result["required_distributions"] != specification["required_distributions"]: - raise ConformanceError(f"experiment result {experiment} names different required distributions") - - diagnostics = result["diagnostics"] - if diagnostics[0]["runner"] == "injected-product-failure": - if ( - len(diagnostics) != 1 - or result["classification"] != "product_failure" - or result["retry"]["attempts"] != 1 - or diagnostics[0]["native_summary"] is not None - ): - raise ConformanceError("injected product-failure evidence has an invalid lifecycle") - return - if any(diagnostic["runner"] == "injected-product-failure" for diagnostic in diagnostics): - raise ConformanceError("injected product-failure evidence must be the only diagnostic") - - binding_positions = [ - index for index, diagnostic in enumerate(diagnostics) if diagnostic["runner"] == "artifact-binding" - ] - if len(binding_positions) > 1 or (binding_positions and binding_positions[0] != len(diagnostics) - 1): - raise ConformanceError("artifact-binding evidence has a duplicate or non-terminal lifecycle") - binding_diagnostic = diagnostics[-1] if binding_positions else None - runner_diagnostics = diagnostics[:-1] if binding_diagnostic is not None else diagnostics - if binding_diagnostic is not None: - binding_classification, _ = retained_attempt_classification(binding_diagnostic) - if ( - result["classification"] != "product_failure" - or binding_classification != "product_failure" - or binding_diagnostic["attempt"] != 1 - or binding_diagnostic["native_summary"] is not None - ): - raise ConformanceError("artifact-binding evidence has an invalid terminal lifecycle") - - expected_runners = specification["runners"] - expected_ids = {runner["id"] for runner in expected_runners} - cursor = 0 - completed_runners = 0 - maximum_attempt = 1 - terminal_failure: str | None = None - for runner in expected_runners: - attempts: list[dict[str, Any]] = [] - while cursor < len(runner_diagnostics) and runner_diagnostics[cursor]["runner"] == runner["id"]: - attempts.append(runner_diagnostics[cursor]) - cursor += 1 - if not attempts: - break - classified = [retained_attempt_classification(diagnostic) for diagnostic in attempts] - passing_attempts = [index for index, (classification, _) in enumerate(classified) if classification == "passed"] - if len(passing_attempts) > 1 or (passing_attempts and passing_attempts[0] != len(attempts) - 1): - raise ConformanceError(f"runner {runner['id']} retains duplicate passing terminal attempts") - observed_attempts = [diagnostic["attempt"] for diagnostic in attempts] - if observed_attempts != list(range(1, len(attempts) + 1)): - raise ConformanceError(f"runner {runner['id']} does not retain a bounded, ordered attempt lifecycle") - maximum_attempt = max(maximum_attempt, observed_attempts[-1]) - for diagnostic, (classification, _retryable) in zip(attempts, classified, strict=True): - validate_retained_runner_summary( - diagnostic, - runner, - plan, - terminal_pass=classification == "passed", - require_contract_summary=classification in {"passed", "product_failure"}, - require_binding=result["classification"] == "passed", - ) - for classification, retryable in classified[:-1]: - if classification != "infrastructure_failure" or not retryable: - raise ConformanceError( - f"runner {runner['id']} retains a non-transient attempt before its terminal attempt" - ) - terminal_classification, _ = classified[-1] - if terminal_classification == "passed": - completed_runners += 1 - continue - terminal_failure = terminal_classification - break - - if cursor < len(runner_diagnostics): - runner_id = runner_diagnostics[cursor]["runner"] - if runner_id in expected_ids: - raise ConformanceError(f"experiment {experiment} retains a duplicate or out-of-order runner {runner_id}") - raise ConformanceError(f"experiment {experiment} retains unknown runner {runner_id}") - if result["retry"]["attempts"] != maximum_attempt: - raise ConformanceError("experiment result retry count disagrees with its runner attempts") - if binding_diagnostic is not None: - if completed_runners != len(expected_runners) or terminal_failure is not None: - raise ConformanceError("artifact-binding evidence requires every declared runner to pass") - return - if result["classification"] == "passed": - if completed_runners != len(expected_runners) or terminal_failure is not None: - raise ConformanceError( - f"passing experiment {experiment} does not end in one terminal pass for every declared runner" - ) - return - if terminal_failure is None or terminal_failure != result["classification"]: - raise ConformanceError("failed experiment result disagrees with its terminal runner attempt") - - -def validate_experiment_result(result: Any, plan: dict[str, Any], contract: dict[str, Any] | None = None) -> None: - _validate_experiment_result( - result, - plan, - contract, - schema=EXPERIMENT_RESULT_SCHEMA, - allowed_distributions=set(DISTRIBUTIONS), - require_waterline_service=True, - ) - - -def validate_recorded_experiment_result(result: Any, plan: dict[str, Any]) -> None: - schema = result.get("schema") if isinstance(result, dict) else None - if schema == EXPERIMENT_RESULT_SCHEMA: - if plan.get("schema") != PLAN_SCHEMA: - raise ConformanceError("recorded experiment result does not match its plan schema") - validate_experiment_result(result, plan) - return - if schema == LEGACY_EXPERIMENT_RESULT_SCHEMA: - if plan.get("schema") != LEGACY_PLAN_SCHEMA: - raise ConformanceError("recorded experiment result does not match its plan schema") - _validate_experiment_result( - result, - plan, - None, - schema=LEGACY_EXPERIMENT_RESULT_SCHEMA, - allowed_distributions=set(COMPONENTS), - require_waterline_service=False, - ) - return - raise ConformanceError("recorded experiment result uses an unsupported schema") - - -def _validate_experiment_result( - result: Any, - plan: dict[str, Any], - contract: dict[str, Any] | None, - *, - schema: str, - allowed_distributions: set[str], - require_waterline_service: bool, -) -> None: - required = { - "schema", - "experiment", - "candidate", - "artifact_tuple", - "source_identities", - "distribution_identities", - "runtime_dependencies", - "runner", - "server_runner", - "owning_contract", - "required_clients", - "required_distributions", - "source_policy", - "started_at", - "finished_at", - "outcome", - "classification", - "failure_fingerprint", - "retry", - "diagnostics", - } - if require_waterline_service: - required.add("waterline_service_runner") - if not isinstance(result, dict) or set(result) != required or result.get("schema") != schema: - raise ConformanceError("experiment result has an invalid top-level shape") - if result["experiment"] not in EXPERIMENTS: - raise ConformanceError("experiment result has an unknown experiment") - binding_fields = [ - "candidate", - "artifact_tuple", - "source_identities", - "distribution_identities", - "runtime_dependencies", - "runner", - "server_runner", - ] - if require_waterline_service: - binding_fields.append("waterline_service_runner") - for field in binding_fields: - if result[field] != plan[field]: - raise ConformanceError(f"experiment result {result['experiment']} has a mismatched {field} binding") - classification = result["classification"] - clients = result["required_clients"] - if ( - not isinstance(clients, list) - or not clients - or len(clients) != len(set(clients)) - or not set(clients).issubset({"sdk-php", "sdk-python", "sdk-rust"}) - ): - raise ConformanceError("experiment result has invalid required clients") - required_distributions = result["required_distributions"] - if ( - not isinstance(required_distributions, list) - or not required_distributions - or len(required_distributions) != len(set(required_distributions)) - or not set(required_distributions).issubset(allowed_distributions) - or not {"server", *clients}.issubset(required_distributions) - ): - raise ConformanceError("experiment result has invalid required distributions") - orchestration_source = ( - "bound_control_plane_and_exact_candidate_images" - if require_waterline_service - else "exact_server_container" - ) - if result["source_policy"] != { - "product_artifacts": "published_only", - "orchestration_source": orchestration_source, - "local_product_source_checkout_used": False, - }: - raise ConformanceError("experiment result does not prove the published-only source policy") - if classification not in {"passed", "product_failure", "infrastructure_failure"}: - raise ConformanceError("experiment result has an invalid classification") - if result["outcome"] != ("pass" if classification == "passed" else "fail"): - raise ConformanceError("experiment result outcome disagrees with its classification") - fingerprint = result["failure_fingerprint"] - if (classification == "passed" and fingerprint is not None) or ( - classification != "passed" and (not isinstance(fingerprint, str) or not DIGEST_PATTERN.fullmatch(fingerprint)) - ): - raise ConformanceError("experiment result has an invalid failure fingerprint") - retry = result["retry"] - if ( - not isinstance(retry, dict) - or set(retry) != {"attempts", "maximum_infrastructure_attempts", "semantic_failures_retryable"} - or not isinstance(retry["attempts"], int) - or not 1 <= retry["attempts"] <= MAX_INFRASTRUCTURE_ATTEMPTS - or retry["maximum_infrastructure_attempts"] != MAX_INFRASTRUCTURE_ATTEMPTS - or retry["semantic_failures_retryable"] is not False - ): - raise ConformanceError("experiment result has an invalid retry record") - diagnostics = result["diagnostics"] - if not isinstance(diagnostics, list) or not 1 <= len(diagnostics) <= 7: - raise ConformanceError("experiment result diagnostics must contain one to seven bounded entries") - for diagnostic in diagnostics: - if not isinstance(diagnostic, dict): - raise ConformanceError("experiment result diagnostic must be an object") - if ( - not isinstance(diagnostic.get("runner"), str) - or not diagnostic["runner"] - or len(diagnostic["runner"]) > 63 - or type(diagnostic.get("attempt")) is not int - or not 1 <= diagnostic["attempt"] <= MAX_INFRASTRUCTURE_ATTEMPTS - or type(diagnostic.get("exit_code")) is not int - or not isinstance(diagnostic.get("timed_out"), bool) - or ( - diagnostic.get("native_outcome") is not None - and (not isinstance(diagnostic["native_outcome"], str) or len(diagnostic["native_outcome"]) > 128) - ) - or not isinstance(diagnostic.get("runner_blocked"), bool) - ): - raise ConformanceError("experiment result diagnostic has an invalid attempt shape") - native_identity_fields = { - "native_result_size_bytes", - "native_result_sha256", - "native_result_prefix_sha256", - "native_result_prefix_bytes", - } - if not native_identity_fields.issubset(diagnostic): - raise ConformanceError("experiment result diagnostic must retain the native result identity shape") - if ( - len(str(diagnostic.get("stdout_tail", ""))) > DIAGNOSTIC_LIMIT - or len(str(diagnostic.get("stderr_tail", ""))) > DIAGNOSTIC_LIMIT - ): - raise ConformanceError("experiment result contains unbounded diagnostic output") - findings = diagnostic.get("findings") - if not isinstance(findings, list) or len(findings) > FINDING_LIMIT: - raise ConformanceError("experiment result contains unbounded findings") - native_size = diagnostic.get("native_result_size_bytes") - native_digest = diagnostic.get("native_result_sha256") - native_prefix_digest = diagnostic.get("native_result_prefix_sha256") - native_prefix_bytes = diagnostic.get("native_result_prefix_bytes") - native_summary = diagnostic.get("native_summary") - if native_size is not None and (type(native_size) is not int or native_size < 0): - raise ConformanceError("experiment result has an invalid native result size") - if native_digest is not None and ( - not isinstance(native_digest, str) or not DIGEST_PATTERN.fullmatch(native_digest) - ): - raise ConformanceError("experiment result has an invalid complete native result identity") - if (native_prefix_digest is None) != (native_prefix_bytes is None) or ( - native_prefix_digest is not None - and ( - not isinstance(native_prefix_digest, str) - or not DIGEST_PATTERN.fullmatch(native_prefix_digest) - or type(native_prefix_bytes) is not int - or not 1 <= native_prefix_bytes <= NATIVE_RESULT_PREFIX_LIMIT - ) - ): - raise ConformanceError("experiment result has an invalid bounded native result identity") - if native_digest is not None and ( - native_size is None - or native_size > NATIVE_RESULT_LIMIT - or native_prefix_digest is not None - or native_prefix_bytes is not None - ): - raise ConformanceError("complete native identities are only valid for bounded evidence") - if native_prefix_digest is not None and ( - native_size is None or native_size <= NATIVE_RESULT_LIMIT or native_digest is not None - ): - raise ConformanceError("bounded native identities are only valid for oversized evidence") - known_size_without_identity = ( - native_size is not None - and native_digest is None - and native_prefix_digest is None - and native_prefix_bytes is None - ) - if ( - native_size is not None - and native_size > NATIVE_RESULT_LIMIT - and (native_digest is not None or (native_prefix_digest is None and not known_size_without_identity)) - ): - raise ConformanceError("oversized native evidence must retain only a bounded identity") - if native_summary is not None and ( - native_size is None - or native_size > NATIVE_RESULT_LIMIT - or native_digest is None - or native_prefix_digest is not None - or native_prefix_bytes is not None - ): - raise ConformanceError("parsed native results must retain their complete bounded identity") - if known_size_without_identity and diagnostic.get("runner_blocked") is not True: - raise ConformanceError( - "native result sizes without an identity are only valid for unreadable infrastructure evidence" - ) - if native_summary is not None: - if not isinstance(native_summary, dict) or set(native_summary) != { - "schema", - "artifact_versions", - "executed_distribution_identities", - "scenario_statuses", - "failure_projection", - "local_product_source_checkout_used", - }: - raise ConformanceError("experiment result has an invalid native summary") - if ( - len(native_summary["artifact_versions"]) > len(allowed_distributions) - or len(native_summary["executed_distribution_identities"]) > len(allowed_distributions) - or len(native_summary["scenario_statuses"]) > 128 - ): - raise ConformanceError("experiment result has an unbounded native summary") - summary_versions = native_summary["artifact_versions"] - if ( - not isinstance(summary_versions, dict) - or not set(summary_versions).issubset(allowed_distributions) - or any(not isinstance(version, str) or len(version) > 128 for version in summary_versions.values()) - ): - raise ConformanceError("experiment result has invalid native artifact versions") - summary_identities = native_summary["executed_distribution_identities"] - if not isinstance(summary_identities, dict) or not set(summary_identities).issubset( - allowed_distributions - ): - raise ConformanceError("experiment result retains an unknown native distribution identity") - for name, identity in summary_identities.items(): - identity_error = native_distribution_identity_structure_error(name, identity) - if identity_error: - raise ConformanceError(identity_error) - scenario_statuses = native_summary["scenario_statuses"] - if not isinstance(scenario_statuses, list) or any( - not isinstance(cell, dict) - or set(cell) != {"id", "status"} - or not isinstance(cell["id"], str) - or not cell["id"] - or len(cell["id"]) > 128 - or not isinstance(cell["status"], str) - or len(cell["status"]) > 64 - for cell in scenario_statuses - ): - raise ConformanceError("experiment result has invalid native scenario statuses") - if any( - contains_sensitive_evidence_text(cell["id"]) or contains_sensitive_evidence_text(cell["status"]) - for cell in scenario_statuses - ): - raise ConformanceError("experiment result has unsanitized native scenario statuses") - projection_error = native_failure_projection_error(native_summary["failure_projection"]) - if projection_error: - raise ConformanceError(projection_error) - validate_public_evidence_strings(result) - if classification == "passed" and artifact_binding_failures(plan, required_distributions, diagnostics): - raise ConformanceError("passing experiment result has incomplete or mismatched native artifact evidence") - if contract is not None: - validate_contract(contract) - if sha256_bytes(canonical_json(contract)) != plan["runner"]["contract_sha256"]: - raise ConformanceError("retained experiment contract does not match the plan binding") - validate_retained_attempt_lifecycle(result, plan, contract) - - -def missing_experiment_summary( - plan: dict[str, Any], - experiment: str, - owner: str, - required_clients: list[str], - required_distributions: list[str], -) -> dict[str, Any]: - fingerprint = sha256_bytes( - canonical_json( - { - "candidate_manifest_sha256": plan["candidate"]["manifest_sha256"], - "candidate_verification_sha256": plan["candidate"]["verification_sha256"], - "contract_sha256": plan["runner"]["contract_sha256"], - "runtime_dependencies": plan["runtime_dependencies"], - "experiment": experiment, - "classification": "infrastructure_failure", - "reason": "experiment result was not retained", - } - ) - ) - return { - "outcome": "fail", - "classification": "infrastructure_failure", - "owning_contract": owner, - "required_clients": required_clients, - "required_distributions": required_distributions, - "result_sha256": None, - "failure_fingerprint": fingerprint, - } - - -def aggregate_results( - plan: dict[str, Any], - contract: dict[str, Any], - result_root: Path, - *, - run_id: int, - run_attempt: int, - source_candidate: str, - source_head_sha: str, - generated_at: str | None = None, -) -> tuple[dict[str, Any], dict[str, Path]]: - validate_plan(plan) - validate_contract(contract) - if plan["candidate"]["name"] != source_candidate: - raise ConformanceError("execution plan does not bind the source workflow candidate") - if plan["runner"]["revision"] != source_head_sha: - raise ConformanceError("execution plan does not bind the source workflow commit") - if run_id < 1 or run_attempt < 1: - raise ConformanceError("GitHub run identity must be positive") - discovered: dict[str, tuple[dict[str, Any], Path]] = {} - for path in result_root.rglob("experiment-result.json"): - result = load_json(path) - validate_experiment_result(result, plan, contract) - experiment = result["experiment"] - if experiment in discovered: - raise ConformanceError(f"multiple retained results exist for experiment {experiment}") - discovered[experiment] = (result, path) - summaries: dict[str, Any] = {} - retained_paths: dict[str, Path] = {} - executed_distribution_identities: dict[str, dict[str, Any]] = {} - for experiment in EXPERIMENTS: - if experiment not in discovered: - summaries[experiment] = missing_experiment_summary( - plan, - experiment, - contract["experiments"][experiment]["owning_contract"], - contract["experiments"][experiment]["required_clients"], - contract["experiments"][experiment]["required_distributions"], - ) - continue - result, path = discovered[experiment] - for diagnostic in result["diagnostics"]: - native_summary = diagnostic.get("native_summary") - if not isinstance(native_summary, dict): - continue - for name, identity in native_summary["executed_distribution_identities"].items(): - merged = executed_distribution_identities.setdefault( - name, - {"kind": identity["kind"], "locator": identity["locator"], "artifacts": []}, - ) - artifacts = { - artifact["name"]: artifact["sha256"] for artifact in [*merged["artifacts"], *identity["artifacts"]] - } - merged["artifacts"] = [ - {"name": artifact_name, "sha256": artifacts[artifact_name]} for artifact_name in sorted(artifacts) - ] - summaries[experiment] = { - "outcome": result["outcome"], - "classification": result["classification"], - "owning_contract": result["owning_contract"], - "required_clients": result["required_clients"], - "required_distributions": result["required_distributions"], - "result_sha256": sha256_file(path), - "failure_fingerprint": result["failure_fingerprint"], - } - retained_paths[experiment] = path - outcome = "pass" if all(item["outcome"] == "pass" for item in summaries.values()) else "fail" - if outcome == "pass" and set(executed_distribution_identities) != set(DISTRIBUTIONS): - raise ConformanceError("passing suite does not retain every required executed distribution identity") - evidence_tag = f"beta-conformance/{plan['candidate']['name']}/{run_id}.{run_attempt}" - suite = { - "schema": SUITE_RESULT_SCHEMA, - "candidate": plan["candidate"], - "artifact_tuple": plan["artifact_tuple"], - "source_identities": plan["source_identities"], - "distribution_identities": plan["distribution_identities"], - "executed_distribution_identities": executed_distribution_identities, - "runtime_dependencies": plan["runtime_dependencies"], - "runner": plan["runner"], - "server_runner": plan["server_runner"], - "waterline_service_runner": plan["waterline_service_runner"], - "source_policy": { - "product_artifacts": "published_only", - "orchestration_source": "bound_control_plane_and_exact_candidate_images", - "local_product_source_checkout_used": False, - }, - "github_run": { - "repository": "durable-workflow/.github", - "run_id": run_id, - "run_attempt": run_attempt, - "evidence_tag": evidence_tag, - }, - "generated_at": github_timestamp(generated_at, "suite generation") if generated_at is not None else now(), - "outcome": outcome, - "experiments": summaries, - } - return suite, retained_paths - - -def write_github_output(path: Path | None, values: dict[str, str]) -> None: - if path is None: - return - with path.open("a", encoding="utf-8") as handle: - for key, value in values.items(): - if "\n" in value or "\r" in value: - raise ConformanceError(f"GitHub output {key} must be a single line") - handle.write(f"{key}={value}\n") - - -def parser() -> argparse.ArgumentParser: - arguments = argparse.ArgumentParser(description=__doc__) - commands = arguments.add_subparsers(dest="command", required=True) - - validate = commands.add_parser("validate", help="validate the portable contract and JSON schemas") - validate.add_argument("contract", type=Path) - validate.add_argument("schemas", nargs="*", type=Path) - - prepare = commands.add_parser("prepare", help="bind an immutable candidate to this runner revision") - prepare.add_argument("manifest", type=Path) - prepare.add_argument("output", type=Path) - prepare.add_argument("--contract", type=Path, required=True) - prepare.add_argument("--repository", type=Path, default=Path(".")) - prepare.add_argument("--runner-revision", required=True) - prepare.add_argument("--docker", default="docker") - prepare.add_argument("--github-output", type=Path) - - restore = commands.add_parser("restore-plan", help="validate and reuse a retained first-attempt plan") - restore.add_argument("plan", type=Path) - restore.add_argument("manifest", type=Path) - restore.add_argument("--contract", type=Path, required=True) - restore.add_argument("--runner-revision", required=True) - restore.add_argument("--github-output", type=Path) - - extract = commands.add_parser("extract", help="extract conformance orchestration from the exact server image") - extract.add_argument("plan", type=Path) - extract.add_argument("output", type=Path) - extract.add_argument("extraction_record", type=Path) - extract.add_argument("--docker", default="docker") - - run = commands.add_parser("run", help="run one isolated experiment") - run.add_argument("plan", type=Path) - run.add_argument("experiment", choices=EXPERIMENTS) - run.add_argument("artifact_root", type=Path) - run.add_argument("result_dir", type=Path) - run.add_argument("--contract", type=Path, required=True) - run.add_argument("--inject-product-failure", action="store_true") - run.add_argument("--inject-identity-failure", action="store_true") - - aggregate = commands.add_parser("aggregate", help="aggregate retained matrix evidence") - aggregate.add_argument("plan", type=Path) - aggregate.add_argument("result_root", type=Path) - aggregate.add_argument("output", type=Path) - aggregate.add_argument("asset_dir", type=Path) - aggregate.add_argument("--contract", type=Path, required=True) - aggregate.add_argument("--run-id", type=int, required=True) - aggregate.add_argument("--run-attempt", type=int, required=True) - aggregate.add_argument("--source-candidate", required=True) - aggregate.add_argument("--source-head-sha", required=True) - aggregate.add_argument("--generated-at") - aggregate.add_argument("--github-output", type=Path) - - retention = commands.add_parser("retention-source", help="validate a completed conformance run") - retention.add_argument("--expected-run-id", type=int, required=True) - retention.add_argument("--expected-run-attempt", type=int, required=True) - retention.add_argument("--github-output", type=Path) - - retention_ref = commands.add_parser("retention-ref", help="validate the durable conformance evidence ref") - retention_ref.add_argument("ref", type=Path) - retention_ref.add_argument("comparison", type=Path) - retention_ref.add_argument("--expected-tag", required=True) - retention_ref.add_argument("--source-sha", required=True) - retention_ref.add_argument("--controller-sha", required=True) - return arguments - - -def main(argv: list[str] | None = None) -> int: - arguments = parser().parse_args(argv) - try: - if arguments.command == "validate": - load_contract(arguments.contract) - for schema in arguments.schemas: - value = load_json(schema, limit=512 * 1024) - if ( - not isinstance(value, dict) - or value.get("$schema") != "https://json-schema.org/draft/2020-12/schema" - ): - raise ConformanceError(f"schema is not JSON Schema draft 2020-12: {schema}") - return 0 - if arguments.command == "prepare": - manifest = load_manifest(arguments.manifest) - contract = load_contract(arguments.contract) - runtime_dependencies = resolve_runtime_dependencies(contract, docker=arguments.docker) - plan = prepare_plan( - arguments.repository, - manifest, - contract, - arguments.runner_revision, - runtime_dependencies, - ) - write_json(arguments.output, plan) - write_github_output(arguments.github_output, plan_github_outputs(plan)) - return 0 - if arguments.command == "restore-plan": - plan = restore_plan( - load_plan(arguments.plan), - load_manifest(arguments.manifest), - load_contract(arguments.contract), - arguments.runner_revision, - ) - write_github_output(arguments.github_output, plan_github_outputs(plan)) - return 0 - if arguments.command == "extract": - extract_runner(load_plan(arguments.plan), arguments.output, arguments.extraction_record, arguments.docker) - return 0 - if arguments.command == "run": - plan = load_plan(arguments.plan) - result = run_experiment( - plan, - load_contract(arguments.contract), - arguments.experiment, - arguments.artifact_root, - arguments.result_dir, - inject_product_failure=arguments.inject_product_failure, - inject_identity_failure=arguments.inject_identity_failure, - ) - print(json.dumps({"experiment": arguments.experiment, "outcome": result["outcome"]}, sort_keys=True)) - return 0 if result["outcome"] == "pass" else 1 - if arguments.command == "aggregate": - plan = load_plan(arguments.plan) - suite, retained = aggregate_results( - plan, - load_contract(arguments.contract), - arguments.result_root, - run_id=arguments.run_id, - run_attempt=arguments.run_attempt, - source_candidate=arguments.source_candidate, - source_head_sha=arguments.source_head_sha, - generated_at=arguments.generated_at, - ) - write_json(arguments.output, suite) - arguments.asset_dir.mkdir(parents=True, exist_ok=True) - shutil.copyfile(arguments.output, arguments.asset_dir / "suite-result.json") - for experiment, path in retained.items(): - shutil.copyfile(path, arguments.asset_dir / f"{experiment}.json") - write_github_output( - arguments.github_output, - { - "candidate": plan["candidate"]["name"], - "evidence_tag": suite["github_run"]["evidence_tag"], - "outcome": suite["outcome"], - }, - ) - print(json.dumps({"evidence_tag": suite["github_run"]["evidence_tag"], "outcome": suite["outcome"]})) - return 0 - if arguments.command == "retention-source": - run, workflow = fetch_retention_source_metadata( - arguments.expected_run_id, - arguments.expected_run_attempt, - os.environ.get("GH_TOKEN", ""), - ) - source = validate_retention_source( - run, - workflow, - expected_run_id=arguments.expected_run_id, - expected_run_attempt=arguments.expected_run_attempt, - ) - write_github_output( - arguments.github_output, - {name: str(value) for name, value in source.items()}, - ) - print(json.dumps(source, sort_keys=True)) - return 0 - if arguments.command == "retention-ref": - ref = validate_retention_ref( - load_json(arguments.ref), - load_json(arguments.comparison), - expected_tag=arguments.expected_tag, - source_sha=arguments.source_sha, - controller_sha=arguments.controller_sha, - ) - print(json.dumps(ref, sort_keys=True)) - return 0 - except (CandidateError, ConformanceError, OSError) as error: - print(f"beta conformance error: {error}", file=sys.stderr) - return 2 - raise AssertionError("unreachable") - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/beta_continuity.py b/scripts/beta_continuity.py deleted file mode 100644 index 925d39f..0000000 --- a/scripts/beta_continuity.py +++ /dev/null @@ -1,3002 +0,0 @@ -#!/usr/bin/env python3 -"""Drive the workspace-unavailable beta continuity drill from GitHub authority.""" - -from __future__ import annotations - -import argparse -import datetime as dt -import hashlib -import json -import os -import re -import subprocess -import sys -import tempfile -import tomllib -import urllib.error -import urllib.parse -import urllib.request -from pathlib import Path -from typing import Any - -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.beta_candidate import ( - COMPONENTS, - VERSION_PATTERN, - CandidateError, - PublicClient, - canonical_json, - fetch_existing_record, - manifest_digest, - read_record_file, - resolve_github_tag, - run_git, - validate_recorded_verification, - write_github_output, -) -from scripts.beta_conformance import ( - EXPERIMENTS as CONFORMANCE_EXPERIMENTS, -) -from scripts.beta_conformance import ( - LEGACY_PLAN_SCHEMA, - LEGACY_SUITE_RESULT_SCHEMA, - ConformanceError, - validate_public_evidence_strings, - validate_recorded_experiment_result, - validate_recorded_plan, -) -from scripts.beta_conformance import ( - PLAN_SCHEMA as CONFORMANCE_PLAN_SCHEMA, -) -from scripts.beta_conformance import ( - SUITE_RESULT_SCHEMA as CONFORMANCE_SUITE_RESULT_SCHEMA, -) -from scripts.release_plan import ( - CONTINUITY_RESOLUTION_SCHEMA, - EXPECTED_DEFAULT_BRANCHES, - FOUNDATION_COMMIT, - FOUNDATION_TAG, - PLAN_TAG_PREFIX, - candidate_manifest, - continuity_resolution_tag, - discover_plan, - read_public_record, - resolve_tag, - validate_continuity_resolution_authority, - validate_plan, -) -from scripts.release_plan import ( - LEGACY_SCHEMA as LEGACY_RELEASE_PLAN_SCHEMA, -) -from scripts.release_plan import ( - SCHEMA as RELEASE_PLAN_SCHEMA, -) -from scripts.release_plan import ( - validate_recorded_plan as validate_recorded_release_plan, -) - -SCHEMA = "durable-workflow.beta-continuity.config/v1" -EVIDENCE_SCHEMA = "durable-workflow.beta-continuity.evidence/v1" -SELECTION_SCHEMA = "durable-workflow.beta-continuity.selection/v1" -CONTROL_REPOSITORY = "durable-workflow/.github" -PHASE_TAG_PREFIX = "beta-continuity/" -SELECTION_TAG_PREFIX = "beta-continuity-selection/" -RELEASE_WORKFLOW = "release-plan.yml" -CONTINUITY_WORKFLOW = "beta-continuity.yml" -OBSERVER_WORKFLOW = "release-plan-observer.yml" -CANDIDATE_WORKFLOW = "beta-candidate.yml" -BETA_CANDIDATE_WORKFLOW_NAME = "Beta candidate" -BETA_CANDIDATE_WORKFLOW_PATH = f".github/workflows/{CANDIDATE_WORKFLOW}" -CONFORMANCE_WORKFLOW = "beta-conformance.yml" -CONFORMANCE_RETENTION_WORKFLOW = "beta-conformance-retention.yml" -CONTINUITY_RESOLUTION_SELECTION_SCHEMA = ( - "durable-workflow.release-plan-continuity-resolution-selection/v1" -) -WORK_ID_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,79}$") -BETA_WORK_ID_MARKER_PREFIX = "") -DURABLE_WORK_ID_MARKER_PREFIX = "") -PLAN_PREFIX_PATTERN = re.compile(r"^[a-z0-9][a-z0-9-]{0,35}$") -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -PUBLIC_REPOSITORY_PATTERN = re.compile(r"^durable-workflow/[A-Za-z0-9._-]+$") -STABLE_VERSION_PATTERN = re.compile(r"^(0\.[0-9]+\.)([0-9]+)$") -ALPHA_VERSION_PATTERN = re.compile(r"^(2\.0\.0-alpha\.)([1-9][0-9]*)$") -SOURCE_MANIFESTS = { - "sdk-python": ("pyproject.toml", "project", "durable-workflow"), - "sdk-rust": ("Cargo.toml", "package", "durable-workflow"), -} -PHASES = ( - "accepted", - "interrupted", - "resumed", - "conformance-requested", - "complete", - "no-op-confirmed", -) -ROUTED_BLOCKER_AUTHORITY_LABELS = ( - "authority:github", - "beta:blocker", - "kind:release-blocker", - "priority:P1", -) -ROUTED_BLOCKER_LIFECYCLE_LABELS = {"status:ready", "status:done"} -ROUTED_BLOCKER_LABELS = ( - *ROUTED_BLOCKER_AUTHORITY_LABELS, - "status:ready", -) -ISSUE_KIND_LABELS = {"kind:defect", "kind:feature", "kind:release-blocker", "kind:cross-repository"} -ROUTED_BLOCKER_MARKER = re.compile( - r"" -) -ROUTED_BLOCKER_MARKER_PREFIX = "" - - -def ensure_issue_comment( - writer: GitHubWriter, - config: dict[str, Any], - plan: dict[str, Any], - phase: str, - record: dict[str, str], - summary: str, -) -> None: - issue = config["authority_issue"] - path = f"/repos/{issue['repository']}/issues/{issue['number']}/comments" - marker = issue_phase_marker(config, phase) - comments = writer.list(path) - if any(marker in str(comment.get("body", "")) for comment in comments): - return - url = f"https://github.com/{CONTROL_REPOSITORY}/tree/{urllib.parse.quote(record['tag'], safe='/')}" - body = ( - f"{marker}\n" - f"GitHub continuity phase `{phase}` is retained at [`{record['tag']}`]({url}) " - f"for immutable plan `release-plan/{plan['plan']}`. {summary}" - ) - writer.request("POST", path, {"body": body}) - - -def base_evidence( - config: dict[str, Any], - issue: dict[str, Any], - plan: dict[str, Any], - phase: str, - run: dict[str, str], -) -> dict[str, Any]: - return { - "schema": EVIDENCE_SCHEMA, - "drill": config["drill"], - "phase": phase, - "observed_at": utc_now(), - "authority_issue": issue, - "release_plan": {"tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", "sha256": manifest_digest(plan)}, - "github_run": run, - } - - -def conformance_release_rank(prefix: str, tag: Any) -> tuple[int, int] | None: - if not isinstance(tag, str) or not tag.startswith(prefix): - return None - match = re.fullmatch(r"([1-9][0-9]*)\.([1-9][0-9]*)", tag.removeprefix(prefix)) - if match is None: - return None - return int(match.group(1)), int(match.group(2)) - - -def exact_conformance_reference(plan: dict[str, Any], reference: Any) -> bool: - candidate = candidate_manifest(plan) - prefix = f"beta-conformance/{candidate['candidate']}/" - if not isinstance(reference, dict) or set(reference) != {"release", "run", "tag"}: - return False - rank = conformance_release_rank(prefix, reference.get("tag")) - run = reference.get("run") - return ( - rank is not None - and isinstance(reference.get("release"), str) - and reference["release"].startswith(f"https://github.com/{CONTROL_REPOSITORY}/releases/tag/") - and run - == { - "repository": CONTROL_REPOSITORY, - "run_id": rank[0], - "run_attempt": rank[1], - "evidence_tag": reference["tag"], - } - ) - - -def validated_conformance_release( - client: PublicClient, - plan: dict[str, Any], - candidate: dict[str, Any], - release: dict[str, Any], - rank: tuple[int, int], - *, - allow_legacy: bool = False, -) -> dict[str, Any] | None: - tag = release.get("tag_name") - release_url = release.get("html_url") - release_assets = release.get("assets") - if ( - not isinstance(tag, str) - or not isinstance(release_url, str) - or not release_url.startswith(f"https://github.com/{CONTROL_REPOSITORY}/releases/tag/") - or not isinstance(release_assets, list) - ): - return None - assets = { - item.get("name"): item - for item in release_assets - if isinstance(item, dict) and isinstance(item.get("name"), str) - } - suite_asset = assets.get("suite-result.json") - if not isinstance(suite_asset, dict): - return None - try: - suite, _suite_payload = validated_conformance_asset(client, suite_asset) - except ConformanceError: - return None - expected_run = { - "repository": CONTROL_REPOSITORY, - "run_id": rank[0], - "run_attempt": rank[1], - "evidence_tag": tag, - } - suite_schema = suite.get("schema") if isinstance(suite, dict) else None - if suite_schema == LEGACY_SUITE_RESULT_SCHEMA and not allow_legacy: - return None - if suite_schema not in {LEGACY_SUITE_RESULT_SCHEMA, CONFORMANCE_SUITE_RESULT_SCHEMA}: - return None - if suite.get("github_run") != expected_run: - return None - if ( - suite.get("outcome") != "pass" - or not isinstance(suite.get("candidate"), dict) - or suite["candidate"].get("name") != candidate["candidate"] - or suite.get("artifact_tuple") != plan["components"] - ): - return None - legacy = suite_schema == LEGACY_SUITE_RESULT_SCHEMA - conformance_plan = { - "schema": LEGACY_PLAN_SCHEMA if legacy else CONFORMANCE_PLAN_SCHEMA, - "candidate": suite.get("candidate"), - "artifact_tuple": suite.get("artifact_tuple"), - "source_identities": suite.get("source_identities"), - "distribution_identities": suite.get("distribution_identities"), - "runtime_dependencies": suite.get("runtime_dependencies"), - "runner": suite.get("runner"), - "server_runner": suite.get("server_runner"), - "experiments": list(CONFORMANCE_EXPERIMENTS), - } - if not legacy: - conformance_plan["waterline_service_runner"] = suite.get("waterline_service_runner") - try: - validate_recorded_plan(conformance_plan) - summaries = suite.get("experiments") - if not isinstance(summaries, dict) or set(summaries) != set(CONFORMANCE_EXPERIMENTS): - return None - for experiment in CONFORMANCE_EXPERIMENTS: - experiment_asset = assets.get(f"{experiment}.json") - if not isinstance(experiment_asset, dict): - raise ConformanceError("durable conformance release is missing an experiment asset") - result, payload = validated_conformance_asset(client, experiment_asset) - validate_recorded_experiment_result(result, conformance_plan) - summary = summaries[experiment] - if ( - not isinstance(result, dict) - or result.get("experiment") != experiment - or not isinstance(summary, dict) - or summary.get("result_sha256") != hashlib.sha256(payload).hexdigest() - or summary.get("outcome") != result.get("outcome") - or summary.get("classification") != result.get("classification") - ): - raise ConformanceError("durable conformance release has mismatched experiment evidence") - except ConformanceError: - return None - return {"release": release_url, "run": expected_run, "tag": tag} - - -def public_conformance_releases(client: PublicClient) -> list[dict[str, Any]]: - return public_releases(client, CONTROL_REPOSITORY) - - -def conformance_evidence( - client: PublicClient, - plan: dict[str, Any], - *, - preferred: dict[str, Any] | None = None, -) -> dict[str, Any] | None: - candidate = candidate_manifest(plan) - releases = public_conformance_releases(client) - prefix = f"beta-conformance/{candidate['candidate']}/" - ranked_releases = sorted( - ( - (rank, release) - for release in releases - if isinstance(release, dict) - and not release.get("draft") - and (rank := conformance_release_rank(prefix, release.get("tag_name"))) is not None - ), - key=lambda item: item[0], - ) - if preferred is not None: - preferred_rank = conformance_release_rank(prefix, preferred.get("tag")) - if preferred_rank is None: - return None - preferred_release = next( - (release for _rank, release in ranked_releases if release.get("tag_name") == preferred["tag"]), - None, - ) - if preferred_release is not None: - evidence = validated_conformance_release( - client, - plan, - candidate, - preferred_release, - preferred_rank, - allow_legacy=plan.get("schema") == LEGACY_RELEASE_PLAN_SCHEMA, - ) - if evidence == preferred: - return evidence - ranked_releases = [(rank, release) for rank, release in ranked_releases if rank > preferred_rank] - - for rank, release in ranked_releases: - evidence = validated_conformance_release(client, plan, candidate, release, rank) - if evidence is not None: - return evidence - return None - - -def validated_conformance_asset(client: PublicClient, asset: dict[str, Any]) -> tuple[Any, bytes]: - url = asset.get("browser_download_url") - if not isinstance(url, str): - raise ConformanceError("durable conformance release asset has no download URL") - payload = client.bytes(url) - try: - value = json.loads(payload.decode()) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise ConformanceError("durable conformance release asset is not valid JSON") from error - validate_public_evidence_strings(value) - return value, payload - - -def accepted_qualification_evidence( - client: PublicClient, - plan: dict[str, Any], - accepted_commit: str, -) -> dict[str, Any]: - acceptance = read_public_json_file(client, accepted_commit, "continuity-evidence.json") - qualification = read_public_json_file(client, accepted_commit, "target-qualification-evidence.json") - targets = qualification.get("targets") if isinstance(qualification, dict) else None - controller_run = acceptance.get("github_run") if isinstance(acceptance, dict) else None - controller_commit = controller_run.get("sha") if isinstance(controller_run, dict) else None - required_targets = set(COMPONENTS) | {"github-control-plane"} - if ( - not isinstance(qualification, dict) - or set(qualification) != {"schema", "targets"} - or qualification.get("schema") != QUALIFICATION_EVIDENCE_SCHEMA - or not isinstance(targets, dict) - or not required_targets <= set(targets) - or not isinstance(controller_commit, str) - or not COMMIT_PATTERN.fullmatch(controller_commit) - ): - raise ContinuityError("accepted continuity record lacks exact target qualification evidence") - - expected_commits = {name: identity["commit"] for name, identity in plan["components"].items()} - expected_commits["github-control-plane"] = controller_commit - for name, expected_commit in expected_commits.items(): - target = targets.get(name) - protected = target.get("protected_checks") if isinstance(target, dict) else None - successful = target.get("successful_check_runs") if isinstance(target, dict) else None - expected_branch = "main" if name == "github-control-plane" else EXPECTED_DEFAULT_BRANCHES[name] - if ( - not isinstance(target, dict) - or target.get("branch") != expected_branch - or target.get("commit") != expected_commit - or not isinstance(protected, list) - or not protected - or not all(isinstance(check, str) and check for check in protected) - or not isinstance(successful, dict) - or set(successful) != set(protected) - or not all(type(run_id) is int and run_id > 0 for run_id in successful.values()) - ): - raise ContinuityError(f"target qualification evidence for {name} does not prove the exact plan source") - return { - "commit": accepted_commit, - "sha256": manifest_digest(qualification), - "tag": phase_tag(plan, "accepted"), - } - - -def exact_completion_authority( - client: PublicClient, - config: dict[str, Any], - plan: dict[str, Any], - complete_commit: str, - noop_commit: str, -) -> dict[str, Any]: - plan_digest = manifest_digest(plan) - plan_tag_value = f"{PLAN_TAG_PREFIX}{plan['plan']}" - public_plan_record = plan_record(client, plan) - if public_plan_record is None: - raise ContinuityError("completed continuity plan has no immutable plan artifact") - plan_commit = public_plan_record["commit"] - recorded_plan = read_public_json_file(client, plan_commit, "release-plan.json") - validate_recorded_release_plan(recorded_plan) - if canonical_json(recorded_plan) != canonical_json(plan): - raise ContinuityError("completed continuity plan artifact differs from the exact accepted plan") - plan_record_value = {"tag": plan_tag_value, "commit": plan_commit, "sha256": plan_digest} - if public_plan_record != plan_record_value: - raise ContinuityError("completed continuity plan record has an invalid public identity") - - accepted_commit = public_phase_commit(client, plan, "accepted") - if accepted_commit is None: - raise ContinuityError("completed continuity plan has no immutable acceptance artifact") - acceptance = accepted_plan_authority(client, plan) - if acceptance is None or acceptance["commit"] != accepted_commit: - raise ContinuityError("completed continuity acceptance does not match the exact plan") - qualification = accepted_qualification_evidence(client, plan, accepted_commit) - - completion_tag = f"release-candidate/{plan['channel']}/{plan['plan']}" - completion_commit = resolve_tag(client, CONTROL_REPOSITORY, completion_tag) - if completion_commit is None: - raise ContinuityError("completed continuity plan has no public verification artifact") - completion_artifact = read_public_json_file(client, completion_commit, "release-candidate.json") - completion_verification = read_public_json_file(client, completion_commit, "verification.json") - expected_release_plan = {"tag": plan_tag_value, "commit": plan_commit, "sha256": plan_digest} - completion_keys = {"schema", "candidate", "channel", "release_plan", "components"} - if not isinstance(completion_artifact, dict) or ( - frozenset(completion_artifact) - not in {frozenset(completion_keys), frozenset(completion_keys | {"release_preparation_sha256"})} - or completion_artifact.get("schema") != "durable-workflow.release-candidate/v1" - or completion_artifact.get("candidate") != plan["plan"] - or completion_artifact.get("channel") != plan["channel"] - or completion_artifact.get("release_plan") != expected_release_plan - or completion_artifact.get("components") != plan["components"] - ): - raise ContinuityError("public completion artifact differs from the exact plan") - public_verification = ( - completion_verification.get("public_verification") if isinstance(completion_verification, dict) else None - ) - completion_verification_keys = { - "schema", - "candidate", - "channel", - "release_plan_sha256", - "public_verification", - } - if ( - not isinstance(completion_verification, dict) - or frozenset(completion_verification) - not in { - frozenset(completion_verification_keys), - frozenset(completion_verification_keys | {"release_preparation_sha256"}), - } - or completion_verification.get("schema") != "durable-workflow.release-candidate-verification/v1" - or completion_verification.get("candidate") != plan["plan"] - or completion_verification.get("channel") != plan["channel"] - or completion_verification.get("release_plan_sha256") != plan_digest - or completion_verification.get("release_preparation_sha256") - != completion_artifact.get("release_preparation_sha256") - or not isinstance(public_verification, dict) - ): - raise ContinuityError("public completion verification differs from the exact plan") - try: - validate_recorded_verification(public_verification, candidate_manifest(plan)) - except CandidateError as error: - raise ContinuityError(f"public completion verification does not prove exact sources: {error}") from error - - published, pending = component_publications(client, plan) - if pending or set(published) != set(COMPONENTS): - raise ContinuityError(f"completed continuity sources are not all public: pending={sorted(pending)}") - - complete_evidence = read_public_json_file(client, complete_commit, "continuity-evidence.json") - complete_plan = read_public_json_file(client, complete_commit, "release-plan.json") - expected_phase_record = {"tag": completion_tag, "commit": completion_commit} - recorded_conformance = complete_evidence.get("conformance") if isinstance(complete_evidence, dict) else None - if ( - canonical_json(complete_plan) != canonical_json(plan) - or not isinstance(complete_evidence, dict) - or complete_evidence.get("schema") != EVIDENCE_SCHEMA - or complete_evidence.get("drill") != config["drill"] - or complete_evidence.get("phase") != "complete" - or complete_evidence.get("outcome") != "passed" - or complete_evidence.get("release_plan") != {"tag": plan_tag_value, "sha256": plan_digest} - or complete_evidence.get("accepted_phase") != phase_tag(plan, "accepted") - or complete_evidence.get("interrupted_phase") != phase_tag(plan, "interrupted") - or complete_evidence.get("resumed_phase") != phase_tag(plan, "resumed") - or complete_evidence.get("plan_record") != plan_record_value - or complete_evidence.get("public_verification") != expected_phase_record - or not exact_conformance_reference(plan, recorded_conformance) - or complete_evidence.get("published_components") != published - ): - raise ContinuityError("immutable completion phase does not prove exact plan artifacts and sources") - live_conformance = conformance_evidence(client, plan, preferred=recorded_conformance) - if live_conformance is None: - raise ContinuityError("completed continuity plan has no live exact-tuple conformance evidence") - - noop_evidence = read_public_json_file(client, noop_commit, "continuity-evidence.json") - noop_plan = read_public_json_file(client, noop_commit, "release-plan.json") - successful_noop = noop_evidence.get("successful_no_op_run") if isinstance(noop_evidence, dict) else None - if ( - canonical_json(noop_plan) != canonical_json(plan) - or noop_evidence.get("schema") != EVIDENCE_SCHEMA - or noop_evidence.get("drill") != config["drill"] - or noop_evidence.get("phase") != "no-op-confirmed" - or noop_evidence.get("outcome") != "successful-scheduled-no-op-confirmed" - or noop_evidence.get("release_plan") != {"tag": plan_tag_value, "sha256": plan_digest} - or noop_evidence.get("complete_phase") != {"tag": phase_tag(plan, "complete"), "commit": complete_commit} - or not isinstance(successful_noop, dict) - or successful_noop.get("conclusion") != "success" - or type(successful_noop.get("id")) is not int - or not isinstance(successful_noop.get("url"), str) - ): - raise ContinuityError("scheduled no-op evidence does not prove the exact completed continuity plan") - - stable_tags = { - plan_tag_value: plan_commit, - phase_tag(plan, "accepted"): accepted_commit, - phase_tag(plan, "complete"): complete_commit, - phase_tag(plan, "no-op-confirmed"): noop_commit, - completion_tag: completion_commit, - } - for tag, expected_commit in stable_tags.items(): - if resolve_tag(client, CONTROL_REPOSITORY, tag) != expected_commit: - raise ContinuityError(f"public evidence tag {tag} moved during completion validation") - return { - "complete_phase": {"tag": phase_tag(plan, "complete"), "commit": complete_commit}, - "no_op_phase": {"tag": phase_tag(plan, "no-op-confirmed"), "commit": noop_commit}, - "plan": plan, - "plan_record": plan_record_value, - "public_verification": expected_phase_record, - "qualification": qualification, - "conformance": live_conformance, - "sources": published, - } - - -def completion_evidence_report(marker: str, completion: dict[str, Any], closing_lines: list[str]) -> str: - plan = completion["plan"] - plan_record_value = completion["plan_record"] - verification = completion["public_verification"] - qualification = completion["qualification"] - conformance = completion["conformance"] - versions = ", ".join(f"{name} `{identity['version']}`" for name, identity in sorted(plan["components"].items())) - lines = [ - marker, - "Exact GitHub continuity evidence is verified and live.", - "", - ( - f"- Plan: [`{plan_record_value['tag']}`](https://github.com/{CONTROL_REPOSITORY}/tree/" - f"{urllib.parse.quote(plan_record_value['tag'], safe='/')}) at `{plan_record_value['commit']}`." - ), - ( - f"- Public verification: [`{verification['tag']}`](https://github.com/{CONTROL_REPOSITORY}/tree/" - f"{urllib.parse.quote(verification['tag'], safe='/')}) at `{verification['commit']}`." - ), - ( - f"- Qualification: [`{qualification['tag']}`](https://github.com/{CONTROL_REPOSITORY}/tree/" - f"{urllib.parse.quote(qualification['tag'], safe='/')}) at `{qualification['commit']}` with SHA-256 " - f"`{qualification['sha256']}`." - ), - f"- Conformance: [`{conformance['tag']}`]({conformance['release']}).", - f"- Published versions: {versions}.", - *closing_lines, - ] - return "\n".join(lines) + "\n" - - -def blocker_completion_report_body( - config: dict[str, Any], - completion: dict[str, Any], - component_name: str, - repository: str, - number: int, -) -> str: - plan = completion["plan"] - identity = plan["components"][component_name] - marker = ( - f"" - ) - return completion_evidence_report( - marker, - completion, - [ - ( - f"- This routed blocker proves {component_name} `{identity['version']}` from exact source " - f"`{identity['commit']}`." - ) - ], - ) - - -def work_item_completion_report_body( - config: dict[str, Any], - completion: dict[str, Any], - specification: dict[str, Any], -) -> str: - plan = completion["plan"] - marker = ( - f"" - ) - return completion_evidence_report( - marker, - completion, - [f"- Trusted work item `{specification['work_id']}` is complete from this exact evidence."], - ) - - -def completion_report_body( - config: dict[str, Any], - completion: dict[str, Any], - blockers: list[dict[str, Any]], - work_items: list[dict[str, Any]], -) -> str: - plan = completion["plan"] - marker = f"" - blocker_summary = ( - ", ".join(f"[{item['repository']}#{item['number']}]({item['url']})" for item in blockers) - if blockers - else "No exact routed blocker issues were present." - ) - work_item_summary = ", ".join(f"[{item['repository']}#{item['number']}]({item['url']})" for item in work_items) - return completion_evidence_report( - marker, - completion, - [ - f"- Routed blockers completed before this parent: {blocker_summary}", - f"- Evidence work items completed before this parent: {work_item_summary}", - ], - ) - - -def ensure_exact_completion_comment(writer: GitHubWriter, path: str, body: str) -> None: - comments = writer.list(path) - if not any(comment.get("body") == body for comment in comments if isinstance(comment, dict)): - writer.request("POST", path, {"body": body}) - - -def require_exact_completion_comment(writer: GitHubWriter, path: str, body: str) -> None: - comments = writer.list(path) - if not any(comment.get("body") == body for comment in comments if isinstance(comment, dict)): - raise ContinuityError(f"GitHub issue evidence comment did not persist at {path}") - - -def converge_routed_blockers( - writer: GitHubWriter, - config: dict[str, Any], - completion: dict[str, Any], -) -> list[dict[str, Any]]: - plan = completion.get("plan") - if not isinstance(plan, dict) or plan.get("components") is None: - raise ContinuityError("verified completion evidence has no exact plan") - routed: list[tuple[str, str, dict[str, Any]]] = [] - for component_name, identity in plan["components"].items(): - repository = COMPONENTS[component_name].repository - issues = writer.list(f"/repos/{repository}/issues?state=all") - for issue in issues: - if ( - not isinstance(issue, dict) - or "pull_request" in issue - or not isinstance(issue.get("number"), int) - or not has_routed_blocker_authority(issue) - or not has_routed_blocker_dependency(config, issue) - ): - continue - if is_exact_routed_blocker(config, issue, component_name, identity["version"]): - routed.append((component_name, repository, issue)) - continue - labels = {label["name"] for label in issue["labels"]} - if issue.get("state") != "closed" or "status:ready" in labels: - raise ContinuityError( - f"active routed blocker {repository}#{issue['number']} differs from the exact completed plan" - ) - - result: list[dict[str, Any]] = [] - for component_name, repository, issue in sorted(routed, key=lambda item: (item[1], item[2]["number"])): - number = issue["number"] - path = f"/repos/{repository}/issues/{number}" - comment_path = f"{path}/comments" - current = writer.get(path) - if ( - not isinstance(current, dict) - or not has_routed_blocker_authority(current) - or not is_exact_routed_blocker( - config, - current, - component_name, - plan["components"][component_name]["version"], - ) - ): - raise ContinuityError(f"routed blocker {repository}#{number} lost its trusted exact authority") - report = blocker_completion_report_body(config, completion, component_name, repository, number) - ensure_exact_completion_comment(writer, comment_path, report) - labels = {label["name"] for label in current["labels"]} - desired_labels = {label for label in labels if not label.startswith("status:")} - desired_labels.discard("completion:evidence-required") - desired_labels.update({"completion:evidence-verified", "status:done"}) - desired_labels = replace_issue_kind( - desired_labels, - "kind:release-blocker", - f"routed blocker {repository}#{number}", - ) - if current.get("state") != "closed" or labels != desired_labels: - writer.request( - "PATCH", - path, - {"labels": sorted(desired_labels), "state": "closed"}, - ) - live = writer.get(path) - live_labels = { - label.get("name") for label in live.get("labels", []) if isinstance(label, dict) and label.get("name") - } - if ( - live.get("state") != "closed" - or not {"completion:evidence-verified", "status:done"} <= live_labels - or not has_routed_blocker_authority(live) - or not is_exact_routed_blocker(config, live, component_name, plan["components"][component_name]["version"]) - ): - raise ContinuityError(f"routed blocker {repository}#{number} did not converge to verified completion") - require_exact_completion_comment(writer, comment_path, report) - result.append( - { - "component": component_name, - "labels": sorted(live_labels), - "number": number, - "repository": repository, - "state": "closed", - "url": live.get("html_url") or f"https://github.com/{repository}/issues/{number}", - "version": plan["components"][component_name]["version"], - } - ) - return result - - -def validate_evidence_work_item( - specification: dict[str, Any], - issue: dict[str, Any], - *, - require_completed: bool = False, -) -> set[str]: - labels = {label.get("name") for label in issue.get("labels", []) if isinstance(label, dict) and label.get("name")} - required = set(specification["required_labels"]) - required_kind = require_exact_issue_kind( - required, - f"trusted evidence work item {specification['repository']}#{specification['number']}", - ) - authority = {label for label in required if not label.startswith("status:") and not label.startswith("completion:")} - statuses = {label for label in labels if label.startswith("status:")} - completions = {label for label in labels if label.startswith("completion:")} - ready = ( - issue.get("state") == "open" - and required <= labels - and statuses == {"status:ready"} - and completions == {"completion:evidence-required"} - ) - completed = ( - issue.get("state") == "closed" - and authority <= labels - and statuses == {"status:done"} - and completions == {"completion:evidence-verified"} - ) - if not has_exact_durable_work_id(issue, specification["work_id"]) or ( - not completed if require_completed else not (ready or completed) - ): - raise ContinuityError( - f"trusted evidence work item {specification['repository']}#{specification['number']} " - "does not match its configured work-id, labels, and lifecycle" - ) - location = f"trusted evidence work item {specification['repository']}#{specification['number']}" - if require_exact_issue_kind(labels, location) != required_kind: - raise ContinuityError( - f"{location} has an incompatible kind" - ) - return labels - - -def load_evidence_work_items( - writer: GitHubWriter, - config: dict[str, Any], -) -> list[tuple[dict[str, Any], dict[str, Any]]]: - work_items: list[tuple[dict[str, Any], dict[str, Any]]] = [] - for specification in config["evidence_work_items"]: - path = f"/repos/{specification['repository']}/issues/{specification['number']}" - issue = writer.get(path) - if not isinstance(issue, dict): - raise ContinuityError(f"trusted evidence work item {path} has an invalid GitHub response") - validate_evidence_work_item(specification, issue) - work_items.append((specification, issue)) - return work_items - - -def converge_evidence_work_items( - writer: GitHubWriter, - config: dict[str, Any], - completion: dict[str, Any], - work_items: list[tuple[dict[str, Any], dict[str, Any]]], -) -> list[dict[str, Any]]: - result: list[dict[str, Any]] = [] - for specification, _issue in work_items: - repository = specification["repository"] - number = specification["number"] - path = f"/repos/{repository}/issues/{number}" - comment_path = f"{path}/comments" - current = writer.get(path) - if not isinstance(current, dict): - raise ContinuityError(f"trusted evidence work item {path} has an invalid GitHub response") - labels = validate_evidence_work_item(specification, current) - report = work_item_completion_report_body(config, completion, specification) - ensure_exact_completion_comment(writer, comment_path, report) - desired_labels = { - label for label in labels if not label.startswith("status:") and not label.startswith("completion:") - } - desired_labels.update({"completion:evidence-verified", "status:done"}) - required_kind = require_exact_issue_kind( - set(specification["required_labels"]), - f"trusted evidence work item {repository}#{number}", - ) - desired_labels = replace_issue_kind( - desired_labels, - required_kind, - f"trusted evidence work item {repository}#{number}", - ) - if current.get("state") != "closed" or labels != desired_labels: - writer.request("PATCH", path, {"labels": sorted(desired_labels), "state": "closed"}) - live = writer.get(path) - live_labels = validate_evidence_work_item(specification, live, require_completed=True) - require_exact_completion_comment(writer, comment_path, report) - result.append( - { - "labels": sorted(live_labels), - "number": number, - "repository": repository, - "state": "closed", - "url": live.get("html_url") or f"https://github.com/{repository}/issues/{number}", - "work_id": specification["work_id"], - } - ) - return result - - -def close_authority_issue( - writer: GitHubWriter, - config: dict[str, Any], - completion: dict[str, Any], -) -> dict[str, Any]: - issue = config["authority_issue"] - path = f"/repos/{issue['repository']}/issues/{issue['number']}" - current = writer.get(path) - if not isinstance(current, dict): - raise ContinuityError("parent continuity issue has an invalid GitHub response") - current_labels = { - label.get("name") for label in current.get("labels", []) if isinstance(label, dict) and label.get("name") - } - required_parent_labels = set(config["required_issue_labels"]) - authority_labels = { - label - for label in required_parent_labels - if not label.startswith("status:") and not label.startswith("completion:") - } - parent_statuses = {label for label in current_labels if label.startswith("status:")} - parent_completions = {label for label in current_labels if label.startswith("completion:")} - parent_ready = ( - current.get("state") == "open" - and required_parent_labels <= current_labels - and parent_statuses == {"status:ready"} - and parent_completions == {"completion:evidence-required"} - ) - parent_completed = ( - current.get("state") == "closed" - and authority_labels <= current_labels - and parent_statuses == {"status:done"} - and parent_completions == {"completion:evidence-verified"} - ) - if not has_exact_work_id(current, issue["work_id"]) or not (parent_ready or parent_completed): - raise ContinuityError("parent continuity issue lost its protected authority before completion") - - evidence_work_items = load_evidence_work_items(writer, config) - blockers = converge_routed_blockers(writer, config, completion) - completed_work_items = converge_evidence_work_items(writer, config, completion, evidence_work_items) - current = writer.get(path) - if not isinstance(current, dict): - raise ContinuityError("parent continuity issue has an invalid GitHub response") - current_labels = { - label.get("name") for label in current.get("labels", []) if isinstance(label, dict) and label.get("name") - } - parent_statuses = {label for label in current_labels if label.startswith("status:")} - parent_completions = {label for label in current_labels if label.startswith("completion:")} - parent_ready = ( - current.get("state") == "open" - and required_parent_labels <= current_labels - and parent_statuses == {"status:ready"} - and parent_completions == {"completion:evidence-required"} - ) - parent_completed = ( - current.get("state") == "closed" - and authority_labels <= current_labels - and parent_statuses == {"status:done"} - and parent_completions == {"completion:evidence-verified"} - ) - if not has_exact_work_id(current, issue["work_id"]) or not (parent_ready or parent_completed): - raise ContinuityError("parent continuity issue lost its protected authority before completion") - comment_path = f"{path}/comments" - report = completion_report_body(config, completion, blockers, completed_work_items) - ensure_exact_completion_comment(writer, comment_path, report) - - labels = {label.get("name") for label in current.get("labels", []) if isinstance(label, dict) and label.get("name")} - labels.discard("status:ready") - labels.discard("status:blocked") - labels.discard("completion:evidence-required") - labels.update({"status:done", "completion:evidence-verified"}) - parent_kind = require_exact_issue_kind(required_parent_labels, "parent continuity issue") - labels = replace_issue_kind(labels, parent_kind, "parent continuity issue completion") - if current.get("state") != "closed" or labels != current_labels: - writer.request("PATCH", path, {"labels": sorted(labels), "state": "closed"}) - live_parent = writer.get(path) - live_labels = { - label.get("name") for label in live_parent.get("labels", []) if isinstance(label, dict) and label.get("name") - } - if ( - live_parent.get("state") != "closed" - or authority_labels - live_labels - or {label for label in live_labels if label.startswith("status:")} != {"status:done"} - or {label for label in live_labels if label.startswith("completion:")} != {"completion:evidence-verified"} - or not has_exact_work_id(live_parent, issue["work_id"]) - ): - raise ContinuityError("parent continuity issue did not converge to verified completion") - require_exact_completion_comment(writer, comment_path, report) - return { - "blockers": blockers, - "evidence_work_items": completed_work_items, - "parent": { - "labels": sorted(live_labels), - "number": issue["number"], - "repository": issue["repository"], - "state": "closed", - "url": live_parent.get("html_url") or f"https://github.com/{issue['repository']}/issues/{issue['number']}", - }, - } - - -def advance_command( - config_path: Path, - plan_path: Path, - state_path: Path, - qualification_path: Path | None, - output: Path | None, -) -> None: - config = load_config(config_path) - plan = load_json(plan_path, "release plan") - validate_recorded_release_plan(plan) - github_token = os.environ.get("GITHUB_TOKEN") - authority_token = os.environ.get("BETA_PRODUCT_WORK_TOKEN") - client = PublicClient(github_token) - writer = GitHubWriter(authority_token or "", os.environ.get("GITHUB_API_URL", "https://api.github.com")) - dispatcher = GitHubWriter(github_token or "", os.environ.get("GITHUB_API_URL", "https://api.github.com")) - issue = authority_issue(config, client, allow_completed=True) - run = { - "id": os.environ.get("GITHUB_RUN_ID", "local"), - "attempt": os.environ.get("GITHUB_RUN_ATTEMPT", "1"), - "repository": os.environ.get("GITHUB_REPOSITORY", CONTROL_REPOSITORY), - "sha": os.environ.get("GITHUB_SHA", "0" * 40), - } - - if issue["state"] == "closed": - complete_commit = public_phase_commit(client, plan, "complete") - noop_commit = public_phase_commit(client, plan, "no-op-confirmed") - if complete_commit is None or noop_commit is None: - raise ContinuityError( - "authority issue closed without immutable complete and scheduled no-op continuity phases" - ) - evidence = read_public_json_file(client, noop_commit, "continuity-evidence.json") - completion_authority = exact_completion_authority(client, config, plan, complete_commit, noop_commit) - close_authority_issue(writer, config, completion_authority) - state_path.write_bytes(canonical_json(evidence)) - write_github_output( - output, - {"phase": "no-op-confirmed", "plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}"}, - ) - return - - accepted_commit = public_phase_commit(client, plan, "accepted") - if accepted_commit is None: - if qualification_path is None or not qualification_path.exists(): - raise ContinuityError("first continuity acceptance requires exact target qualification evidence") - public_at_acceptance, pending_at_acceptance = component_publications(client, plan) - if not pending_at_acceptance: - raise ContinuityError("continuity acceptance requires at least one component pending publication") - evidence = base_evidence(config, issue, plan, "accepted", run) - evidence.update( - { - "outcome": "accepted", - "candidate_identity": {"components": plan["components"], "plan_sha256": manifest_digest(plan)}, - "credential_boundary": "GitHub protected beta product work environment", - "pending_components_at_acceptance": pending_at_acceptance, - "public_components_at_acceptance": public_at_acceptance, - "superseded_interruption": superseded_interruption(config, client), - } - ) - record = record_phase(Path.cwd(), plan, "accepted", evidence, qualification_path=qualification_path) - ensure_issue_comment( - writer, - config, - plan, - "accepted", - record, - "The public issue, exact target commits, protected checks, and candidate tuple are now remote authority.", - ) - writer.dispatch( - CONTROL_REPOSITORY, - RELEASE_WORKFLOW, - "main", - {"release_plan": canonical_json(plan).decode().strip()}, - ) - state_path.write_bytes(canonical_json(evidence)) - write_github_output(output, {"phase": "accepted", "plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}"}) - return - - complete_commit = public_phase_commit(client, plan, "complete") - if complete_commit is not None: - completion = read_public_json_file(client, complete_commit, "continuity-evidence.json") - event = os.environ.get("GITHUB_EVENT_NAME", "local") - noop_commit = public_phase_commit(client, plan, "no-op-confirmed") - successful_noop = ( - successful_scheduled_noop_run(writer, completion, run) - if event == "schedule" and noop_commit is None - else None - ) - if noop_commit is None and successful_noop is None: - state = base_evidence(config, issue, plan, "complete", run) - state.update( - { - "outcome": "waiting-for-subsequent-scheduled-no-op", - "complete_phase": {"tag": phase_tag(plan, "complete"), "commit": complete_commit}, - } - ) - state_path.write_bytes(canonical_json(state)) - write_github_output(output, {"phase": "complete", "plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}"}) - return - if noop_commit is None: - evidence = base_evidence(config, issue, plan, "no-op-confirmed", run) - evidence.update( - { - "outcome": "successful-scheduled-no-op-confirmed", - "complete_phase": {"tag": phase_tag(plan, "complete"), "commit": complete_commit}, - "successful_no_op_run": successful_noop, - } - ) - noop_record = record_phase(Path.cwd(), plan, "no-op-confirmed", evidence) - ensure_issue_comment( - writer, - config, - plan, - "no-op-confirmed", - noop_record, - "A later scheduled controller run found the completed exact plan and performed no release work.", - ) - noop_commit = noop_record["commit"] - else: - evidence = read_public_json_file(client, noop_commit, "continuity-evidence.json") - completion_authority = exact_completion_authority(client, config, plan, complete_commit, noop_commit) - close_authority_issue(writer, config, completion_authority) - state_path.write_bytes(canonical_json(evidence)) - write_github_output( - output, - {"phase": "no-op-confirmed", "plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}"}, - ) - return - - record = plan_record(client, plan) - if record is None: - ensure_dispatch( - writer, - CONTROL_REPOSITORY, - RELEASE_WORKFLOW, - "main", - {"release_plan": canonical_json(plan).decode().strip()}, - plan["plan"], - ) - state = base_evidence(config, issue, plan, "accepted", run) - state.update({"outcome": "waiting-for-immutable-plan"}) - state_path.write_bytes(canonical_json(state)) - write_github_output(output, {"phase": "accepted", "plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}"}) - return - - acceptance = accepted_publication_state(client, plan, accepted_commit) - published, pending = component_publications(client, plan) - interrupted_commit = public_phase_commit(client, plan, "interrupted") - if interrupted_commit is None: - interruption_triggers = recovery_publication_triggers(writer, record["tag"], acceptance, published) - else: - interruption_triggers = validate_interrupted_evidence( - client, - plan, - interrupted_commit, - acceptance, - ) - - if interrupted_commit is None and not interruption_triggers: - recovery_candidates = [name for name in acceptance["pending_components"] if name in pending] - if not recovery_candidates: - raise ContinuityError( - "all acceptance-pending components became public without a qualifying exact-plan recovery; " - "a new continuity identity must supersede this plan" - ) - first_component = config["first_component"] - recovery_component = first_component if first_component in recovery_candidates else recovery_candidates[0] - dispatch_recovery(writer, recovery_component, record["tag"]) - state = base_evidence(config, issue, plan, "publication-started", run) - state.update( - { - "outcome": "waiting-for-post-acceptance-recovery-publication", - "acceptance_publication_state": acceptance, - "plan_record": record, - "published_components": published, - "pending_components": pending, - "recovery_component": recovery_component, - } - ) - state_path.write_bytes(canonical_json(state)) - write_github_output(output, {"phase": "publication-started", "plan_tag": record["tag"]}) - return - - if interrupted_commit is None: - require_partial_publication(published, pending) - evidence = base_evidence(config, issue, plan, "interrupted", run) - evidence.update( - { - "outcome": "intentionally-interrupted", - "accepted_phase": {"tag": acceptance["tag"], "commit": acceptance["commit"]}, - "interruption_triggers": interruption_triggers, - "plan_record": record, - "published_components": published, - "pending_components": pending, - "resume_contract": "A later GitHub run must dispatch this exact immutable plan tag.", - } - ) - phase_record = record_phase(Path.cwd(), plan, "interrupted", evidence) - ensure_issue_comment( - writer, - config, - plan, - "interrupted", - phase_record, - ( - f"The controller yielded after {len(interruption_triggers)} acceptance-pending component(s) " - f"became public through exact-plan repository recovery; {len(pending)} remain." - ), - ) - state_path.write_bytes(canonical_json(evidence)) - write_github_output(output, {"phase": "interrupted", "plan_tag": record["tag"]}) - return - - resumed_commit = public_phase_commit(client, plan, "resumed") - if resumed_commit is None: - workflows = {name: dispatch_recovery(writer, name, record["tag"]) for name in COMPONENTS} - evidence = base_evidence(config, issue, plan, "resumed", run) - evidence.update( - { - "outcome": "resumed-identical-plan", - "interrupted_phase": {"tag": phase_tag(plan, "interrupted"), "commit": interrupted_commit}, - "plan_record": record, - "published_components_at_resume": published, - "pending_components_at_resume": pending, - "repository_recovery_workflows": workflows, - } - ) - phase_record = record_phase(Path.cwd(), plan, "resumed", evidence) - ensure_issue_comment( - writer, - config, - plan, - "resumed", - phase_record, - "All seven repository-owned recovery workflows received the identical immutable plan tag.", - ) - state_path.write_bytes(canonical_json(evidence)) - write_github_output(output, {"phase": "resumed", "plan_tag": record["tag"]}) - return - - if pending: - for name in pending: - dispatch_recovery(writer, name, record["tag"]) - state = base_evidence(config, issue, plan, "resumed", run) - state.update( - { - "outcome": "recovering", - "plan_record": record, - "published_components": published, - "pending_components": pending, - } - ) - state_path.write_bytes(canonical_json(state)) - write_github_output(output, {"phase": "resumed", "plan_tag": record["tag"]}) - return - - completion_tag = f"release-candidate/{plan['channel']}/{plan['plan']}" - completion_commit = resolve_tag(client, CONTROL_REPOSITORY, completion_tag) - if completion_commit is None: - ensure_dispatch( - writer, - CONTROL_REPOSITORY, - OBSERVER_WORKFLOW, - "main", - {"plan_tag": record["tag"]}, - record["tag"], - ) - state = base_evidence(config, issue, plan, "public-verification", run) - state.update({"outcome": "waiting-for-verification", "published_components": published}) - state_path.write_bytes(canonical_json(state)) - write_github_output(output, {"phase": "public-verification", "plan_tag": record["tag"]}) - return - - conformance = conformance_evidence(client, plan) - requested_commit = public_phase_commit(client, plan, "conformance-requested") - if conformance is None: - candidate = candidate_manifest(plan) - candidate_tag = f"beta-candidate/{candidate['candidate']}" - candidate_commit = resolve_tag(client, CONTROL_REPOSITORY, candidate_tag) - if candidate_commit is None: - ensure_dispatch( - dispatcher, - CONTROL_REPOSITORY, - CANDIDATE_WORKFLOW, - "main", - {"candidate_manifest": canonical_json(candidate).decode().strip()}, - candidate["candidate"], - ) - state = base_evidence(config, issue, plan, "candidate-verification", run) - state.update( - { - "outcome": "waiting-for-immutable-candidate", - "candidate": candidate, - "public_verification": {"tag": completion_tag, "commit": completion_commit}, - } - ) - state_path.write_bytes(canonical_json(state)) - write_github_output(output, {"phase": "candidate-verification", "plan_tag": record["tag"]}) - return - progress = ensure_conformance_execution_or_retention(dispatcher, candidate) - if requested_commit is None: - evidence = base_evidence(config, issue, plan, "conformance-requested", run) - evidence.update( - { - "outcome": "requested-clean-github-runner-conformance", - "candidate": {"manifest": candidate, "tag": candidate_tag, "commit": candidate_commit}, - "public_verification": {"tag": completion_tag, "commit": completion_commit}, - "conformance_progress": progress, - } - ) - phase_record = record_phase(Path.cwd(), plan, "conformance-requested", evidence) - ensure_issue_comment( - writer, - config, - plan, - "conformance-requested", - phase_record, - "Public verification passed and exact-tuple conformance execution or retention was requested.", - ) - state_path.write_bytes(canonical_json(evidence)) - else: - state = base_evidence(config, issue, plan, "conformance-requested", run) - state.update( - { - "outcome": progress["outcome"], - "conformance_progress": progress, - } - ) - state_path.write_bytes(canonical_json(state)) - write_github_output(output, {"phase": "conformance-requested", "plan_tag": record["tag"]}) - return - - complete_commit = public_phase_commit(client, plan, "complete") - if complete_commit is None: - evidence = base_evidence(config, issue, plan, "complete", run) - evidence.update( - { - "outcome": "passed", - "accepted_phase": phase_tag(plan, "accepted"), - "interrupted_phase": phase_tag(plan, "interrupted"), - "resumed_phase": phase_tag(plan, "resumed"), - "plan_record": record, - "public_verification": {"tag": completion_tag, "commit": completion_commit}, - "conformance": conformance, - "published_components": published, - } - ) - phase_record = record_phase(Path.cwd(), plan, "complete", evidence) - ensure_issue_comment( - writer, - config, - plan, - "complete", - phase_record, - ( - "The interrupted seven-artifact release, public verification, and exact-tuple conformance all " - "passed. The authority remains open for a later scheduled no-op confirmation." - ), - ) - state_path.write_bytes(canonical_json(evidence)) - else: - evidence = read_public_json_file(client, complete_commit, "continuity-evidence.json") - state_path.write_bytes(canonical_json(evidence)) - write_github_output(output, {"phase": "complete", "plan_tag": record["tag"]}) - - -def blocker_body(config: dict[str, Any], blocker: dict[str, str], selection: dict[str, Any]) -> str: - issue = config["authority_issue"] - selection_url = f"https://github.com/{CONTROL_REPOSITORY}/tree/{selection['tag']}" - return ( - "## Classification\n\n" - "Beta continuity release blocker owned by this repository.\n\n" - "## Evidence\n\n" - f"The GitHub-only continuity planner retained its version selection at {selection_url}, then paused: " - f"{blocker['reason']}.\n\n" - "## Acceptance criteria\n\n" - f"- Version `{blocker['version']}` is prepared from the default branch and published without rewriting " - "history.\n" - "- The repository-owned Release plan recovery workflow accepts that exact source and prepared plan.\n" - "- Protected target qualification is green before the continuity planner retries.\n\n" - "## Dependency\n\n" - f"Blocks https://github.com/{issue['repository']}/issues/{issue['number']}.\n\n" - f"\n" - ) - - -def consolidated_blocker_finding(blocker: dict[str, str], selection: dict[str, Any]) -> str: - selection_url = f"https://github.com/{CONTROL_REPOSITORY}/tree/{selection['tag']}" - return ( - f"## Consolidated continuity finding: {blocker['component']}\n\n" - f"The retained [continuity selection]({selection_url}) cannot advance because " - f"{blocker['reason']}.\n\n" - "### Additional acceptance criteria\n\n" - f"- Prepare and qualify version `{blocker['version']}` from the repository default branch.\n" - "- Resume the same retained release selection after this condition clears.\n\n" - f"\n" - ) - - -def _continuity_creation_budget( - config: dict[str, Any], - issues: list[dict[str, Any]], -) -> tuple[list[str], dict[str, Any] | None]: - budget = config["public_issue_budget"] - now = dt.datetime.now(dt.UTC) - - def labels(issue: dict[str, Any]) -> set[str]: - return { - str(label["name"]) - for label in issue.get("labels", []) - if isinstance(label, dict) and isinstance(label.get("name"), str) - } - - open_issues = [ - issue - for issue in issues - if ( - isinstance(issue, dict) - and "pull_request" not in issue - and issue.get("state") == "open" - ) - ] - actionable = [ - issue - for issue in open_issues - if "authority:github" in labels(issue) - and not labels(issue) & {"status:done", "status:superseded"} - ] - - def created_at(issue: dict[str, Any]) -> dt.datetime | None: - value = issue.get("created_at") - if value is None: - return None - try: - return parse_github_timestamp(value, "routed blocker creation timestamp") - except ContinuityError: - return None - - reasons: list[str] = [] - if len(actionable) >= int(budget["max_open_actionable"]): - reasons.append("open-actionable-budget") - if any( - created is not None and int((now - created).total_seconds()) >= int(budget["stale_age_seconds"]) - for issue in open_issues - if (created := created_at(issue)) is not None - ): - reasons.append("open-issue-older-than-7d") - roots = [ - issue - for issue in actionable - if {"authority:github", "kind:release-blocker"} <= labels(issue) - and isinstance(issue.get("body"), str) - and type(issue.get("number")) is int - ] - root = min( - roots, - key=lambda issue: ( - created_at(issue) or dt.datetime.max.replace(tzinfo=dt.UTC), - int(issue["number"]), - ), - default=None, - ) - return reasons, root - - -def route_blockers(config_path: Path, state_path: Path) -> None: - config = load_config(config_path) - state = load_json(state_path, "continuity planning state") - blockers = state.get("blockers") - selection = state.get("selection") - if state.get("outcome") != "blocked" or not isinstance(blockers, list) or not blockers: - raise ContinuityError("continuity planning state contains no routable blockers") - if not isinstance(selection, dict) or not isinstance(selection.get("tag"), str): - raise ContinuityError("continuity planning state has no immutable version selection") - writer = GitHubWriter( - os.environ.get("BETA_PRODUCT_WORK_TOKEN", ""), - os.environ.get("GITHUB_API_URL", "https://api.github.com"), - ) - routing: list[dict[str, Any]] = [] - for blocker in blockers: - repository = blocker["repository"] - issues = writer.list(f"/repos/{repository}/issues?state=all") - routed = [ - (issue["number"], issue) - for issue in issues - if isinstance(issue, dict) - and "pull_request" not in issue - and is_exact_routed_blocker( - config, - issue, - blocker["component"], - blocker["version"], - ) - and has_routed_blocker_authority(issue) - and isinstance(issue.get("number"), int) - ] - if routed: - number, issue = min(routed, key=lambda item: item[0]) - labels = {label["name"] for label in issue["labels"]} - desired_labels = { - label for label in labels if not label.startswith("status:") and not label.startswith("completion:") - } - desired_labels.add("status:ready") - desired_labels = replace_issue_kind( - desired_labels, - "kind:release-blocker", - f"routed blocker {repository}#{number}", - ) - if issue.get("state") != "open" or labels != desired_labels: - writer.request( - "PATCH", - f"/repos/{repository}/issues/{number}", - {"state": "open", "labels": sorted(desired_labels)}, - ) - routing.append({"action": "reused", "number": number, "repository": repository}) - continue - budget_reasons, root = _continuity_creation_budget(config, issues) - if budget_reasons: - if root is not None: - marker = f"" - body = str(root["body"]) - if marker not in body: - body = f"{body.rstrip()}\n\n{consolidated_blocker_finding(blocker, selection)}" - writer.request( - "PATCH", - f"/repos/{repository}/issues/{root['number']}", - {"body": body}, - ) - root["body"] = body - routing.append( - { - "action": "consolidated", - "budget_reasons": budget_reasons, - "number": int(root["number"]), - "repository": repository, - } - ) - else: - routing.append( - { - "action": "retained-private-audit", - "budget_reasons": budget_reasons, - "repository": repository, - } - ) - continue - component = blocker["component"] - writer.request( - "POST", - f"/repos/{repository}/issues", - { - "title": f"Release blocker: prepare {component} source for GitHub continuity", - "body": blocker_body(config, blocker, selection), - "labels": list(ROUTED_BLOCKER_LABELS), - }, - ) - routing.append({"action": "created", "repository": repository}) - state["routing"] = sorted(routing, key=lambda record: (record["repository"], record["action"])) - state_path.write_bytes(canonical_json(state)) - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - commands = parser.add_subparsers(dest="command", required=True) - - plan = commands.add_parser("plan") - plan.add_argument("config", type=Path) - plan.add_argument("release_plan", type=Path) - plan.add_argument("expected_commits", type=Path) - plan.add_argument("state", type=Path) - plan.add_argument("--github-output", type=Path) - plan.add_argument("--expected-plan-tag") - - callback = commands.add_parser("dispatch-accepted") - callback.add_argument("release_plan", type=Path) - callback.add_argument("--github-output", type=Path) - - advance = commands.add_parser("advance") - advance.add_argument("config", type=Path) - advance.add_argument("release_plan", type=Path) - advance.add_argument("state", type=Path) - advance.add_argument("--qualification", type=Path) - advance.add_argument("--github-output", type=Path) - - blockers = commands.add_parser("route-blockers") - blockers.add_argument("config", type=Path) - blockers.add_argument("state", type=Path) - - source = commands.add_parser("resolution-source") - source.add_argument("--expected-run-id", type=int, required=True) - source.add_argument("--expected-run-attempt", type=int, required=True) - source.add_argument("--github-output", type=Path) - - resolution = commands.add_parser("record-resolution") - resolution.add_argument("selection", type=Path) - resolution.add_argument("--source-sha", required=True) - resolution.add_argument("--run-id", type=int, required=True) - resolution.add_argument("--run-attempt", type=int, required=True) - resolution.add_argument("--remote", default="origin") - - args = parser.parse_args() - try: - if args.command == "plan": - plan_command( - args.config, - args.release_plan, - args.expected_commits, - args.state, - args.github_output, - args.expected_plan_tag, - ) - elif args.command == "dispatch-accepted": - dispatch_accepted_continuity(args.release_plan, args.github_output) - elif args.command == "advance": - advance_command( - args.config, - args.release_plan, - args.state, - args.qualification, - args.github_output, - ) - elif args.command == "route-blockers": - route_blockers(args.config, args.state) - elif args.command == "resolution-source": - result = resolution_source_command( - args.expected_run_id, - args.expected_run_attempt, - args.github_output, - ) - print(json.dumps(result, sort_keys=True)) - else: - result = record_continuity_resolution( - Path.cwd(), - args.selection, - source_sha=args.source_sha, - run_id=args.run_id, - run_attempt=args.run_attempt, - remote=args.remote, - ) - print(json.dumps(result, sort_keys=True)) - except PlanBlocked as error: - print(f"beta continuity planning blocked: {error}", file=sys.stderr) - return 2 - except (CandidateError, ContinuityError) as error: - print(f"beta continuity error: {error}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/component_release_recovery.py b/scripts/component_release_recovery.py deleted file mode 100644 index 4c4563a..0000000 --- a/scripts/component_release_recovery.py +++ /dev/null @@ -1,3420 +0,0 @@ -#!/usr/bin/env python3 -"""Discover and classify one repository's work for an immutable release plan.""" - -from __future__ import annotations - -import argparse -import contextlib -import datetime as dt -import email.utils -import errno -import hashlib -import http.client -import io -import json -import os -import re -import shutil -import ssl -import subprocess -import sys -import tarfile -import tempfile -import time -import urllib.error -import urllib.parse -import urllib.request -import zipfile -from collections.abc import Callable, Mapping -from dataclasses import dataclass -from pathlib import Path -from typing import Any - -# GitHub Actions invokes this file directly from the repository root. In that -# mode Python adds scripts/, rather than the repository root, to sys.path. -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.packagist_metadata import PackagistMetadataError, exact_package_version -from scripts.recovery_workflow_authority import ( - RecoveryWorkflowAuthorityError, - load_qualified_authority, - verify_workflow_source, -) - -SCHEMA = "durable-workflow.release-plan/v2" -LEGACY_SCHEMA = "durable-workflow.release-plan/v1" -LEGACY_PLAN_DIGESTS = frozenset( - { - "0be354d5ea603170b6aef8ae0d9861886c4ccc0f75e6acb763239b30dd5d8ba3", - "295a3f654716ea8cd8dc693c1cd15a4b487737e5f01184bad7363fbde6717c40", - "486d9ef7c5a7f4443a89566cab33d7f2bccc518254ab6698d918a431d6a1c9ce", - "498804a2c7fd5b0e34f93ef080bea3073bc98e420e8bf84a98ca4cdb94729973", - "7bd737c92f139eec33026bc88a6491dc635d819a87a61c985e14e06aca645582", - "80e88698fa37b6d738d111dd2be3e3c145607973f8147c54cc25e5d91d415b17", - "9c0a5879652a2d5f4806a9167399687328c1764fa10dbc8d76215b43ac83b9d6", - "db90616c98f305c61d7eb2fb9ed03cc28f06963e9ca020c8ef6d7c6a8557f7bc", - "e1fc6e20c9d2ded0b5e7ac4d6be75ba861d31fc4b2db651dc0272dca623f2c7f", - } -) -PREPARATION_SCHEMA = "durable-workflow.release-preparation/v1" -STATE_SCHEMA = "durable-workflow.component-release-recovery/v1" -CONTROL_REPOSITORY = "durable-workflow/.github" -PLAN_TAG_PREFIX = "release-plan/" -COMPLETION_TAG_PREFIX = "release-candidate/" -FAILURE_TAG_PREFIX = "release-plan-failure/" -CONTINUITY_TAG_PREFIX = "beta-continuity/" -CONTINUITY_EVIDENCE_SCHEMA = "durable-workflow.beta-continuity.evidence/v1" -CONTINUITY_SUPERSESSION_REASON = "missing-post-acceptance-publication-trigger" -CONTINUITY_RESOLUTION_TAG_PREFIX = "release-plan-continuity-resolution/" -CONTINUITY_RESOLUTION_SCHEMA = "durable-workflow.release-plan-continuity-resolution/v2" -CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW = ".github/workflows/beta-candidate.yml" -CONTINUITY_RESOLUTION_QUALIFICATION_EVENT = "push" -CONTINUITY_RESOLUTION_QUALIFICATION_BRANCH = "main" -SUPERSESSION_ENVIRONMENT = "release-plan-supersession" -SUPERSESSION_WORKFLOW = ".github/workflows/release-plan-supersession.yml" -SUPERSESSION_REASON = "published-version-source-conflict" -SOURCE_MANIFEST_REASON = "source-manifest-version-conflict" -OCCUPIED_SOURCE_MANIFEST_REASON = "occupied-source-manifest-version-conflict" -SUPERSESSION_API_VERSION = "2026-03-10" -FOUNDATION_TAG = "beta-candidate/beta-continuity-foundation" -FOUNDATION_COMMIT = "4995052410bd4301c5796ffba54e0b6d2f490ed1" -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -PLAN_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,55}$") -VERSION_PATTERN = re.compile( - r"^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)" - r"(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)" - r"(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?" - r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$" -) -ALPHA_VERSION_PATTERN = re.compile(r"^2\.0\.0-alpha\.[1-9][0-9]*$") -BETA_VERSION_PATTERN = re.compile(r"^2\.0\.0-beta\.[1-9][0-9]*$") -RC_VERSION_PATTERN = re.compile(r"^2\.0\.0-rc\.[1-9][0-9]*$") -MARKDOWN_MEDIA_TYPE = "text/markdown" -GITHUB_READ_MAX_ATTEMPTS = 5 -GITHUB_READ_RETRY_BASE_SECONDS = 2.0 -GITHUB_READ_RETRY_MAX_SECONDS = 120.0 -GITHUB_READ_REQUEST_TIMEOUT_SECONDS = 30.0 -GITHUB_READ_DEADLINE_SECONDS = 600.0 -IMPLICIT_AUTHORITY_MAX_ATTEMPTS = 3 -INFRASTRUCTURE_EXIT_CODE = 75 - -SOURCE_CHANGELOGS = {"workflow", "waterline", "sdk-php", "sdk-python"} -SOURCE_MANIFESTS = { - "sdk-python": {"path": "pyproject.toml", "package": "durable-workflow"}, - "sdk-rust": {"path": "Cargo.toml", "package": "durable-workflow"}, -} -SUPERSESSION_ENVIRONMENT_URL = ( - f"https://github.com/{CONTROL_REPOSITORY}/deployments/activity_log?" - f"environments_filter={SUPERSESSION_ENVIRONMENT}" -) -SUPERSESSION_ENVIRONMENT_API_URL = ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{SUPERSESSION_ENVIRONMENT}" -) - - -@dataclass(frozen=True) -class Component: - repository: str - default_branch: str - distribution: str - package: str - dependencies: tuple[str, ...] - release_workflow: str | None - release_tag_input: str | None - - -COMPONENTS = { - "workflow": Component("durable-workflow/workflow", "v2", "composer", "durable-workflow/workflow", (), None, None), - "sdk-php": Component("durable-workflow/sdk-php", "main", "composer", "durable-workflow/sdk", (), None, None), - "waterline": Component( - "durable-workflow/waterline", - "v2", - "composer", - "durable-workflow/waterline", - ("workflow", "sdk-php"), - None, - None, - ), - "server": Component( - "durable-workflow/server", - "main", - "oci", - "docker.io/durableworkflow/server", - ("workflow",), - "release.yml", - "tag", - ), - "cli": Component( - "durable-workflow/cli", "main", "github-release", "durable-workflow/cli", ("server",), "release.yml", "tag" - ), - "sdk-python": Component( - "durable-workflow/sdk-python", - "main", - "pypi", - "durable-workflow", - ("server",), - "publish.yml", - "release_tag", - ), - "sdk-rust": Component( - "durable-workflow/sdk-rust", - "main", - "crates.io", - "durable-workflow", - ("server",), - "release.yml", - "release_tag", - ), -} - -CLI_ASSETS = { - "dw.phar", - "dw-linux-x86_64", - "dw-linux-aarch64", - "dw-macos-aarch64", - "dw-windows-x86_64.exe", - "dw.rb", - "install.sh", - "install.ps1", - "verify-release.sh", - "SHA256SUMS", -} - - -class RecoveryError(RuntimeError): - """A release plan cannot safely advance.""" - - def __init__(self, message: str, phase: str = "preflight") -> None: - super().__init__(message) - self.phase = phase - - -class NotFound(RecoveryError): - """A public API resource is absent.""" - - -class PublicInfrastructureError(RuntimeError): - """A bounded set of transient GitHub public-read attempts was exhausted.""" - - def __init__( - self, - endpoint_class: str, - attempts: int, - *, - reason: str, - failure: str | None = None, - ) -> None: - self.evidence: dict[str, str | int] = { - "classification": "github-read-transient", - "endpoint_class": endpoint_class, - "attempts": attempts, - "reason": reason, - } - if failure is not None: - self.evidence["failure"] = failure - evidence = [ - f"{key}={value}" - for key, value in self.evidence.items() - if key != "failure" - ] - if failure is not None: - evidence.append(failure) - super().__init__(f"GitHub public read transient failure exhausted ({', '.join(evidence)})") - - -class _TransientGitHubRead(RuntimeError): - """One GitHub public-read attempt encountered retryable infrastructure.""" - - def __init__(self, evidence: str, headers: Mapping[str, str] | None = None) -> None: - self.evidence = evidence - self.headers = headers or {} - super().__init__(evidence) - - -class _GitHubCliResponse(io.BytesIO): - """A response-shaped wrapper around one GitHub CLI API result.""" - - def __init__(self, body: bytes, headers: Mapping[str, str]) -> None: - super().__init__(body) - self.headers = headers - - -def canonical_json(value: Any) -> bytes: - return (json.dumps(value, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - - -class PublicClient: - def __init__( - self, - token: str | None = None, - *, - max_attempts: int = GITHUB_READ_MAX_ATTEMPTS, - retry_base_seconds: float = GITHUB_READ_RETRY_BASE_SECONDS, - retry_max_seconds: float = GITHUB_READ_RETRY_MAX_SECONDS, - request_timeout_seconds: float = GITHUB_READ_REQUEST_TIMEOUT_SECONDS, - deadline_seconds: float = GITHUB_READ_DEADLINE_SECONDS, - sleep: Callable[[float], None] = time.sleep, - now: Callable[[], float] = time.time, - monotonic: Callable[[], float] = time.monotonic, - ) -> None: - if ( - max_attempts < 1 - or retry_base_seconds < 0 - or retry_max_seconds < retry_base_seconds - or request_timeout_seconds <= 0 - or deadline_seconds <= 0 - ): - raise ValueError("invalid GitHub public-read retry configuration") - self.token = token - self.max_attempts = max_attempts - self.retry_base_seconds = retry_base_seconds - self.retry_max_seconds = retry_max_seconds - self.request_timeout_seconds = request_timeout_seconds - self.sleep = sleep - self.now = now - self.monotonic = monotonic - self.deadline = monotonic() + deadline_seconds - - @staticmethod - def _github_endpoint_class(url: str) -> str | None: - parsed = urllib.parse.urlsplit(url) - host = (parsed.hostname or "").lower() - if host == "api.github.com": - path = parsed.path - endpoint_classes = ( - ("/releases", "releases-api"), - ("/git/", "git-api"), - ("/contents/", "contents-api"), - ("/commits/", "commits-api"), - ("/actions/", "actions-api"), - ("/environments/", "environments-api"), - ) - for marker, endpoint_class in endpoint_classes: - if marker in path: - return endpoint_class - if path.startswith("/users/"): - return "users-api" - return "repositories-api" - if host == "github.com" or host.endswith(".github.com") or host.endswith(".githubusercontent.com"): - return "github-download" - return None - - @staticmethod - def _error_detail(error: urllib.error.HTTPError) -> str: - try: - return error.read(1024).decode(errors="replace") - except OSError: - return "response body unavailable" - - @staticmethod - def _header_value(headers: Mapping[str, str], name: str) -> str | None: - normalized_name = name.casefold() - return next( - (value for header_name, value in headers.items() if header_name.casefold() == normalized_name), - None, - ) - - @classmethod - def _is_rate_limited(cls, error: urllib.error.HTTPError, detail: str) -> bool: - headers = error.headers or {} - return error.code == 429 or ( - error.code == 403 - and ( - cls._header_value(headers, "Retry-After") is not None - or cls._header_value(headers, "X-RateLimit-Remaining") == "0" - or "rate limit" in detail.lower() - ) - ) - - @staticmethod - def _transport_name(error: BaseException) -> str | None: - reason = error.reason if isinstance(error, urllib.error.URLError) else error - if isinstance(reason, ssl.SSLCertVerificationError): - return "tls-certificate-verification" - if isinstance( - reason, - ConnectionError | TimeoutError | http.client.IncompleteRead | http.client.RemoteDisconnected, - ): - return type(reason).__name__ - if isinstance(reason, OSError) and reason.errno in { - errno.ECONNABORTED, - errno.ECONNRESET, - errno.EPIPE, - errno.ETIMEDOUT, - }: - return type(reason).__name__ - return None - - def _server_retry_delay(self, headers: Mapping[str, str]) -> float | None: - delays: list[float] = [] - retry_after = self._header_value(headers, "Retry-After") - if retry_after: - try: - delays.append(float(retry_after)) - except ValueError: - try: - retry_at = email.utils.parsedate_to_datetime(retry_after) - except (TypeError, ValueError): - pass - else: - if retry_at.tzinfo is None: - retry_at = retry_at.replace(tzinfo=dt.UTC) - delays.append(retry_at.timestamp() - self.now()) - rate_limit_reset = self._header_value(headers, "X-RateLimit-Reset") - if rate_limit_reset: - with contextlib.suppress(ValueError): - delays.append(float(rate_limit_reset) - self.now()) - return max((delay for delay in delays if delay > 0), default=None) - - def _retry_delay(self, attempt: int, failure: _TransientGitHubRead) -> float: - backoff = min(self.retry_base_seconds * (2 ** (attempt - 1)), self.retry_max_seconds) - return max(backoff, self._server_retry_delay(failure.headers) or 0) - - def _remaining_time(self) -> float: - return self.deadline - self.monotonic() - - @staticmethod - def _parse_github_cli_response(output: bytes) -> tuple[int | None, dict[str, str], bytes]: - separator = b"\r\n\r\n" if b"\r\n\r\n" in output else b"\n\n" - head, found, body = output.partition(separator) - lines = head.replace(b"\r\n", b"\n").splitlines() - status_match = re.fullmatch(rb"HTTP/\S+ ([0-9]{3})(?: .*)?", lines[0]) if lines else None - if not found or status_match is None: - return None, {}, output - headers: dict[str, str] = {} - for line in lines[1:]: - name, present, value = line.partition(b":") - if present: - normalized = name.decode(errors="replace").strip().casefold() - headers[normalized] = value.decode(errors="replace").strip() - return int(status_match.group(1)), headers, body - - @staticmethod - def _github_cli_transport_failure(stderr: bytes) -> str | None: - detail = stderr.decode(errors="replace").lower() - if any(marker in detail for marker in ("certificate", "x509:", "tls: failed to verify")): - return "tls-certificate-verification" - transport_markers = ( - "connection refused", - "connection reset", - "connection was reset", - "i/o timeout", - "network is unreachable", - "no such host", - "temporary failure in name resolution", - "tls handshake timeout", - "unexpected eof", - ) - if any(marker in detail for marker in transport_markers): - return "github-cli-network" - return None - - def _github_cli_request( - self, - url: str, - headers: Mapping[str, str], - timeout: float, - ) -> _GitHubCliResponse: - if not self.token: - raise RecoveryError("GitHub CLI API transport requires GITHUB_TOKEN or GH_TOKEN") - parsed = urllib.parse.urlsplit(url) - if parsed.scheme != "https" or parsed.hostname != "api.github.com": - raise RecoveryError(f"GitHub CLI API transport rejected non-API URL: {url}") - endpoint = parsed.path.lstrip("/") - if parsed.query: - endpoint = f"{endpoint}?{parsed.query}" - command = ["gh", "api", "--hostname", "github.com", "--include", "--method", "GET"] - for name, value in headers.items(): - if name.lower() != "authorization": - command.extend(("--header", f"{name}: {value}")) - command.append(endpoint) - environment = os.environ.copy() - environment.update( - { - "GH_PROMPT_DISABLED": "1", - "GH_TOKEN": self.token, - "NO_COLOR": "1", - } - ) - try: - process = subprocess.run( - command, - check=False, - capture_output=True, - env=environment, - timeout=timeout, - ) - except FileNotFoundError as error: - raise RecoveryError("GitHub Actions-supported gh API transport is unavailable") from error - except subprocess.TimeoutExpired as error: - raise _TransientGitHubRead("transport=github-cli-timeout") from error - - status, response_headers, body = self._parse_github_cli_response(process.stdout) - if process.returncode != 0: - if status is None: - status_match = re.search(rb"\(HTTP ([0-9]{3})\)", process.stderr) - status = int(status_match.group(1)) if status_match else None - if status is not None: - raise urllib.error.HTTPError( - url, - status, - process.stderr.decode(errors="replace").strip(), - response_headers, - io.BytesIO(body), - ) - if transport := self._github_cli_transport_failure(process.stderr): - raise _TransientGitHubRead(f"transport={transport}") - detail = process.stderr.decode(errors="replace").strip() or "unknown GitHub CLI failure" - raise RecoveryError(f"GitHub CLI API request failed for {url}: {detail[:512]}") - if status is None or not 200 <= status <= 299: - raise RecoveryError(f"GitHub CLI API response was malformed for {url}") - return _GitHubCliResponse(body, response_headers) - - def _run( - self, - url: str, - operation: Callable[[urllib.response.addinfourl], Any], - *, - headers: dict[str, str] | None, - accept: str | None, - ) -> Any: - endpoint_class = self._github_endpoint_class(url) - attempt_limit = self.max_attempts if endpoint_class is not None else 1 - request_headers = {"User-Agent": "durable-workflow-release-recovery/1", **(headers or {})} - if accept: - request_headers["Accept"] = accept - if self.token and urllib.parse.urlsplit(url).hostname == "api.github.com": - request_headers["Authorization"] = f"Bearer {self.token}" - request_headers.setdefault("X-GitHub-Api-Version", "2022-11-28") - - for attempt in range(1, attempt_limit + 1): - if endpoint_class is not None and self._remaining_time() <= 0: - raise PublicInfrastructureError(endpoint_class, attempt - 1, reason="workflow-deadline") - timeout = ( - min(self.request_timeout_seconds, self._remaining_time()) - if endpoint_class is not None - else 60 - ) - failure: _TransientGitHubRead | None = None - try: - if ( - urllib.parse.urlsplit(url).hostname == "api.github.com" - and self.token - and os.environ.get("GITHUB_ACTIONS") == "true" - ): - response = self._github_cli_request(url, request_headers, timeout) - else: - request = urllib.request.Request(url, headers=request_headers) - response = urllib.request.urlopen(request, timeout=timeout) - result = operation(response) - if endpoint_class is not None and self._remaining_time() <= 0: - raise PublicInfrastructureError(endpoint_class, attempt, reason="workflow-deadline") - return result - except urllib.error.HTTPError as error: - detail = self._error_detail(error) - if endpoint_class is not None and (500 <= error.code <= 599 or self._is_rate_limited(error, detail)): - failure = _TransientGitHubRead(f"status={error.code}", error.headers) - elif error.code == 404: - raise NotFound(f"public resource is absent: {url}") from error - else: - raise RecoveryError(f"public request failed ({error.code}) for {url}: {detail}") from error - except (urllib.error.URLError, ConnectionError, TimeoutError, http.client.IncompleteRead) as error: - transport = self._transport_name(error) - if endpoint_class is not None and transport is not None: - failure = _TransientGitHubRead(f"transport={transport}") - else: - reason = error.reason if isinstance(error, urllib.error.URLError) else error - raise RecoveryError(f"public request failed for {url}: {reason}") from error - except _TransientGitHubRead as error: - if endpoint_class is None: - raise RecoveryError(f"public request failed for {url}: {error}") from error - failure = error - - assert endpoint_class is not None and failure is not None - if attempt == attempt_limit: - raise PublicInfrastructureError( - endpoint_class, - attempt, - reason="retry-exhausted", - failure=failure.evidence, - ) - delay = self._retry_delay(attempt, failure) - if delay >= self._remaining_time(): - raise PublicInfrastructureError( - endpoint_class, - attempt, - reason="workflow-deadline", - failure=failure.evidence, - ) - print( - f"GitHub public read retry: endpoint_class={endpoint_class} " - f"attempt={attempt}/{attempt_limit} {failure.evidence} delay={delay:g}s", - file=sys.stderr, - ) - self.sleep(delay) - raise AssertionError("GitHub public-read retry loop ended unexpectedly") - - def request( - self, - url: str, - *, - headers: dict[str, str] | None = None, - accept: str | None = None, - ) -> urllib.response.addinfourl: - return self._run(url, lambda response: response, headers=headers, accept=accept) - - def json(self, url: str, *, headers: dict[str, str] | None = None, accept: str | None = None) -> Any: - def read_json(response: urllib.response.addinfourl) -> Any: - with response: - try: - return json.load(response) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise RecoveryError(f"public endpoint did not return valid JSON: {url}") from error - - return self._run(url, read_json, headers=headers, accept=accept) - - def bytes(self, url: str, *, headers: dict[str, str] | None = None, accept: str | None = None) -> bytes: - def read_bytes(response: urllib.response.addinfourl) -> bytes: - with response: - return response.read() - - return self._run(url, read_bytes, headers=headers, accept=accept) - - def download(self, url: str, path: Path, *, expected_sha256: str | None = None) -> dict[str, Any]: - def download_once(response: urllib.response.addinfourl) -> tuple[str, int]: - digest = hashlib.sha256() - size = 0 - with response, path.open("wb") as destination: - while chunk := response.read(1024 * 1024): - digest.update(chunk) - destination.write(chunk) - size += len(chunk) - return digest.hexdigest(), size - - actual, size = self._run(url, download_once, headers=None, accept=None) - if expected_sha256 and actual != expected_sha256.lower(): - raise RecoveryError(f"download digest mismatch for {url}: expected {expected_sha256}, got {actual}") - return {"url": url, "size": size, "sha256": actual} - - -def validate_plan(plan: Any) -> None: - if not isinstance(plan, dict): - raise RecoveryError("release plan must be a JSON object") - expected = {"schema", "plan", "channel", "foundation", "components", "beta_authorization"} - if set(plan) != expected or plan.get("schema") not in {LEGACY_SCHEMA, SCHEMA}: - raise RecoveryError("release plan does not satisfy a supported channel-aware contract") - if not isinstance(plan["plan"], str) or not PLAN_PATTERN.fullmatch(plan["plan"]): - raise RecoveryError("release plan has an invalid identity") - if plan["channel"] not in {"alpha", "beta", "rc"}: - raise RecoveryError("release plan channel must be alpha, beta, or rc") - foundation = plan["foundation"] - legacy_foundation = {"tag": FOUNDATION_TAG, "commit": FOUNDATION_COMMIT} - aggregate_rc_foundation = ( - plan["channel"] == "rc" - and isinstance(foundation, dict) - and set(foundation) == {"tag", "commit"} - and foundation.get("tag") == f"beta-candidate/rc-{plan['plan']}" - and COMMIT_PATTERN.fullmatch(str(foundation.get("commit", ""))) is not None - ) - if foundation != legacy_foundation and not aggregate_rc_foundation: - raise RecoveryError("release plan does not name its proven immutable candidate foundation") - components = plan["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise RecoveryError("release plan must contain the exact seven-component tuple") - for name, identity in components.items(): - if not isinstance(identity, dict) or set(identity) != {"version", "commit"}: - raise RecoveryError(f"components.{name} must contain only version and commit") - if not isinstance(identity["version"], str) or parse_semver(identity["version"]) is None: - raise RecoveryError(f"components.{name}.version is not exact SemVer") - if not isinstance(identity["commit"], str) or not COMMIT_PATTERN.fullmatch(identity["commit"]): - raise RecoveryError(f"components.{name}.commit is not a full source identity") - channel_pattern = { - "alpha": ALPHA_VERSION_PATTERN, - "beta": BETA_VERSION_PATTERN, - "rc": RC_VERSION_PATTERN, - }[plan["channel"]] - channel_components = COMPONENTS if plan["channel"] == "rc" else ("workflow", "waterline") - for name in channel_components: - if not channel_pattern.fullmatch(components[name]["version"]): - raise RecoveryError(f"{name} does not have an exact 2.0.0-{plan['channel']}.N identity") - authorization = plan["beta_authorization"] - if plan["channel"] == "alpha" and authorization is not None: - raise RecoveryError("alpha plans cannot claim beta authorization") - if aggregate_rc_foundation and authorization is not None: - raise RecoveryError("aggregate release-candidate plans cannot claim beta qualification") - if ( - plan["channel"] in {"beta", "rc"} - and not aggregate_rc_foundation - and ( - not isinstance(authorization, dict) - or set(authorization) != {"tag", "commit"} - or not re.fullmatch(r"beta-authorization/[a-z0-9][a-z0-9._-]{0,55}", str(authorization.get("tag", ""))) - or not COMMIT_PATTERN.fullmatch(str(authorization.get("commit", ""))) - ) - ): - raise RecoveryError("beta and release-candidate plans require immutable beta qualification") - if plan["schema"] == LEGACY_SCHEMA and manifest_digest(plan) not in LEGACY_PLAN_DIGESTS: - raise RecoveryError("legacy release plan is not an exact recorded historical contract") - - -def beta_authorization_matches_plan( - plan: dict[str, Any], - authorization: dict[str, str], - record: Any, -) -> bool: - if plan["channel"] == "beta": - return record == { - "schema": "durable-workflow.beta-authorization/v1", - "channel": "beta", - "candidate": plan["plan"], - "components": plan["components"], - } - if plan["channel"] != "rc" or not isinstance(record, dict): - return False - components = record.get("components") - candidate = record.get("candidate") - if ( - set(record) != {"schema", "channel", "candidate", "components"} - or record.get("schema") != "durable-workflow.beta-authorization/v1" - or record.get("channel") != "beta" - or not isinstance(candidate, str) - or authorization["tag"] != f"beta-authorization/{candidate}" - or not isinstance(components, dict) - or set(components) != set(COMPONENTS) - ): - return False - versions: set[str] = set() - for identity in components.values(): - if ( - not isinstance(identity, dict) - or set(identity) != {"version", "commit"} - or not BETA_VERSION_PATTERN.fullmatch(str(identity.get("version", ""))) - or not COMMIT_PATTERN.fullmatch(str(identity.get("commit", ""))) - ): - return False - versions.add(identity["version"]) - return len(versions) == 1 - - -def manifest_digest(value: Any) -> str: - return hashlib.sha256(canonical_json(value)).hexdigest() - - -def numeric_identifier_precedence(identifier: str) -> tuple[int, str]: - return len(identifier), identifier - - -def increment_numeric_identifier(identifier: str) -> str: - digits = list(identifier) - index = len(digits) - 1 - while index >= 0 and digits[index] == "9": - digits[index] = "0" - index -= 1 - if index < 0: - return "1" + "".join(digits) - digits[index] = chr(ord(digits[index]) + 1) - return "".join(digits) - - -@dataclass(frozen=True) -class SemVer: - value: str - core: tuple[str, str, str] - prerelease: tuple[str, ...] - build: tuple[str, ...] - - @property - def precedence( - self, - ) -> tuple[ - tuple[int, str], - tuple[int, str], - tuple[int, str], - int, - tuple[tuple[int, tuple[int, str] | str], ...], - ]: - major, minor, patch = (numeric_identifier_precedence(part) for part in self.core) - identifiers = tuple( - (0, numeric_identifier_precedence(part)) if part.isdigit() else (1, part) for part in self.prerelease - ) - return major, minor, patch, 0 if self.prerelease else 1, identifiers - - def immediately_precedes(self, successor: SemVer) -> bool: - if not self.prerelease: - return ( - not successor.prerelease - and successor.core[:2] == self.core[:2] - and successor.core[2] == increment_numeric_identifier(self.core[2]) - ) - if self.prerelease[-1].isdigit(): - expected_prerelease = self.prerelease[:-1] + ( - increment_numeric_identifier(self.prerelease[-1]), - ) - else: - expected_prerelease = self.prerelease + ("1",) - return successor.core == self.core and successor.prerelease == expected_prerelease - - -def parse_semver(version: str) -> SemVer | None: - if VERSION_PATTERN.fullmatch(version) is None: - return None - without_build, build_separator, build = version.partition("+") - core, prerelease_separator, prerelease = without_build.partition("-") - major, minor, patch = core.split(".") - return SemVer( - value=version, - core=(major, minor, patch), - prerelease=tuple(prerelease.split(".")) if prerelease_separator else (), - build=tuple(build.split(".")) if build_separator else (), - ) - - -def is_immediate_version_successor(previous: str, successor: str) -> bool: - previous_semver = parse_semver(previous) - successor_semver = parse_semver(successor) - return ( - previous_semver is not None - and successor_semver is not None - and previous_semver.immediately_precedes(successor_semver) - ) - - -def conflict_component_names(conflicts: Any) -> list[str]: - if not isinstance(conflicts, list): - raise RecoveryError("release plan failure conflicts must be a non-empty list", "plan-discovery") - names = [conflict.get("component") if isinstance(conflict, dict) else None for conflict in conflicts] - if ( - not names - or any(not isinstance(name, str) or name not in COMPONENTS for name in names) - or len(names) != len(set(names)) - ): - raise RecoveryError( - f"conflicting components must be unique names from {sorted(COMPONENTS)}", - "plan-discovery", - ) - expected_order = [name for name in COMPONENTS if name in names] - if names != expected_order: - raise RecoveryError("conflicting components must follow release-plan component order", "plan-discovery") - return names - - -def validate_successor_transition( - failed_plan: dict[str, Any], - successor_plan: dict[str, Any], - conflicts: list[Any], -) -> None: - validate_plan(failed_plan) - validate_plan(successor_plan) - conflict_names = conflict_component_names(conflicts) - if successor_plan["plan"] == failed_plan["plan"]: - raise RecoveryError("a superseding release plan must use a new plan identity", "plan-discovery") - if successor_plan["channel"] != failed_plan["channel"]: - raise RecoveryError("a superseding release plan cannot change the release channel", "plan-discovery") - if successor_plan["foundation"] != failed_plan["foundation"]: - raise RecoveryError("a superseding release plan cannot change the candidate foundation", "plan-discovery") - for name, identity in failed_plan["components"].items(): - successor_identity = successor_plan["components"][name] - if name not in conflict_names and successor_identity != identity: - raise RecoveryError( - f"superseding release plan changes unaffected component {name}", - "plan-discovery", - ) - for conflict in conflicts: - name = conflict["component"] - failed_identity = failed_plan["components"][name] - successor_identity = successor_plan["components"][name] - if successor_identity == failed_identity: - raise RecoveryError( - f"superseding release plan leaves conflict unresolved for {name}", - "plan-discovery", - ) - if conflict["reason"] == SUPERSESSION_REASON: - if successor_identity["commit"] != failed_identity["commit"]: - raise RecoveryError( - f"superseding release plan must retain {name}'s conflicting planned commit", - "plan-discovery", - ) - if not is_immediate_version_successor( - failed_identity["version"], - successor_identity["version"], - ): - raise RecoveryError( - f"superseding release plan must allocate {name}'s immediate next version", - "plan-discovery", - ) - elif conflict["reason"] == SOURCE_MANIFEST_REASON: - if successor_identity["version"] != failed_identity["version"]: - raise RecoveryError( - f"superseding release plan must retain {name}'s intended version", - "plan-discovery", - ) - if successor_identity["commit"] == failed_identity["commit"]: - raise RecoveryError( - f"superseding release plan must replace {name}'s incompatible source commit", - "plan-discovery", - ) - elif conflict["reason"] == OCCUPIED_SOURCE_MANIFEST_REASON: - if not is_immediate_version_successor( - failed_identity["version"], - successor_identity["version"], - ): - raise RecoveryError( - f"superseding release plan must allocate {name}'s immediate next version", - "plan-discovery", - ) - if successor_identity["commit"] == failed_identity["commit"]: - raise RecoveryError( - f"superseding release plan must replace {name}'s incompatible tagged source commit", - "plan-discovery", - ) - else: - raise RecoveryError( - f"release plan failure has an unsupported conflict reason for {name}", - "plan-discovery", - ) - - -def validate_environment_protection_evidence(protection: Any) -> None: - expected_keys = { - "custom_branch_policies", - "deployment_branch_policy", - "environment_id", - "environment_url", - "required_reviewer_rule_ids", - } - if not isinstance(protection, dict) or set(protection) != expected_keys: - raise RecoveryError( - "release plan failure environment protection evidence has an invalid shape", - "plan-discovery", - ) - reviewer_rule_ids = protection["required_reviewer_rule_ids"] - branch_policy = protection["deployment_branch_policy"] - custom_policies = protection["custom_branch_policies"] - if ( - type(protection["environment_id"]) is not int - or protection["environment_id"] < 1 - or protection["environment_url"] != SUPERSESSION_ENVIRONMENT_URL - or not isinstance(reviewer_rule_ids, list) - or not reviewer_rule_ids - or any(type(rule_id) is not int or rule_id < 1 for rule_id in reviewer_rule_ids) - or reviewer_rule_ids != sorted(set(reviewer_rule_ids)) - ): - raise RecoveryError( - "release plan failure lacks protected-environment reviewer evidence", - "plan-discovery", - ) - if branch_policy != {"custom_branch_policies": True, "protected_branches": False}: - raise RecoveryError( - "release plan failure lacks the protected environment custom-branch policy", - "plan-discovery", - ) - if ( - not isinstance(custom_policies, list) - or len(custom_policies) != 1 - or not isinstance(custom_policies[0], dict) - or set(custom_policies[0]) != {"id", "name"} - or type(custom_policies[0]["id"]) is not int - or custom_policies[0]["id"] < 1 - or custom_policies[0]["name"] != "main" - ): - raise RecoveryError( - "release plan failure lacks the protected environment custom main-branch policy", - "plan-discovery", - ) - - -def validate_environment_approval_evidence(approval: Any, authorization: dict[str, Any]) -> None: - expected_keys = {"comment", "environments", "run_attempt", "run_id", "state", "user"} - if not isinstance(approval, dict) or set(approval) != expected_keys: - raise RecoveryError( - "release plan failure environment approval evidence has an invalid shape", - "plan-discovery", - ) - environments = approval["environments"] - user = approval["user"] - protection = authorization["environment_protection"] - if ( - approval["state"] != "approved" - or not isinstance(approval["comment"], str) - or approval["run_id"] != authorization["run_id"] - or approval["run_attempt"] != authorization["run_attempt"] - or not isinstance(environments, list) - or len(environments) != 1 - or not isinstance(environments[0], dict) - or set(environments[0]) != {"html_url", "id", "name", "node_id", "url"} - ): - raise RecoveryError( - "release plan failure lacks an approved deployment bound to its workflow run", - "plan-discovery", - ) - environment = environments[0] - if ( - environment["id"] != protection["environment_id"] - or type(environment["id"]) is not int - or environment["name"] != SUPERSESSION_ENVIRONMENT - or environment["url"] != SUPERSESSION_ENVIRONMENT_API_URL - or environment["html_url"] != SUPERSESSION_ENVIRONMENT_URL - or not isinstance(environment["node_id"], str) - or not environment["node_id"] - ): - raise RecoveryError( - "release plan failure approval names the wrong protected environment", - "plan-discovery", - ) - if not isinstance(user, dict) or set(user) != {"html_url", "id", "login", "node_id", "url"}: - raise RecoveryError( - "release plan failure approving user evidence has an invalid shape", - "plan-discovery", - ) - login = user["login"] - if ( - type(user["id"]) is not int - or user["id"] < 1 - or not isinstance(user["node_id"], str) - or not user["node_id"] - or not isinstance(login, str) - or not re.fullmatch(r"[A-Za-z0-9-]{1,39}", login) - or user["url"] != f"https://api.github.com/users/{login}" - or user["html_url"] != f"https://github.com/{login}" - ): - raise RecoveryError( - "release plan failure lacks a durable approving user identity", - "plan-discovery", - ) - - -def validate_source_manifest_evidence( - evidence: Any, - component_name: str, - identity: dict[str, str], - *, - must_match_version: bool, -) -> None: - expected_keys = {"declared_version", "package", "path", "sha256", "source_commit", "url"} - specification = SOURCE_MANIFESTS.get(component_name) - if ( - specification is None - or not isinstance(evidence, dict) - or set(evidence) != expected_keys - or evidence["path"] != specification["path"] - or evidence["package"] != specification["package"] - or evidence["source_commit"] != identity["commit"] - or not re.fullmatch(r"[0-9a-f]{64}", str(evidence["sha256"])) - or not isinstance(evidence["declared_version"], str) - or not VERSION_PATTERN.fullmatch(evidence["declared_version"]) - or evidence["url"] - != ( - f"https://github.com/{COMPONENTS[component_name].repository}/blob/" - f"{identity['commit']}/{specification['path']}" - ) - ): - raise RecoveryError( - f"release plan failure has invalid source-manifest evidence for {component_name}", - "plan-discovery", - ) - version_matches = evidence["declared_version"] == identity["version"] - if version_matches is not must_match_version: - state = "match" if must_match_version else "conflict with" - raise RecoveryError( - f"release plan failure source manifest does not {state} {component_name} version allocation", - "plan-discovery", - ) - - -def publication_absence_locations( - component_name: str, - version: str, -) -> tuple[dict[str, str], dict[str, str]]: - component = COMPONENTS[component_name] - encoded_version = urllib.parse.quote(version, safe="") - release = { - "api_url": f"https://api.github.com/repos/{component.repository}/releases/tags/{encoded_version}", - "status": "absent", - "url": f"https://github.com/{component.repository}/releases/tag/{encoded_version}", - } - encoded_package = urllib.parse.quote(component.package, safe="") - if component.distribution == "pypi": - distribution = { - "api_url": f"https://pypi.org/pypi/{encoded_package}/{encoded_version}/json", - "kind": "pypi", - "status": "absent", - "url": f"https://pypi.org/project/{encoded_package}/{encoded_version}/", - } - elif component.distribution == "crates.io": - distribution = { - "api_url": f"https://crates.io/api/v1/crates/{encoded_package}/{encoded_version}", - "kind": "crates.io", - "status": "absent", - "url": f"https://crates.io/crates/{encoded_package}/{encoded_version}", - } - else: - raise RecoveryError( - f"{component_name} has no supported source-manifest distribution absence proof", - "plan-discovery", - ) - return release, distribution - - -def validate_occupied_source_manifest_evidence( - conflict: dict[str, Any], - component_name: str, - identity: dict[str, str], -) -> None: - component = COMPONENTS[component_name] - source_tag = conflict["source_tag"] - if ( - not isinstance(source_tag, dict) - or set(source_tag) != {"commit", "repository", "tag", "tag_object", "url"} - or source_tag["repository"] != component.repository - or source_tag["tag"] != identity["version"] - or source_tag["commit"] != identity["commit"] - or not COMMIT_PATTERN.fullmatch(str(source_tag["tag_object"])) - or source_tag["url"] != f"https://github.com/{component.repository}/tree/{identity['version']}" - ): - raise RecoveryError( - f"release plan failure does not prove {component_name}'s occupied planned source tag", - "plan-discovery", - ) - expected_release, expected_distribution = publication_absence_locations(component_name, identity["version"]) - if conflict["github_release"] != expected_release: - raise RecoveryError( - f"release plan failure lacks {component_name} GitHub Release absence evidence", - "plan-discovery", - ) - if conflict["distribution"] != expected_distribution: - raise RecoveryError( - f"release plan failure lacks {component_name} distribution absence evidence", - "plan-discovery", - ) - - -def canonical_cli_embedded_identity(version: str, commit: str) -> str: - return f"dw {version.lstrip('v')} (commit {commit[:12]})" - - -def require_distribution_identity( - distribution: dict[str, Any], - component_name: str, - version: str, - observed_commit: str, -) -> None: - component = COMPONENTS[component_name] - if distribution.get("kind") != component.distribution: - raise RecoveryError("public distribution evidence has the wrong kind", "plan-discovery") - if component.distribution == "composer": - matches = ( - distribution.get("source_reference") == observed_commit - and distribution.get("dist_reference") == observed_commit - ) - elif component.distribution == "github-release": - package_source = distribution.get("package_source") - matches = ( - isinstance(package_source, dict) - and set(package_source) == {"commit", "embedded_phar_identity"} - and package_source.get("commit") == observed_commit - and package_source.get("embedded_phar_identity") - == canonical_cli_embedded_identity(version, observed_commit) - ) - authority = distribution.get("build_attestation_authority") - exact_tag_authority = { - "mode": "exact-tag", - "ref": f"refs/tags/{version}", - "commit": observed_commit, - } - qualified_main_authority = { - "mode": "qualified-main-workflow", - "ref": "refs/heads/main", - "workflow": f"{component.repository}/.github/workflows/release.yml", - } - if distribution.get("build_attestations_verified") is not True or authority not in ( - exact_tag_authority, - qualified_main_authority, - ): - raise RecoveryError( - "public distribution evidence has an untrusted build attestation authority", - "plan-discovery", - ) - elif component.distribution == "pypi": - source = distribution.get("source_identity") - matches = isinstance(source, dict) and source.get("source_commit") == observed_commit - elif component.distribution == "crates.io": - matches = distribution.get("archive_vcs_commit") == observed_commit - else: - configs = distribution.get("configs") - matches = ( - isinstance(configs, list) - and bool(configs) - and all( - isinstance(config, dict) - and isinstance(config.get("labels"), dict) - and config["labels"].get("org.opencontainers.image.revision") == observed_commit - for config in configs - ) - ) - if not matches: - raise RecoveryError( - "public distribution evidence does not bind the observed source commit", - "plan-discovery", - ) - - -def validate_conflict_record( - conflict: Any, - failed_plan: dict[str, Any], - successor_plan: dict[str, Any], -) -> None: - if not isinstance(conflict, dict): - raise RecoveryError( - "release plan failure conflict evidence has an invalid shape", - "plan-discovery", - ) - component_name = conflict.get("component") - if component_name not in COMPONENTS: - raise RecoveryError( - "release plan failure names an unknown conflicting component", - "plan-discovery", - ) - identity = failed_plan["components"][component_name] - successor_identity = successor_plan["components"][component_name] - common_identity_matches = ( - conflict.get("version") == identity["version"] - and conflict.get("planned_commit") == identity["commit"] - ) - reason = conflict.get("reason") - if reason == SUPERSESSION_REASON: - expected_keys = { - "component", - "version", - "planned_commit", - "observed_commit", - "reason", - "github_release", - "distribution", - } - if ( - set(conflict) != expected_keys - or not common_identity_matches - or not COMMIT_PATTERN.fullmatch(str(conflict.get("observed_commit", ""))) - or conflict["observed_commit"] == identity["commit"] - ): - raise RecoveryError( - "release plan failure conflict does not prove a different public source identity", - "plan-discovery", - ) - release = conflict["github_release"] - if ( - not isinstance(release, dict) - or set(release) != {"id", "url"} - or type(release["id"]) is not int - or release["id"] < 1 - or not isinstance(release["url"], str) - or not release["url"].startswith( - f"https://github.com/{COMPONENTS[component_name].repository}/releases/" - ) - ): - raise RecoveryError( - "release plan failure lacks durable GitHub Release evidence", - "plan-discovery", - ) - distribution = conflict["distribution"] - if not isinstance(distribution, dict): - raise RecoveryError( - "release plan failure lacks matching distribution evidence", - "plan-discovery", - ) - require_distribution_identity( - distribution, - component_name, - conflict["version"], - conflict["observed_commit"], - ) - elif reason == SOURCE_MANIFEST_REASON: - expected_keys = { - "component", - "version", - "planned_commit", - "reason", - "source_manifest", - "successor_source_manifest", - } - if set(conflict) != expected_keys or not common_identity_matches: - raise RecoveryError( - "release plan failure manifest conflict evidence has an invalid shape", - "plan-discovery", - ) - validate_source_manifest_evidence( - conflict["source_manifest"], - component_name, - identity, - must_match_version=False, - ) - validate_source_manifest_evidence( - conflict["successor_source_manifest"], - component_name, - successor_identity, - must_match_version=True, - ) - elif reason == OCCUPIED_SOURCE_MANIFEST_REASON: - expected_keys = { - "component", - "version", - "planned_commit", - "reason", - "source_manifest", - "source_tag", - "github_release", - "distribution", - "successor_source_manifest", - } - if set(conflict) != expected_keys or not common_identity_matches: - raise RecoveryError( - "release plan failure occupied manifest conflict evidence has an invalid shape", - "plan-discovery", - ) - validate_source_manifest_evidence( - conflict["source_manifest"], - component_name, - identity, - must_match_version=False, - ) - validate_source_manifest_evidence( - conflict["successor_source_manifest"], - component_name, - successor_identity, - must_match_version=True, - ) - validate_occupied_source_manifest_evidence(conflict, component_name, identity) - else: - raise RecoveryError( - f"release plan failure has an unsupported conflict reason for {component_name}", - "plan-discovery", - ) - - -def validate_supersession_record( - record: Any, - failed_plan: dict[str, Any], - failed_plan_commit: str, - successor_plan: dict[str, Any], -) -> None: - expected = { - "schema", - "outcome", - "failed_plan", - "conflicts", - "successor_plan", - "authorization", - } - if not isinstance(record, dict) or set(record) != expected: - raise RecoveryError( - f"release plan failure record keys must be exactly {sorted(expected)}", - "plan-discovery", - ) - expected_failed = { - "tag": f"{PLAN_TAG_PREFIX}{failed_plan['plan']}", - "commit": failed_plan_commit, - "sha256": manifest_digest(failed_plan), - } - if record["schema"] != "durable-workflow.release-plan-failure/v1": - raise RecoveryError( - "release plan failure record has an unsupported schema", - "plan-discovery", - ) - if record["outcome"] != "terminal-failure" or record["failed_plan"] != expected_failed: - raise RecoveryError( - "release plan failure record does not terminate this exact immutable plan", - "plan-discovery", - ) - expected_successor = { - "tag": f"{PLAN_TAG_PREFIX}{successor_plan['plan']}", - "sha256": manifest_digest(successor_plan), - } - if record["successor_plan"] != expected_successor: - raise RecoveryError( - "release plan failure record names a different successor plan", - "plan-discovery", - ) - conflicts = record["conflicts"] - conflict_component_names(conflicts) - for conflict in conflicts: - validate_conflict_record(conflict, failed_plan, successor_plan) - validate_successor_transition(failed_plan, successor_plan, conflicts) - - authorization = record["authorization"] - authorization_keys = { - "actor", - "environment", - "environment_approval", - "environment_protection", - "repository", - "run_attempt", - "run_id", - "run_url", - "workflow_commit", - "workflow_ref", - } - if not isinstance(authorization, dict) or set(authorization) != authorization_keys: - raise RecoveryError( - "release plan failure authorization evidence has an invalid shape", - "plan-discovery", - ) - protection = authorization["environment_protection"] - validate_environment_protection_evidence(protection) - workflow_ref = f"{CONTROL_REPOSITORY}/{SUPERSESSION_WORKFLOW}@refs/heads/main" - if ( - authorization.get("repository") != CONTROL_REPOSITORY - or authorization.get("environment") != SUPERSESSION_ENVIRONMENT - or authorization.get("workflow_ref") != workflow_ref - or not COMMIT_PATTERN.fullmatch(str(authorization.get("workflow_commit", ""))) - or not re.fullmatch(r"[A-Za-z0-9-]{1,39}", str(authorization.get("actor", ""))) - or type(authorization.get("run_id")) is not int - or authorization["run_id"] < 1 - or type(authorization.get("run_attempt")) is not int - or authorization["run_attempt"] != 1 - or authorization.get("run_url") - != f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{authorization.get('run_id')}" - ): - raise RecoveryError( - "release plan failure was not authorized by the protected supersession workflow", - "plan-discovery", - ) - validate_environment_approval_evidence(authorization["environment_approval"], authorization) - - -def protected_environment_evidence( - client: PublicClient, -) -> tuple[dict[str, Any], set[tuple[int, str]]]: - encoded = urllib.parse.quote(SUPERSESSION_ENVIRONMENT, safe="") - environment = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - if not isinstance(environment, dict): - raise RecoveryError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} has invalid public evidence", - "plan-discovery", - ) - rules = environment.get("protection_rules") - if not isinstance(rules, list): - raise RecoveryError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} has no protection rules", - "plan-discovery", - ) - reviewer_rules = [ - rule - for rule in rules - if isinstance(rule, dict) and rule.get("type") == "required_reviewers" and rule.get("reviewers") - ] - rule_ids = sorted( - rule["id"] - for rule in reviewer_rules - if type(rule.get("id")) is int and rule["id"] > 0 - ) - if len(reviewer_rules) != 1 or len(rule_ids) != 1: - raise RecoveryError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} must contain one explicit reviewer policy", - "plan-discovery", - ) - reviewer_user_ids: list[int] = [] - required_reviewers: set[tuple[int, str]] = set() - for reviewer in reviewer_rules[0]["reviewers"]: - identity = reviewer.get("reviewer") if isinstance(reviewer, dict) else None - if ( - not isinstance(identity, dict) - or reviewer.get("type") != "User" - or type(identity.get("id")) is not int - or identity["id"] < 1 - or not isinstance(identity.get("login"), str) - or not re.fullmatch(r"[A-Za-z0-9-]{1,39}", identity["login"]) - ): - raise RecoveryError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} has an unverifiable required reviewer", - "plan-discovery", - ) - reviewer_user_ids.append(identity["id"]) - required_reviewers.add((identity["id"], identity["login"])) - reviewer_user_ids = sorted(set(reviewer_user_ids)) - prevent_self_review = reviewer_rules[0].get("prevent_self_review") - environment_id = environment.get("id") - if ( - not rule_ids - or not reviewer_user_ids - or type(prevent_self_review) is not bool - or type(environment_id) is not int - or environment_id < 1 - or environment.get("html_url") != SUPERSESSION_ENVIRONMENT_URL - or environment.get("deployment_branch_policy") - != {"custom_branch_policies": True, "protected_branches": False} - ): - raise RecoveryError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} lacks the required reviewer and branch policy", - "plan-discovery", - ) - policies = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}/" - "deployment-branch-policies?per_page=100", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - branch_policies = policies.get("branch_policies") if isinstance(policies, dict) else None - if ( - not isinstance(branch_policies, list) - or type(policies.get("total_count")) is not int - or policies["total_count"] != 1 - or len(branch_policies) != 1 - or not isinstance(branch_policies[0], dict) - or type(branch_policies[0].get("id")) is not int - or branch_policies[0]["id"] < 1 - or branch_policies[0].get("name") != "main" - or branch_policies[0].get("type", "branch") != "branch" - ): - raise RecoveryError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} must allow only the main branch", - "plan-discovery", - ) - evidence = { - "custom_branch_policies": [{"id": branch_policies[0]["id"], "name": "main"}], - "deployment_branch_policy": { - "custom_branch_policies": True, - "protected_branches": False, - }, - "environment_id": environment_id, - "environment_url": SUPERSESSION_ENVIRONMENT_URL, - "required_reviewer_rule_ids": rule_ids, - } - validate_environment_protection_evidence(evidence) - return evidence, required_reviewers - - -def protected_run_approval_evidence( - client: PublicClient, - authorization: dict[str, Any], - environment_protection: dict[str, Any], - required_reviewers: set[tuple[int, str]], -) -> dict[str, Any]: - run_id = authorization["run_id"] - run_attempt = authorization["run_attempt"] - run = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - actor = run.get("actor") if isinstance(run, dict) else None - repository = run.get("repository") if isinstance(run, dict) else None - if ( - not isinstance(actor, dict) - or actor.get("login") != authorization["actor"] - or not isinstance(repository, dict) - or repository.get("full_name") != CONTROL_REPOSITORY - or type(run.get("id")) is not int - or run["id"] != run_id - or type(run.get("run_attempt")) is not int - or run["run_attempt"] != run_attempt - or run.get("event") != "workflow_dispatch" - or run.get("path") not in {SUPERSESSION_WORKFLOW, f"{SUPERSESSION_WORKFLOW}@main"} - or run.get("head_branch") != "main" - or run.get("head_sha") != authorization["workflow_commit"] - or run.get("status") != "completed" - or run.get("conclusion") != "success" - or run.get("html_url") != authorization["run_url"] - ): - raise RecoveryError( - "protected supersession workflow run evidence does not match GitHub", - "plan-discovery", - ) - if run_attempt != 1: - raise RecoveryError( - "GitHub approval history cannot prove protected approval for a rerun attempt", - "plan-discovery", - ) - history = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}/approvals", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - if ( - not isinstance(history, list) - or len(history) != 1 - or not isinstance(history[0], dict) - or history[0].get("state") != "approved" - ): - raise RecoveryError( - "protected supersession run must contain exactly one approved review", - "plan-discovery", - ) - review = history[0] - environments = review.get("environments") - user = review.get("user") - if ( - not isinstance(review.get("comment"), str) - or not isinstance(environments, list) - or len(environments) != 1 - or not isinstance(environments[0], dict) - or not isinstance(user, dict) - ): - raise RecoveryError( - "protected supersession approval history is malformed", - "plan-discovery", - ) - environment = environments[0] - evidence = { - "comment": review["comment"], - "environments": [ - { - "html_url": environment.get("html_url"), - "id": environment.get("id"), - "name": environment.get("name"), - "node_id": environment.get("node_id"), - "url": environment.get("url"), - } - ], - "run_attempt": run_attempt, - "run_id": run_id, - "state": review["state"], - "user": { - "html_url": user.get("html_url"), - "id": user.get("id"), - "login": user.get("login"), - "node_id": user.get("node_id"), - "url": user.get("url"), - }, - } - validate_environment_approval_evidence( - evidence, - { - **authorization, - "environment_protection": environment_protection, - }, - ) - if (evidence["user"]["id"], evidence["user"]["login"]) not in required_reviewers: - raise RecoveryError( - "protected supersession approving user is not authorized by the current reviewer policy", - "plan-discovery", - ) - return evidence - - -def revalidate_supersession_authority(record: dict[str, Any], client: PublicClient) -> None: - authorization = record["authorization"] - protection, required_reviewers = protected_environment_evidence(client) - if protection != authorization["environment_protection"]: - raise RecoveryError( - "release plan failure protected environment policy no longer matches GitHub", - "plan-discovery", - ) - approval = protected_run_approval_evidence( - client, - authorization, - protection, - required_reviewers, - ) - if approval != authorization["environment_approval"]: - raise RecoveryError( - "release plan failure approved deployment evidence no longer matches GitHub", - "plan-discovery", - ) - - -def validate_release_preparation(preparation: Any, plan: dict[str, Any]) -> None: - if not isinstance(preparation, dict) or set(preparation) != { - "schema", - "release_plan", - "components", - }: - raise RecoveryError("release preparation has an invalid top-level shape", "plan-discovery") - if preparation["schema"] != PREPARATION_SCHEMA or preparation["release_plan"] != { - "tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "sha256": manifest_digest(plan), - }: - raise RecoveryError("release preparation names a different immutable plan", "plan-discovery") - components = preparation["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise RecoveryError("release preparation does not cover the exact component tuple", "plan-discovery") - release_dates: set[str] = set() - for name, entry in components.items(): - identity = plan["components"][name] - if not isinstance(entry, dict) or set(entry) != { - "version", - "source_commit", - "release_notes", - }: - raise RecoveryError(f"release preparation for {name} has an invalid shape", "plan-discovery") - if entry["version"] != identity["version"] or entry["source_commit"] != identity["commit"]: - raise RecoveryError( - f"release preparation for {name} names a different planned identity", - "plan-discovery", - ) - notes = entry["release_notes"] - if not isinstance(notes, dict) or set(notes) != { - "format", - "heading", - "markdown", - "release_date", - "sha256", - "source", - }: - raise RecoveryError(f"release preparation for {name} has invalid release notes", "plan-discovery") - release_date = notes["release_date"] - try: - parsed_date = dt.date.fromisoformat(release_date) - except (TypeError, ValueError) as error: - raise RecoveryError( - f"release preparation for {name} has an invalid release date", - "plan-discovery", - ) from error - heading = f"## [{identity['version']}] - {parsed_date.isoformat()}" - markdown = notes["markdown"] - if ( - notes["format"] != MARKDOWN_MEDIA_TYPE - or release_date != parsed_date.isoformat() - or notes["heading"] != heading - or not isinstance(markdown, str) - or not markdown.startswith(f"{heading}\n\n") - or not markdown.endswith("\n") - or notes["sha256"] != hashlib.sha256(markdown.encode()).hexdigest() - ): - raise RecoveryError( - f"release preparation for {name} has mismatched versioned note content", - "plan-discovery", - ) - source = notes["source"] - expected_kind = "changelog-unreleased" if name in SOURCE_CHANGELOGS else "source-commit-message" - expected_source_url = ( - f"https://github.com/{COMPONENTS[name].repository}/blob/{identity['commit']}/CHANGELOG.md" - if name in SOURCE_CHANGELOGS - else f"https://github.com/{COMPONENTS[name].repository}/commit/{identity['commit']}" - ) - if ( - not isinstance(source, dict) - or set(source) != {"kind", "sha256", "url"} - or source["kind"] != expected_kind - or not re.fullmatch(r"[0-9a-f]{64}", str(source["sha256"])) - or source["url"] != expected_source_url - ): - raise RecoveryError( - f"release preparation for {name} has invalid note-source evidence", - "plan-discovery", - ) - release_dates.add(release_date) - if len(release_dates) != 1: - raise RecoveryError("release preparation components do not share one release date", "plan-discovery") - - -def resolve_tag(client: PublicClient, repository: str, tag: str) -> str | None: - encoded = urllib.parse.quote(tag, safe="") - try: - ref = client.json(f"https://api.github.com/repos/{repository}/git/ref/tags/{encoded}") - except NotFound: - return None - target = ref.get("object", {}) - seen: set[str] = set() - while target.get("type") == "tag": - sha = target.get("sha") - if not isinstance(sha, str) or sha in seen: - raise RecoveryError(f"invalid annotated tag chain for {repository}@{tag}", "tag-preflight") - seen.add(sha) - target = client.json(f"https://api.github.com/repos/{repository}/git/tags/{sha}").get("object", {}) - if target.get("type") != "commit" or not COMMIT_PATTERN.fullmatch(str(target.get("sha", ""))): - raise RecoveryError(f"tag {repository}@{tag} does not resolve to a commit", "tag-preflight") - return str(target["sha"]) - - -def read_record(client: PublicClient, tag: str, commit: str, filename: str) -> Any: - if resolve_tag(client, CONTROL_REPOSITORY, tag) != commit: - raise RecoveryError(f"immutable record {tag} does not resolve to {commit}", "plan-discovery") - encoded_filename = urllib.parse.quote(filename, safe="/") - raw = client.bytes( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/contents/{encoded_filename}?ref={commit}", - accept="application/vnd.github.raw+json", - ) - try: - return json.loads(raw) - except json.JSONDecodeError as error: - raise RecoveryError(f"immutable record {tag}:{filename} is not valid JSON", "plan-discovery") from error - - -def read_plan_authority(client: PublicClient, tag: str, commit: str) -> tuple[dict[str, Any], dict[str, Any] | None]: - plan = read_record(client, tag, commit, "release-plan.json") - try: - validate_plan(plan) - except RecoveryError as error: - raise RecoveryError(str(error), "plan-discovery") from error - if tag != f"{PLAN_TAG_PREFIX}{plan['plan']}": - raise RecoveryError("release plan tag and document identity differ", "plan-discovery") - try: - preparation = read_record(client, tag, commit, "release-preparation.json") - except NotFound: - preparation = None - if preparation is not None: - validate_release_preparation(preparation, plan) - return plan, preparation - - -def validate_release_mirrors( - client: PublicClient, - tag: str, - release: Any, - plan: dict[str, Any], - preparation: dict[str, Any] | None, -) -> None: - if not isinstance(release, dict) or release.get("tag_name") != tag: - raise RecoveryError(f"release plan {tag} has invalid GitHub Release metadata", "plan-discovery") - if release.get("draft"): - raise RecoveryError(f"release plan {tag} is still a draft", "plan-discovery") - assets_value = release.get("assets") - if not isinstance(assets_value, list) or not all(isinstance(asset, dict) for asset in assets_value): - raise RecoveryError(f"release plan {tag} has malformed Release assets", "plan-discovery") - assets = {asset.get("name"): asset for asset in assets_value} - if len(assets) != len(assets_value): - raise RecoveryError(f"release plan {tag} has duplicate Release asset names", "plan-discovery") - records = [("release-plan.json", plan)] - if preparation is not None: - records.append(("release-preparation.json", preparation)) - for filename, value in records: - asset = assets.get(filename) - if not isinstance(asset, dict) or not isinstance(asset.get("browser_download_url"), str): - raise RecoveryError( - f"release plan {tag} lacks its durable {filename} mirror asset", - "plan-discovery", - ) - mirror = client.bytes(asset["browser_download_url"]) - if mirror != canonical_json(value): - raise RecoveryError( - f"release plan {tag} {filename} mirror differs from immutable Git authority", - "plan-discovery", - ) - if preparation is None and "release-preparation.json" in assets: - raise RecoveryError( - f"release plan {tag} release-preparation.json mirror lacks immutable Git authority", - "plan-discovery", - ) - - -def immutable_plan_recorded_at(client: PublicClient, commit: str) -> dt.datetime: - value = client.json(f"https://api.github.com/repos/{CONTROL_REPOSITORY}/git/commits/{commit}") - committer = value.get("committer") if isinstance(value, dict) else None - recorded_at = committer.get("date") if isinstance(committer, dict) else None - try: - parsed = dt.datetime.fromisoformat(str(recorded_at).replace("Z", "+00:00")) - except ValueError as error: - raise RecoveryError("release plan Git commit lacks an immutable recorded-at time", "plan-discovery") from error - if not isinstance(value, dict) or value.get("sha") != commit or parsed.tzinfo is None or parsed.utcoffset() is None: - raise RecoveryError("release plan Git commit has invalid immutable metadata", "plan-discovery") - return parsed.astimezone(dt.UTC) - - -def list_release_plan_tags(client: PublicClient) -> list[str]: - url = f"https://api.github.com/repos/{CONTROL_REPOSITORY}/git/matching-refs/tags/{PLAN_TAG_PREFIX}" - refs = client.json(url) - if not isinstance(refs, list): - raise RecoveryError("GitHub did not return the immutable release-plan tag registry", "plan-discovery") - tags: list[str] = [] - for ref in refs: - value = ref.get("ref") if isinstance(ref, dict) else None - tag = value.removeprefix("refs/tags/") if isinstance(value, str) else "" - if ( - value != f"refs/tags/{tag}" - or not tag.startswith(PLAN_TAG_PREFIX) - or not PLAN_PATTERN.fullmatch(tag.removeprefix(PLAN_TAG_PREFIX)) - ): - raise RecoveryError( - "GitHub returned a malformed immutable release-plan tag registry entry", - "plan-discovery", - ) - tags.append(tag) - if not tags: - raise RecoveryError("no public release plan is available", "plan-discovery") - if len(tags) != len(set(tags)): - raise RecoveryError("immutable release-plan tag registry contains duplicate authorities", "plan-discovery") - return tags - - -def list_continuity_resolution_tags(client: PublicClient, interrupted_plan: str) -> list[str]: - prefix = f"{CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted_plan}/" - url = f"https://api.github.com/repos/{CONTROL_REPOSITORY}/git/matching-refs/tags/{prefix}" - refs = client.json(url) - if not isinstance(refs, list): - raise RecoveryError( - "GitHub did not return the immutable continuity-resolution tag registry", - "plan-discovery", - ) - tags: list[str] = [] - for ref in refs: - value = ref.get("ref") if isinstance(ref, dict) else None - tag = value.removeprefix("refs/tags/") if isinstance(value, str) else "" - digest = tag.removeprefix(prefix) - if value != f"refs/tags/{tag}" or not tag.startswith(prefix) or not re.fullmatch(r"[0-9a-f]{64}", digest): - raise RecoveryError( - "GitHub returned a malformed immutable continuity-resolution tag registry entry", - "plan-discovery", - ) - tags.append(tag) - if len(tags) != len(set(tags)): - raise RecoveryError( - "immutable continuity-resolution tag registry contains duplicate authorities", - "plan-discovery", - ) - return tags - - -def validate_continuity_resolution_qualification( - qualification: Any, - client: PublicClient, -) -> dict[str, Any]: - expected = { - "repository": CONTROL_REPOSITORY, - "workflow": CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW, - "event": CONTINUITY_RESOLUTION_QUALIFICATION_EVENT, - "head_branch": CONTINUITY_RESOLUTION_QUALIFICATION_BRANCH, - "status": "completed", - "conclusion": "success", - } - if ( - not isinstance(qualification, dict) - or set(qualification) != {*expected, "head_sha", "run_id", "run_attempt"} - or any(qualification.get(field) != value for field, value in expected.items()) - or not COMMIT_PATTERN.fullmatch(str(qualification.get("head_sha", ""))) - or type(qualification.get("run_id")) is not int - or qualification["run_id"] < 1 - or type(qualification.get("run_attempt")) is not int - or qualification["run_attempt"] < 1 - ): - raise RecoveryError( - "continuity successor resolution has invalid Beta candidate qualification", - "plan-discovery", - ) - run_id = qualification["run_id"] - run_attempt = qualification["run_attempt"] - try: - run = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/" - f"{run_id}/attempts/{run_attempt}" - ) - except NotFound as error: - raise RecoveryError( - "continuity successor resolution Beta candidate qualification is absent", - "plan-discovery", - ) from error - if not isinstance(run, dict): - raise RecoveryError( - "continuity successor resolution Beta candidate qualification is absent", - "plan-discovery", - ) - repository = run.get("repository") - head_repository = run.get("head_repository") - if ( - type(run.get("id")) is not int - or run.get("id") != run_id - or type(run.get("run_attempt")) is not int - or run.get("run_attempt") != run_attempt - or not isinstance(repository, dict) - or repository.get("full_name") != CONTROL_REPOSITORY - or not isinstance(head_repository, dict) - or head_repository.get("full_name") != CONTROL_REPOSITORY - or run.get("path") - not in { - CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW, - f"{CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW}@main", - f"{CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW}@refs/heads/main", - } - or run.get("event") != CONTINUITY_RESOLUTION_QUALIFICATION_EVENT - or run.get("head_branch") != CONTINUITY_RESOLUTION_QUALIFICATION_BRANCH - ): - raise RecoveryError( - "continuity successor resolution qualification is from an untrusted workflow", - "plan-discovery", - ) - if run.get("head_sha") != qualification["head_sha"]: - raise RecoveryError( - "continuity successor resolution qualification is bound to another source revision", - "plan-discovery", - ) - if run.get("status") != "completed": - raise RecoveryError( - "continuity successor resolution Beta candidate qualification is pending", - "plan-discovery", - ) - if run.get("conclusion") == "cancelled": - raise RecoveryError( - "continuity successor resolution Beta candidate qualification was cancelled", - "plan-discovery", - ) - if run.get("conclusion") != "success": - raise RecoveryError( - "continuity successor resolution Beta candidate qualification failed", - "plan-discovery", - ) - return qualification - - -def completion_manifest( - plan: dict[str, Any], - commit: str, - preparation: dict[str, Any] | None, -) -> dict[str, Any]: - result = { - "schema": "durable-workflow.release-candidate/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "release_plan": { - "tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "commit": commit, - "sha256": manifest_digest(plan), - }, - "components": plan["components"], - } - if preparation is not None: - result["release_preparation_sha256"] = manifest_digest(preparation) - return result - - -def direct_plan_lifecycle( - client: PublicClient, - tag: str, - commit: str, - plan: dict[str, Any], - preparation: dict[str, Any] | None, -) -> tuple[str, str | dict[str, Any] | None]: - completion_tag = f"{COMPLETION_TAG_PREFIX}{plan['channel']}/{plan['plan']}" - failure_tag = f"{FAILURE_TAG_PREFIX}{plan['plan']}" - completion_commit = resolve_tag(client, CONTROL_REPOSITORY, completion_tag) - failure_commit = resolve_tag(client, CONTROL_REPOSITORY, failure_tag) - if completion_commit is not None and failure_commit is not None: - raise RecoveryError( - f"release plan {tag} has conflicting completion and terminal-failure records", - "plan-discovery", - ) - if completion_commit is not None: - completion = read_record(client, completion_tag, completion_commit, "release-candidate.json") - if completion != completion_manifest(plan, commit, preparation): - raise RecoveryError( - f"release plan {tag} has an invalid immutable completion record", - "plan-discovery", - ) - return "completed", None - if failure_commit is not None: - failure = read_record(client, failure_tag, failure_commit, "release-plan-failure.json") - successor = read_record(client, failure_tag, failure_commit, "successor-release-plan.json") - validate_plan(successor) - validate_supersession_record(failure, plan, commit, successor) - revalidate_supersession_authority(failure, client) - expected_successor = { - "tag": f"{PLAN_TAG_PREFIX}{successor['plan']}", - "sha256": manifest_digest(successor), - } - return "superseded", { - **expected_successor, - "plan": successor, - } - - interruption_tag = f"{CONTINUITY_TAG_PREFIX}{plan['plan']}/interrupted" - interruption_commit = resolve_tag(client, CONTROL_REPOSITORY, interruption_tag) - if interruption_commit is None: - return "actionable", None - evidence = read_record(client, interruption_tag, interruption_commit, "continuity-evidence.json") - interrupted_plan = read_record(client, interruption_tag, interruption_commit, "release-plan.json") - digest = manifest_digest(plan) - if ( - interrupted_plan != plan - or not isinstance(evidence, dict) - or evidence.get("schema") != CONTINUITY_EVIDENCE_SCHEMA - or evidence.get("phase") != "interrupted" - or evidence.get("outcome") != "intentionally-interrupted" - or evidence.get("release_plan") != {"tag": tag, "sha256": digest} - or evidence.get("plan_record") != {"tag": tag, "commit": commit, "sha256": digest} - ): - raise RecoveryError( - f"release plan {tag} has an invalid immutable interruption record", - "plan-discovery", - ) - return "interrupted", interruption_tag - - -def accepted_continuity_supersession( - client: PublicClient, - authority: dict[str, Any], -) -> dict[str, Any] | None: - plan = authority["plan"] - accepted_tag = f"{CONTINUITY_TAG_PREFIX}{plan['plan']}/accepted" - accepted_commit = resolve_tag(client, CONTROL_REPOSITORY, accepted_tag) - if accepted_commit is None: - return None - evidence = read_record(client, accepted_tag, accepted_commit, "continuity-evidence.json") - accepted_plan = read_record(client, accepted_tag, accepted_commit, "release-plan.json") - digest = manifest_digest(plan) - if ( - accepted_plan != plan - or not isinstance(evidence, dict) - or evidence.get("schema") != CONTINUITY_EVIDENCE_SCHEMA - or evidence.get("phase") != "accepted" - or evidence.get("outcome") != "accepted" - or evidence.get("release_plan") != {"tag": authority["tag"], "sha256": digest} - or evidence.get("candidate_identity") != {"components": plan["components"], "plan_sha256": digest} - ): - raise RecoveryError( - f"release plan {authority['tag']} has an invalid immutable continuity acceptance", - "plan-discovery", - ) - superseded = evidence.get("superseded_interruption") - if superseded is None: - return None - if ( - not isinstance(superseded, dict) - or set(superseded) != {"commit", "evidence_sha256", "plan_sha256", "reason", "tag"} - or superseded.get("reason") != CONTINUITY_SUPERSESSION_REASON - or not COMMIT_PATTERN.fullmatch(str(superseded.get("commit", ""))) - or not re.fullmatch(r"[0-9a-f]{64}", str(superseded.get("evidence_sha256", ""))) - or not re.fullmatch(r"[0-9a-f]{64}", str(superseded.get("plan_sha256", ""))) - or not str(superseded.get("tag", "")).startswith(CONTINUITY_TAG_PREFIX) - ): - raise RecoveryError( - f"release plan {authority['tag']} has an invalid superseded interruption identity", - "plan-discovery", - ) - return { - **superseded, - "continuity_claim": { - "plan": { - "tag": authority["tag"], - "commit": authority["commit"], - "sha256": digest, - }, - "acceptance": { - "tag": accepted_tag, - "commit": accepted_commit, - "sha256": manifest_digest(evidence), - }, - }, - } - - -def resolve_continuity_successor_fork( - client: PublicClient, - interrupted: dict[str, Any], - successors: list[dict[str, Any]], -) -> str: - resolution_tags = list_continuity_resolution_tags(client, interrupted["plan"]["plan"]) - if not resolution_tags: - raise RecoveryError( - f"release plan {interrupted['tag']} has multiple continuity successors", - "plan-discovery", - ) - if len(resolution_tags) != 1: - raise RecoveryError( - f"release plan {interrupted['tag']} has multiple continuity successor resolutions", - "plan-discovery", - ) - - resolution_tag = resolution_tags[0] - resolution_commit = resolve_tag(client, CONTROL_REPOSITORY, resolution_tag) - if resolution_commit is None: - raise RecoveryError( - f"continuity successor resolution {resolution_tag} is absent", - "plan-discovery", - ) - resolution = read_record( - client, - resolution_tag, - resolution_commit, - "continuity-successor-resolution.json", - ) - first_supersession = successors[0]["supersession"] - expected_interruption = { - "plan": { - "tag": interrupted["tag"], - "commit": interrupted["commit"], - "sha256": manifest_digest(interrupted["plan"]), - }, - "evidence": { - "tag": first_supersession["tag"], - "commit": first_supersession["commit"], - "sha256": first_supersession["evidence_sha256"], - }, - } - expected_claims = sorted( - (successor["supersession"]["continuity_claim"] for successor in successors), - key=lambda claim: claim["plan"]["tag"], - ) - selected = resolution.get("selected_successor") if isinstance(resolution, dict) else None - expected_tag = ( - f"{CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted['plan']['plan']}/{manifest_digest(resolution)}" - if isinstance(resolution, dict) - else "" - ) - if ( - not isinstance(resolution, dict) - or set(resolution) - != { - "interruption", - "qualification", - "schema", - "selected_successor", - "successor_claims", - } - or resolution.get("schema") != CONTINUITY_RESOLUTION_SCHEMA - or resolution.get("interruption") != expected_interruption - or resolution.get("successor_claims") != expected_claims - or selected not in [claim["plan"] for claim in expected_claims] - or resolution_tag != expected_tag - ): - raise RecoveryError( - f"release plan {interrupted['tag']} has an invalid immutable continuity successor resolution", - "plan-discovery", - ) - validate_continuity_resolution_qualification(resolution["qualification"], client) - return str(selected["tag"]) - - -def classify_plan_authorities(client: PublicClient) -> list[dict[str, Any]]: - authorities: list[dict[str, Any]] = [] - tags = list_release_plan_tags(client) - for tag in tags: - commit = resolve_tag(client, CONTROL_REPOSITORY, tag) - if commit is None: - raise RecoveryError(f"release plan tag {tag} is absent", "plan-discovery") - plan, preparation = read_plan_authority(client, tag, commit) - lifecycle, successor = direct_plan_lifecycle(client, tag, commit, plan, preparation) - authorities.append( - { - "tag": tag, - "commit": commit, - "recorded_at": immutable_plan_recorded_at(client, commit), - "plan": plan, - "preparation": preparation, - "lifecycle": lifecycle, - "successor": successor, - } - ) - - authorities.sort(key=lambda item: item["recorded_at"]) - if len({item["recorded_at"] for item in authorities}) != len(authorities): - raise RecoveryError( - "release plans have ambiguous immutable Git recorded-at authority", - "plan-discovery", - ) - by_tag = {item["tag"]: item for item in authorities} - continuity_successors: dict[str, list[dict[str, Any]]] = {} - for successor in authorities: - superseded = accepted_continuity_supersession(client, successor) - if superseded is None: - continue - interruption_tag = superseded["tag"] - matches = [ - item for item in authorities if item["lifecycle"] == "interrupted" and item["successor"] == interruption_tag - ] - if len(matches) != 1: - raise RecoveryError( - f"continuity successor {successor['tag']} names an unknown or ambiguous interruption", - "plan-discovery", - ) - interrupted = matches[0] - interruption_commit = resolve_tag(client, CONTROL_REPOSITORY, interruption_tag) - interruption_evidence = read_record( - client, - interruption_tag, - superseded["commit"], - "continuity-evidence.json", - ) - if ( - interruption_commit != superseded["commit"] - or manifest_digest(interruption_evidence) != superseded["evidence_sha256"] - or manifest_digest(interrupted["plan"]) != superseded["plan_sha256"] - or successor["recorded_at"] <= interrupted["recorded_at"] - ): - raise RecoveryError( - f"continuity successor {successor['tag']} has conflicting interruption authority", - "plan-discovery", - ) - continuity_successors.setdefault(interrupted["tag"], []).append( - {"tag": successor["tag"], "supersession": superseded} - ) - - for interrupted_tag, successors in continuity_successors.items(): - interrupted = by_tag[interrupted_tag] - interrupted["lifecycle"] = "superseded" - successor_tag = ( - successors[0]["tag"] - if len(successors) == 1 - else resolve_continuity_successor_fork(client, interrupted, successors) - ) - successor = by_tag[successor_tag] - interrupted["successor"] = { - "tag": successor_tag, - "sha256": manifest_digest(successor["plan"]), - "plan": successor["plan"], - } - - for authority in authorities: - successor_identity = authority["successor"] - if authority["lifecycle"] != "superseded" or successor_identity is None: - continue - if not isinstance(successor_identity, dict): - raise RecoveryError( - f"superseded release plan {authority['tag']} has a malformed successor identity", - "plan-discovery", - ) - successor_tag = successor_identity.get("tag") - successor = by_tag.get(successor_tag) - if successor is None: - if authority is authorities[-1]: - raise RecoveryError( - f"latest release plan {authority['tag']} is superseded but its successor is not recorded", - "plan-discovery", - ) - raise RecoveryError( - f"superseded release plan {authority['tag']} has an incomplete successor authority", - "plan-discovery", - ) - expected_successor_identity = { - "tag": successor["tag"], - "sha256": manifest_digest(successor["plan"]), - "plan": successor["plan"], - } - if successor_identity != expected_successor_identity: - raise RecoveryError( - f"superseded release plan {authority['tag']} has a conflicting successor identity", - "plan-discovery", - ) - if successor["recorded_at"] <= authority["recorded_at"]: - raise RecoveryError( - f"superseded release plan {authority['tag']} names a non-successor Git authority", - "plan-discovery", - ) - - return authorities - - -def semver_precedence( - version: str, -) -> tuple[ - tuple[int, str], - tuple[int, str], - tuple[int, str], - int, - tuple[tuple[int, tuple[int, str] | str], ...], -]: - parsed = parse_semver(version) - if parsed is None: - raise ValueError("version is not exact SemVer") - return parsed.precedence - - -def current_product_train_authorities( - authorities: list[dict[str, Any]], -) -> list[dict[str, Any]]: - """Select one maximal SemVer train after resolving validated supersession edges.""" - - for authority in authorities: - try: - validate_plan(authority.get("plan")) - except RecoveryError as error: - raise RecoveryError(str(error), "plan-discovery") from error - - def immutable_identity( - authority: dict[str, Any], - ) -> tuple[tuple[str, str], ...]: - return tuple( - ( - authority["plan"]["components"][name]["version"], - authority["plan"]["components"][name]["commit"], - ) - for name in COMPONENTS - ) - - version_precedence = { - authority["tag"]: { - name: semver_precedence(identity["version"]) - for name, identity in authority["plan"]["components"].items() - } - for authority in authorities - } - - def dominates(candidate: dict[str, Any], other: dict[str, Any]) -> bool: - return all( - version_precedence[candidate["tag"]][name] >= version_precedence[other["tag"]][name] - for name in COMPONENTS - ) and any( - version_precedence[candidate["tag"]][name] > version_precedence[other["tag"]][name] - for name in COMPONENTS - ) - - maximal = [ - authority - for authority in authorities - if not any( - other is not authority and dominates(other, authority) - for other in authorities - ) - ] - by_tag = {authority["tag"]: authority for authority in authorities} - maximal_tags = {authority["tag"] for authority in maximal} - resolved_predecessors: set[str] = set() - for authority in maximal: - successor_identity = authority.get("successor") - if authority.get("lifecycle") != "superseded" or not isinstance(successor_identity, dict): - continue - successor = by_tag.get(successor_identity.get("tag")) - if successor is None or successor["tag"] not in maximal_tags: - continue - expected_successor_identity = { - "tag": successor["tag"], - "sha256": manifest_digest(successor["plan"]), - "plan": successor["plan"], - } - if successor_identity == expected_successor_identity: - resolved_predecessors.add(authority["tag"]) - - unresolved_maximal = [ - authority - for authority in maximal - if authority["tag"] not in resolved_predecessors - ] - current_identities = { - immutable_identity(authority) - for authority in unresolved_maximal - } - if len(current_identities) != 1: - raise RecoveryError( - "release plan authority has conflicting current product trains", - "plan-discovery", - ) - selected_identity = next(iter(current_identities)) - return [ - authority - for authority in authorities - if immutable_identity(authority) == selected_identity - ] - - -def classify_implicit_plan_authority( - client: PublicClient, -) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]: - authorities = classify_plan_authorities(client) - current_train = current_product_train_authorities(authorities) - nonterminal_older = [ - item - for item in current_train[:-1] - if item["lifecycle"] in {"actionable", "interrupted"} - ] - if nonterminal_older: - raise RecoveryError( - f"release plan authority is ambiguous: {nonterminal_older[0]['tag']} remains " - f"{nonterminal_older[0]['lifecycle']} before {current_train[-1]['tag']}", - "plan-discovery", - ) - selected = current_train[-1] - if selected["lifecycle"] == "superseded": - selected = None - return selected, authorities - - -def implicit_plan_authority_converged( - client: PublicClient, - authority_snapshot: list[dict[str, Any]], -) -> bool: - _selected, current_snapshot = classify_implicit_plan_authority(client) - return current_snapshot == authority_snapshot - - -def select_implicit_plan_authority(client: PublicClient) -> dict[str, Any]: - for _attempt in range(IMPLICIT_AUTHORITY_MAX_ATTEMPTS): - selected, authority_snapshot = classify_implicit_plan_authority(client) - if implicit_plan_authority_converged(client, authority_snapshot): - if selected is None: - raise RecoveryError( - "no public release plan is available", - "plan-discovery", - ) - return {**selected, "authority_snapshot": authority_snapshot} - raise RecoveryError( - "release plan registry or lifecycle authority did not converge " - f"after {IMPLICIT_AUTHORITY_MAX_ATTEMPTS} attempts", - "plan-discovery", - ) - - -def revalidate_implicit_plan_authority( - client: PublicClient, - implicit_authority: dict[str, Any], -) -> None: - authority_snapshot = implicit_authority.get("authority_snapshot") - if not isinstance(authority_snapshot, list) or not implicit_plan_authority_converged( - client, - authority_snapshot, - ): - raise RecoveryError( - "implicit release plan authority changed during component preflight; " - "refusing a stale recovery action", - "plan-discovery", - ) - - -def require_completed_plan_verification( - authority: dict[str, Any], - action: str, -) -> None: - if authority.get("lifecycle") == "completed" and action == "publish": - raise RecoveryError( - f"release plan {authority['tag']} is completed; " - "refusing publication instead of idempotent verification", - "plan-discovery", - ) - - -def discover_plan( - client: PublicClient, requested_tag: str | None, component_name: str -) -> tuple[ - str, - str, - dict[str, Any], - dict[str, Any] | None, - dict[str, Any] | None, -]: - if component_name not in COMPONENTS: - raise RecoveryError(f"unknown release component: {component_name}", "plan-discovery") - implicit_authority = None - if requested_tag: - tag = requested_tag - if not tag.startswith(PLAN_TAG_PREFIX): - raise RecoveryError(f"release plan tag must start with {PLAN_TAG_PREFIX}", "plan-discovery") - try: - release = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/releases/tags/{urllib.parse.quote(tag, safe='')}" - ) - except NotFound as error: - raise RecoveryError(f"release plan {tag} has no durable GitHub Release", "plan-discovery") from error - commit = resolve_tag(client, CONTROL_REPOSITORY, tag) - if commit is None: - raise RecoveryError(f"release plan tag {tag} is absent", "plan-discovery") - plan, preparation = read_plan_authority(client, tag, commit) - else: - selected = select_implicit_plan_authority(client) - implicit_authority = selected - tag = selected["tag"] - commit = selected["commit"] - plan = selected["plan"] - preparation = selected["preparation"] - try: - release = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/releases/tags/{urllib.parse.quote(tag, safe='')}" - ) - except NotFound as error: - raise RecoveryError(f"release plan {tag} has no durable GitHub Release", "plan-discovery") from error - validate_release_mirrors(client, tag, release, plan, preparation) - if preparation is None: - try: - verify_component(client, component_name, plan["components"][component_name]) - except NotFound as error: - raise RecoveryError( - f"release plan {tag} lacks immutable release-preparation.json; " - "only completed legacy releases may recover without it", - "plan-discovery", - ) from error - return tag, commit, plan, preparation, implicit_authority - - -def load_recovery_workflow_authority( - client: PublicClient, -) -> tuple[dict[str, dict[str, str]], dict[str, Any]]: - identities = { - name: (component.repository, component.default_branch) - for name, component in COMPONENTS.items() - } - try: - return load_qualified_authority(client, identities) - except RecoveryWorkflowAuthorityError as error: - raise RecoveryError(str(error), "default-branch-preflight") from error - - -def verify_recovery_workflow_source(name: str, source: str, expected_sha256: str) -> str: - try: - return verify_workflow_source(name, source, expected_sha256) - except RecoveryWorkflowAuthorityError as error: - raise RecoveryError(str(error), "default-branch-preflight") from error - - -def select_publication_run( - release_tag: str, - release_commit: str, - runs: Any, -) -> dict[str, Any]: - if not VERSION_PATTERN.fullmatch(release_tag) or not COMMIT_PATTERN.fullmatch(release_commit): - raise RecoveryError("publication run selection requires an exact release identity", "publication") - if not isinstance(runs, list): - raise RecoveryError("publication run metadata must be a JSON array", "publication") - - exact_runs: list[dict[str, Any]] = [] - for run in runs: - if not isinstance(run, dict) or run.get("headBranch") != release_tag: - continue - if run.get("headSha") != release_commit: - raise RecoveryError( - f"publication run {run.get('databaseId')} for {release_tag} is bound to a different source commit", - "publication", - ) - if not isinstance(run.get("databaseId"), int) or not isinstance(run.get("status"), str): - raise RecoveryError("publication run metadata is incomplete", "publication") - exact_runs.append(run) - - selected = next((run for run in exact_runs if run["status"] != "completed"), None) - action = "wait" - if selected is None: - selected = next( - (run for run in exact_runs if run["status"] == "completed" and run.get("conclusion") == "success"), - None, - ) - action = "complete" - if selected is None: - selected = next((run for run in exact_runs if run["status"] == "completed"), None) - action = "rerun" - if selected is None: - return {"action": "dispatch", "run_id": None, "status": None, "conclusion": None} - return { - "action": action, - "run_id": selected["databaseId"], - "status": selected["status"], - "conclusion": selected.get("conclusion"), - } - - -def verify_plan_authority( - client: PublicClient, plan: dict[str, Any] -) -> tuple[dict[str, str], dict[str, dict[str, Any]]]: - foundation_identity = plan["foundation"] - if ( - foundation_identity["tag"] != FOUNDATION_TAG - and resolve_tag(client, CONTROL_REPOSITORY, foundation_identity["tag"]) - != foundation_identity["commit"] - ): - raise RecoveryError( - "aggregate candidate foundation tag does not match its pinned commit", - "plan-preflight", - ) - foundation = read_record( - client, - foundation_identity["tag"], - foundation_identity["commit"], - "candidate.json", - ) - if foundation_identity["tag"] == FOUNDATION_TAG: - if foundation.get("candidate") != "beta-continuity-foundation": - raise RecoveryError("immutable candidate foundation has an unexpected identity", "plan-preflight") - else: - expected_foundation = { - "schema": "durable-workflow.beta-candidate/v2", - "candidate": f"rc-{plan['plan']}", - "components": plan["components"], - } - if foundation != expected_foundation: - raise RecoveryError( - "aggregate release-candidate foundation names a different exact tuple", - "plan-preflight", - ) - verification = read_record( - client, - foundation_identity["tag"], - foundation_identity["commit"], - "verification.json", - ) - verification_components = verification.get("components") if isinstance(verification, dict) else None - if ( - not isinstance(verification, dict) - or verification.get("schema") != "durable-workflow.beta-candidate-verification/v2" - or verification.get("candidate") != foundation["candidate"] - or verification.get("manifest_sha256") != manifest_digest(foundation) - or verification.get("outcome") != "verified" - or not isinstance(verification_components, dict) - or set(verification_components) != set(COMPONENTS) - or any( - result.get("version") != plan["components"][name]["version"] - or result.get("commit") != plan["components"][name]["commit"] - or result.get("outcome") != "verified" - for name, result in verification_components.items() - if isinstance(result, dict) - ) - or any(not isinstance(result, dict) for result in verification_components.values()) - ): - raise RecoveryError( - "aggregate release-candidate foundation lacks exact verification evidence", - "plan-preflight", - ) - authority, authority_source = load_recovery_workflow_authority(client) - branches: dict[str, str] = {} - recovery_workflows: dict[str, dict[str, Any]] = {} - for name, component in COMPONENTS.items(): - repository = client.json(f"https://api.github.com/repos/{component.repository}") - actual = repository.get("default_branch") - if actual != component.default_branch: - raise RecoveryError( - f"{component.repository} default branch is {actual!r}; recovery requires {component.default_branch!r}", - "default-branch-preflight", - ) - branches[name] = str(actual) - expected = authority[name] - expected_path = expected["path"] - workflow = client.json( - f"https://api.github.com/repos/{component.repository}/actions/workflows/release-plan-recovery.yml" - ) - if workflow.get("path") != expected_path or workflow.get("state") != expected["state"]: - raise RecoveryError( - f"{component.repository} does not expose an active {expected_path} on its default branch", - "default-branch-preflight", - ) - source = client.bytes( - f"https://api.github.com/repos/{component.repository}/contents/{expected_path}" - f"?ref={component.default_branch}", - accept="application/vnd.github.raw+json", - ).decode("utf-8") - source_sha256 = verify_recovery_workflow_source(name, source, expected["sha256"]) - recovery_workflows[name] = { - "authority": authority_source, - "default_branch": component.default_branch, - "path": expected_path, - "sha256": source_sha256, - "state": workflow["state"], - "workflow_id": workflow.get("id"), - "url": workflow.get("html_url"), - } - authorization = plan["beta_authorization"] - if authorization is not None: - record = read_record(client, authorization["tag"], authorization["commit"], "beta-authorization.json") - if not beta_authorization_matches_plan(plan, authorization, record): - raise RecoveryError( - "beta qualification does not authorize this prerelease transition", "channel-authorization" - ) - return branches, recovery_workflows - - -def require_source_tag(client: PublicClient, name: str, identity: dict[str, str]) -> str: - component = COMPONENTS[name] - source = resolve_tag(client, component.repository, identity["version"]) - if source is None: - raise NotFound( - f"source tag {component.repository}@{identity['version']} is not present", - "source-tag", - ) - if source != identity["commit"]: - raise RecoveryError( - f"source tag {component.repository}@{identity['version']} points to {source}, not {identity['commit']}", - "source-tag", - ) - return source - - -def verify_github_release(client: PublicClient, name: str, version: str) -> dict[str, Any]: - component = COMPONENTS[name] - encoded = urllib.parse.quote(version, safe="") - try: - release = client.json(f"https://api.github.com/repos/{component.repository}/releases/tags/{encoded}") - except NotFound as error: - raise NotFound(f"GitHub Release {component.repository}@{version} is absent", "github-release") from error - if release.get("draft") or release.get("tag_name") != version: - raise RecoveryError(f"GitHub Release {component.repository}@{version} is not public", "github-release") - return {"id": release.get("id"), "url": release.get("html_url")} - - -def verify_composer(client: PublicClient, component: Component, version: str, commit: str) -> dict[str, Any]: - encoded = "/".join(urllib.parse.quote(part, safe="") for part in component.package.split("/")) - url = f"https://repo.packagist.org/p2/{encoded}.json" - payload = client.json(url) - try: - release = exact_package_version(payload, component.package, version) - except PackagistMetadataError as error: - raise RecoveryError( - f"Packagist metadata for {component.package} is invalid: {error}", "registry-publication" - ) from error - if release is None: - raise NotFound(f"Packagist does not expose {component.package}@{version}", "registry-publication") - source_metadata = release.get("source") - dist_metadata = release.get("dist") - source = source_metadata.get("reference") if isinstance(source_metadata, dict) else None - dist = dist_metadata.get("reference") if isinstance(dist_metadata, dict) else None - if source != commit or dist != commit: - raise RecoveryError( - f"Packagist identity for {component.package}@{version} is {source}/{dist}, not {commit}", - "registry-publication", - ) - return {"kind": "composer", "registry": url, "source_reference": source, "dist_reference": dist} - - -def oci_json(client: PublicClient, url: str, token: str, accept: str) -> tuple[Any, str | None]: - response = client.request(url, headers={"Authorization": f"Bearer {token}"}, accept=accept) - with response: - return json.load(response), response.headers.get("Docker-Content-Digest") - - -def verify_oci(client: PublicClient, component: Component, version: str, commit: str) -> dict[str, Any]: - repository = component.package.split("/", 1)[1] - token_url = "https://auth.docker.io/token?service=registry.docker.io&scope=" + urllib.parse.quote( - f"repository:{repository}:pull" - ) - token = client.json(token_url).get("token") - if not token: - raise RecoveryError(f"Docker Hub did not grant public pull access to {component.package}:{version}") - accept = ", ".join( - ( - "application/vnd.oci.image.index.v1+json", - "application/vnd.docker.distribution.manifest.list.v2+json", - "application/vnd.oci.image.manifest.v1+json", - "application/vnd.docker.distribution.manifest.v2+json", - ) - ) - url = f"https://registry-1.docker.io/v2/{repository}/manifests/{urllib.parse.quote(version, safe='')}" - try: - manifest, digest = oci_json(client, url, str(token), accept) - except NotFound as error: - raise NotFound(f"Docker Hub does not expose {component.package}:{version}", "registry-publication") from error - if not re.fullmatch(r"sha256:[0-9a-f]{64}", str(digest or "")): - raise RecoveryError(f"Docker Hub image {component.package}:{version} has no immutable digest") - descriptors = manifest.get("manifests") - if not isinstance(descriptors, list): - raise RecoveryError(f"Docker Hub image {component.package}:{version} is not multi-platform") - platforms: set[str] = set() - for descriptor in descriptors: - platform = descriptor.get("platform", {}) - label = f"{platform.get('os')}/{platform.get('architecture')}" - if label not in {"linux/amd64", "linux/arm64"}: - continue - child, child_digest = oci_json( - client, - f"https://registry-1.docker.io/v2/{repository}/manifests/{descriptor['digest']}", - str(token), - accept, - ) - if child_digest != descriptor["digest"]: - raise RecoveryError(f"Docker Hub platform digest changed for {component.package}:{version}") - config_digest = child.get("config", {}).get("digest") - config = client.json( - f"https://registry-1.docker.io/v2/{repository}/blobs/{config_digest}", - headers={"Authorization": f"Bearer {token}"}, - ) - labels = config.get("config", {}).get("Labels") or {} - if labels.get("org.opencontainers.image.revision") != commit: - raise RecoveryError(f"Docker Hub image {component.package}:{version} names a different source commit") - if labels.get("dev.durable-workflow.release.tag") != version: - raise RecoveryError(f"Docker Hub image {component.package}:{version} names a different release tag") - platforms.add(label) - if platforms != {"linux/amd64", "linux/arm64"}: - raise RecoveryError(f"Docker Hub image {component.package}:{version} lacks required Linux platforms") - return {"kind": "oci", "image": f"{component.package}:{version}", "digest": digest, "platforms": sorted(platforms)} - - -def archive_files(path: Path, *, zipped: bool = False) -> dict[str, bytes]: - files: dict[str, bytes] = {} - if zipped: - with zipfile.ZipFile(path) as archive: - for member in archive.infolist(): - if not member.is_dir(): - files[member.filename] = archive.read(member) - return files - with tarfile.open(path, "r:*") as archive: - for member in archive.getmembers(): - if member.isfile() and (extracted := archive.extractfile(member)) is not None: - files[member.name] = extracted.read() - return files - - -def strip_root(files: dict[str, bytes]) -> dict[str, bytes]: - return { - relative: content - for name, content in files.items() - if (separator := name.partition("/"))[1] and (relative := separator[2]) - } - - -def verify_pypi(client: PublicClient, component: Component, version: str, commit: str) -> dict[str, Any]: - package = urllib.parse.quote(component.package, safe="") - encoded_version = urllib.parse.quote(version, safe="") - url = f"https://pypi.org/pypi/{package}/{encoded_version}/json" - try: - payload = client.json(url) - except NotFound as error: - raise NotFound(f"PyPI does not expose {component.package}=={version}", "registry-publication") from error - files = [item for item in payload.get("urls", []) if not item.get("yanked")] - sdist = next((item for item in files if item.get("packagetype") == "sdist"), None) - wheels = [item for item in files if item.get("packagetype") == "bdist_wheel"] - if sdist is None or not wheels: - raise RecoveryError(f"PyPI release {component.package}=={version} lacks a wheel or source archive") - with tempfile.TemporaryDirectory(prefix="release-recovery-pypi-") as temporary: - directory = Path(temporary) - source_path = directory / "source.tar.gz" - sdist_path = directory / str(sdist["filename"]) - client.download(f"https://github.com/{component.repository}/archive/{commit}.tar.gz", source_path) - client.download(sdist["url"], sdist_path, expected_sha256=sdist.get("digests", {}).get("sha256")) - source_files = strip_root(archive_files(source_path)) - sdist_files = strip_root(archive_files(sdist_path)) - compared = 0 - for name, content in sdist_files.items(): - if ".egg-info/" in name or name.endswith(("/PKG-INFO", "PKG-INFO")): - continue - if name == "setup.cfg" and name not in source_files: - continue - if source_files.get(name) != content: - raise RecoveryError(f"PyPI source file {name} differs from source commit {commit}") - compared += 1 - if not compared: - raise RecoveryError(f"PyPI release {component.package}=={version} has no comparable source files") - return {"kind": "pypi", "registry": url, "source_files_compared": compared} - - -def verify_crate(client: PublicClient, component: Component, version: str, commit: str) -> dict[str, Any]: - package = urllib.parse.quote(component.package, safe="") - encoded_version = urllib.parse.quote(version, safe="") - url = f"https://crates.io/api/v1/crates/{package}/{encoded_version}" - try: - payload = client.json(url) - except NotFound as error: - raise NotFound(f"crates.io does not expose {component.package}@{version}", "registry-publication") from error - published = payload.get("version", {}) - if published.get("num") != version or published.get("yanked"): - raise RecoveryError(f"crates.io release {component.package}@{version} is not active") - checksum = published.get("checksum") - with tempfile.TemporaryDirectory(prefix="release-recovery-crate-") as temporary: - archive_path = Path(temporary) / f"{component.package}-{version}.crate" - client.download( - f"https://crates.io/api/v1/crates/{package}/{encoded_version}/download", - archive_path, - expected_sha256=checksum, - ) - with tarfile.open(archive_path, "r:gz") as archive: - members = [member for member in archive.getmembers() if member.name.endswith("/.cargo_vcs_info.json")] - if len(members) != 1 or (extracted := archive.extractfile(members[0])) is None: - raise RecoveryError("published crate has no unique source identity") - vcs = json.load(extracted) - if vcs.get("git", {}).get("sha1") != commit or vcs.get("git", {}).get("dirty", False): - raise RecoveryError(f"crates.io archive for {component.package}@{version} names a different source commit") - return {"kind": "crates.io", "registry": url, "checksum": checksum, "source_commit": commit} - - -def parse_checksums(raw: bytes) -> dict[str, str]: - checksums: dict[str, str] = {} - try: - lines = raw.decode("utf-8").splitlines() - except UnicodeDecodeError as error: - raise RecoveryError("CLI SHA256SUMS is not valid UTF-8", "registry-publication") from error - for line in lines: - match = re.fullmatch(r"([0-9a-fA-F]{64})\s+[*]?([^/\s]+)", line.strip()) - if match: - checksums[match.group(2)] = match.group(1).lower() - return checksums - - -def verify_cli(client: PublicClient, component: Component, version: str, commit: str) -> dict[str, Any]: - encoded = urllib.parse.quote(version, safe="") - try: - release = client.json(f"https://api.github.com/repos/{component.repository}/releases/tags/{encoded}") - except NotFound as error: - raise NotFound(f"CLI GitHub Release {version} is absent", "registry-publication") from error - assets = {asset.get("name"): asset for asset in release.get("assets", [])} - missing = CLI_ASSETS - set(assets) - if release.get("draft") or release.get("tag_name") != version or missing: - raise RecoveryError(f"CLI GitHub Release {version} is incomplete; missing assets: {sorted(missing)}") - - checksum_asset = assets["SHA256SUMS"] - checksum_raw = client.bytes(checksum_asset["browser_download_url"]) - checksums = parse_checksums(checksum_raw) - downloadable = sorted(CLI_ASSETS - {"SHA256SUMS"}) - missing_checksums = set(downloadable) - set(checksums) - if missing_checksums: - raise RecoveryError( - f"CLI SHA256SUMS does not cover every public release asset; missing: {sorted(missing_checksums)}", - "registry-publication", - ) - - verified_assets: list[dict[str, Any]] = [] - signer_workflow = f"{component.repository}/.github/workflows/release.yml" - attestation_modes = [ - ( - "exact-tag", - ["--source-ref", f"refs/tags/{version}", "--source-digest", commit], - {"mode": "exact-tag", "ref": f"refs/tags/{version}", "commit": commit}, - ), - ( - "qualified-main-workflow", - ["--source-ref", "refs/heads/main", "--signer-workflow", signer_workflow], - {"mode": "qualified-main-workflow", "ref": "refs/heads/main", "workflow": signer_workflow}, - ), - ] - selected_attestation_mode: tuple[str, list[str], dict[str, str]] | None = None - with tempfile.TemporaryDirectory(prefix="release-recovery-cli-") as temporary: - directory = Path(temporary) - downloaded_paths: list[Path] = [] - for name in downloadable: - asset = assets[name] - asset_path = directory / name - result = client.download( - asset["browser_download_url"], - asset_path, - expected_sha256=checksums[name], - ) - result.update({"name": name, "asset_id": asset.get("id")}) - verified_assets.append(result) - downloaded_paths.append(asset_path) - - checksum_path = directory / "SHA256SUMS" - checksum_path.write_bytes(checksum_raw) - verified_assets.append( - { - "name": "SHA256SUMS", - "asset_id": checksum_asset.get("id"), - "url": checksum_asset["browser_download_url"], - "size": len(checksum_raw), - "sha256": hashlib.sha256(checksum_raw).hexdigest(), - } - ) - downloaded_paths.append(checksum_path) - - if shutil.which("gh") is None: - raise RecoveryError( - "GitHub CLI is required to verify CLI release attestations", - "registry-publication", - ) - for asset_path in downloaded_paths: - base_arguments = ["gh", "attestation", "verify", str(asset_path), "--repo", component.repository] - candidates = attestation_modes if selected_attestation_mode is None else [selected_attestation_mode] - failures: list[str] = [] - for mode in candidates: - process = subprocess.run([*base_arguments, *mode[1]], check=False, text=True, capture_output=True) - if process.returncode == 0: - selected_attestation_mode = mode - break - failures.append(f"{mode[0]}: {process.stderr.strip()}") - else: - raise RecoveryError( - f"CLI build attestation failed for {asset_path.name}: {'; '.join(failures)}", - "registry-publication", - ) - - assert selected_attestation_mode is not None - if shutil.which("php") is None: - raise RecoveryError("PHP is required to verify CLI release source metadata", "registry-publication") - phar_version = subprocess.run( - ["php", str(directory / "dw.phar"), "--version"], - cwd=directory, - check=False, - text=True, - capture_output=True, - env={"PATH": os.environ.get("PATH", os.defpath)}, - ) - expected_identity = f"{version} (commit {commit[:12]}," - if phar_version.returncode or expected_identity not in phar_version.stdout: - raise RecoveryError( - f"CLI PHAR for {version} does not embed planned source commit {commit}", "registry-publication" - ) - - return { - "kind": "github-release", - "id": release.get("id"), - "url": release.get("html_url"), - "build_attestations_verified": True, - "build_attestation_authority": selected_attestation_mode[2], - "package_source": {"commit": commit, "embedded_phar_identity": phar_version.stdout.strip()}, - "assets": verified_assets, - } - - -VERIFIERS = { - "composer": verify_composer, - "oci": verify_oci, - "pypi": verify_pypi, - "crates.io": verify_crate, - "github-release": verify_cli, -} - - -def verify_component(client: PublicClient, name: str, identity: dict[str, str]) -> dict[str, Any]: - component = COMPONENTS[name] - require_source_tag(client, name, identity) - distribution = VERIFIERS[component.distribution](client, component, identity["version"], identity["commit"]) - github_release = ( - distribution - if component.distribution == "github-release" - else verify_github_release(client, name, identity["version"]) - ) - return { - "version": identity["version"], - "commit": identity["commit"], - "distribution": distribution, - "github_release": github_release, - } - - -def verify_distribution(client: PublicClient, name: str, identity: dict[str, str]) -> dict[str, Any]: - component = COMPONENTS[name] - require_source_tag(client, name, identity) - distribution = VERIFIERS[component.distribution](client, component, identity["version"], identity["commit"]) - return {"version": identity["version"], "commit": identity["commit"], "distribution": distribution} - - -def write_output(path: Path | None, values: dict[str, str]) -> None: - if path is None: - return - with path.open("a", encoding="utf-8") as output: - for key, value in values.items(): - output.write(f"{key}={value}\n") - - -def base_state(component: str, tag: str | None = None, plan: dict[str, Any] | None = None) -> dict[str, Any]: - return { - "schema": STATE_SCHEMA, - "component": component, - "release_plan_tag": tag, - "plan": plan.get("plan") if plan else None, - "channel": plan.get("channel") if plan else None, - "observed_at": dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"), - } - - -def scheduled_continuity_pause(client: PublicClient, plan: dict[str, Any]) -> dict[str, str] | None: - accepted_tag = f"{CONTINUITY_TAG_PREFIX}{plan['plan']}/accepted" - accepted_commit = resolve_tag(client, CONTROL_REPOSITORY, accepted_tag) - if accepted_commit is None: - return None - accepted_plan = read_record(client, accepted_tag, accepted_commit, "release-plan.json") - validate_plan(accepted_plan) - if canonical_json(accepted_plan) != canonical_json(plan): - raise RecoveryError("continuity acceptance record names a different release plan", "continuity-gate") - resumed_tag = f"{CONTINUITY_TAG_PREFIX}{plan['plan']}/resumed" - resumed_commit = resolve_tag(client, CONTROL_REPOSITORY, resumed_tag) - if resumed_commit is not None: - resumed_plan = read_record(client, resumed_tag, resumed_commit, "release-plan.json") - validate_plan(resumed_plan) - if canonical_json(resumed_plan) != canonical_json(plan): - raise RecoveryError("continuity resume record names a different release plan", "continuity-gate") - return None - return {"accepted_tag": accepted_tag, "accepted_commit": accepted_commit, "resumed_tag": resumed_tag} - - -def resolve_component( - client: PublicClient, - component_name: str, - tag: str, - record_commit: str, - plan: dict[str, Any], - preparation: dict[str, Any] | None, - implicit_authority: dict[str, Any] | None = None, -) -> tuple[dict[str, Any], dict[str, str]]: - if component_name not in COMPONENTS: - raise RecoveryError(f"unknown release component: {component_name}") - branches, recovery_workflows = verify_plan_authority(client, plan) - component = COMPONENTS[component_name] - identity = plan["components"][component_name] - prepared_identity = None - if preparation is not None: - validate_release_preparation(preparation, plan) - prepared_identity = preparation["components"][component_name] - upstream: dict[str, Any] = {} - for dependency in component.dependencies: - try: - upstream[dependency] = verify_component(client, dependency, plan["components"][dependency]) - except NotFound as error: - raise RecoveryError( - f"{component_name} is waiting for upstream {dependency}: {error}", "upstream-publication" - ) from error - - existing_tag = resolve_tag(client, component.repository, identity["version"]) - if existing_tag is not None and existing_tag != identity["commit"]: - raise RecoveryError( - f"existing version tag {component.repository}@{identity['version']} " - f"points to {existing_tag}, not {identity['commit']}", - "tag-preflight", - ) - completed: dict[str, Any] | None = None - if existing_tag is not None: - with contextlib.suppress(NotFound): - completed = verify_component(client, component_name, identity) - if completed is not None: - action = "skip" - else: - if preparation is None: - raise RecoveryError( - f"release plan {tag} lacks release preparation required before publishing {component_name}", - "plan-discovery", - ) - if existing_tag is None: - with contextlib.suppress(NotFound): - VERIFIERS[component.distribution]( - client, - component, - identity["version"], - identity["commit"], - ) - action = "publish" - if implicit_authority is not None: - revalidate_implicit_plan_authority(client, implicit_authority) - require_completed_plan_verification(implicit_authority, action) - state = base_state(component_name, tag, plan) - state.update( - { - "phase": "complete" if action == "skip" else "publication", - "outcome": "verified" if action == "skip" else "ready", - "plan_record_commit": record_commit, - "default_branches": branches, - "recovery_workflows": recovery_workflows, - "upstream": upstream, - "source_tag": {"status": "present" if existing_tag else "absent", "commit": existing_tag}, - "declared_identity": identity, - "public_evidence": completed, - "resume_action": ( - "No action is required; repeated recovery verifies and skips this component" - if action == "skip" - else f"Run {component.repository} Actions workflow Release plan recovery for {tag}" - ), - } - ) - authority_evidence = next(iter(recovery_workflows.values()), {}).get("authority") - if authority_evidence is not None: - state["recovery_workflow_authority"] = authority_evidence - if prepared_identity is not None: - state["release_preparation"] = { - "record_commit": record_commit, - "record_sha256": manifest_digest(preparation), - "release_date": prepared_identity["release_notes"]["release_date"], - "release_notes_sha256": prepared_identity["release_notes"]["sha256"], - "source": prepared_identity["release_notes"]["source"], - } - outputs = { - "action": action, - "plan": str(plan["plan"]), - "channel": str(plan["channel"]), - "plan_tag": tag, - "plan_record_commit": record_commit, - "version": str(identity["version"]), - "commit": str(identity["commit"]), - "default_branch": component.default_branch, - "release_workflow": component.release_workflow or "", - "release_tag_input": component.release_tag_input or "", - } - if prepared_identity is not None: - outputs.update( - { - "release_date": str(prepared_identity["release_notes"]["release_date"]), - "release_notes_sha256": str(prepared_identity["release_notes"]["sha256"]), - } - ) - return state, outputs - - -def verify_with_retry( - client: PublicClient, - component_name: str, - plan: dict[str, Any], - attempts: int, - sleep_seconds: int, - registry_only: bool = False, -) -> dict[str, Any]: - last_error: RecoveryError | None = None - for attempt in range(1, attempts + 1): - try: - verifier = verify_distribution if registry_only else verify_component - return verifier(client, component_name, plan["components"][component_name]) - except NotFound as error: - last_error = error - if attempt < attempts: - print(f"waiting for public artifact ({attempt}/{attempts}): {error}", file=sys.stderr) - time.sleep(sleep_seconds) - assert last_error is not None - raise last_error - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - resolve = subparsers.add_parser("resolve") - resolve.add_argument("--component", required=True, choices=sorted(COMPONENTS)) - resolve.add_argument("--plan-tag") - resolve.add_argument("--plan-output", required=True, type=Path) - resolve.add_argument("--preparation-output", required=True, type=Path) - resolve.add_argument("--evidence", required=True, type=Path) - resolve.add_argument("--github-output", type=Path) - resolve.add_argument("--allow-empty", action="store_true") - - verify = subparsers.add_parser("verify") - verify.add_argument("--component", required=True, choices=sorted(COMPONENTS)) - verify.add_argument("--plan", required=True, type=Path) - verify.add_argument("--attempts", type=int, default=1) - verify.add_argument("--sleep", type=int, default=0) - verify.add_argument("--registry-only", action="store_true") - verify.add_argument("--evidence", required=True, type=Path) - - select_run = subparsers.add_parser("select-publication-run") - select_run.add_argument("--release-tag", required=True) - select_run.add_argument("--release-commit", required=True) - select_run.add_argument("--runs", required=True, type=Path) - - args = parser.parse_args() - token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") - client = PublicClient(token) - try: - if args.command == "select-publication-run": - try: - runs = json.loads(args.runs.read_bytes()) - except (OSError, json.JSONDecodeError) as error: - raise RecoveryError(f"cannot read publication run metadata: {error}", "publication") from error - selection = select_publication_run(args.release_tag, args.release_commit, runs) - print( - "\t".join( - str(selection.get(field) or "") - for field in ("action", "run_id", "status", "conclusion") - ) - ) - elif args.command == "resolve": - tag: str | None = args.plan_tag - record_commit: str | None = None - plan: dict[str, Any] | None = None - try: - tag, record_commit, plan, preparation, implicit_authority = discover_plan( - client, - args.plan_tag, - args.component, - ) - args.plan_output.write_bytes(canonical_json(plan)) - if preparation is not None: - args.preparation_output.write_bytes(canonical_json(preparation)) - continuity_pause = scheduled_continuity_pause(client, plan) if args.plan_tag is None else None - if continuity_pause is not None: - assert implicit_authority is not None - revalidate_implicit_plan_authority(client, implicit_authority) - paused = base_state(args.component, tag, plan) - paused.update( - { - "phase": "continuity-gate", - "outcome": "paused", - "plan_record_commit": record_commit, - "continuity": continuity_pause, - "resume_action": ( - f"Wait for {continuity_pause['resumed_tag']} or explicitly recover exact plan {tag}" - ), - } - ) - args.evidence.write_bytes(canonical_json(paused)) - write_output( - args.github_output, - { - "action": "none", - "plan": str(plan["plan"]), - "channel": str(plan["channel"]), - "plan_tag": tag, - "plan_record_commit": record_commit, - }, - ) - return 0 - state, outputs = resolve_component( - client, - args.component, - tag, - record_commit, - plan, - preparation, - implicit_authority, - ) - args.evidence.write_bytes(canonical_json(state)) - write_output(args.github_output, outputs) - except RecoveryError as error: - if ( - args.allow_empty - and args.plan_tag is None - and error.phase == "plan-discovery" - and str(error) == "no public release plan is available" - ): - no_op = base_state(args.component) - no_op.update( - { - "phase": "plan-discovery", - "outcome": "no-op", - "reason": str(error), - "resume_action": "No action is required; scheduled recovery found no eligible release plan", - } - ) - args.evidence.write_bytes(canonical_json(no_op)) - write_output(args.github_output, {"action": "none"}) - return 0 - failure = base_state(args.component, tag, plan) - if record_commit is not None: - failure["plan_record_commit"] = record_commit - failure.update( - { - "phase": error.phase, - "outcome": "failed", - "reason": str(error), - "durable_evidence": { - "release_plan": tag, - "source_tag": f"https://github.com/{COMPONENTS[args.component].repository}/releases", - "actions": f"https://github.com/{COMPONENTS[args.component].repository}/actions", - }, - "resume_action": ( - f"Run {COMPONENTS[args.component].repository} Actions workflow " - f"Release plan recovery{f' for {tag}' if tag else ''}" - ), - } - ) - args.evidence.write_bytes(canonical_json(failure)) - raise - else: - if args.attempts < 1 or args.sleep < 0: - raise RecoveryError("retry attempts must be positive and sleep must be non-negative") - try: - plan = json.loads(args.plan.read_bytes()) - except (OSError, json.JSONDecodeError) as error: - raise RecoveryError(f"cannot read canonical release plan: {error}") from error - validate_plan(plan) - try: - public = verify_with_retry( - client, - args.component, - plan, - args.attempts, - args.sleep, - registry_only=args.registry_only, - ) - state = base_state(args.component, f"{PLAN_TAG_PREFIX}{plan['plan']}", plan) - state.update( - { - "phase": "complete", - "outcome": "verified", - "public_evidence": public, - "resume_action": "No action is required", - } - ) - args.evidence.write_bytes(canonical_json(state)) - except RecoveryError as error: - state = base_state(args.component, f"{PLAN_TAG_PREFIX}{plan['plan']}", plan) - state.update( - { - "phase": error.phase, - "outcome": "failed", - "reason": str(error), - "resume_action": ( - f"Run {COMPONENTS[args.component].repository} Actions workflow Release plan recovery " - f"for {PLAN_TAG_PREFIX}{plan['plan']}" - ), - } - ) - args.evidence.write_bytes(canonical_json(state)) - raise - except PublicInfrastructureError as error: - if hasattr(args, "evidence") and hasattr(args, "component"): - transport = base_state(args.component) - transport.update( - { - "phase": "runner-transport", - "outcome": "runner-transport", - "transport": error.evidence, - "resume_action": "Retry recovery after trusted GitHub API transport is available", - } - ) - args.evidence.write_bytes(canonical_json(transport)) - if args.command == "resolve": - write_output(args.github_output, {"action": "none"}) - print(f"release recovery infrastructure failed: {error}", file=sys.stderr) - return INFRASTRUCTURE_EXIT_CODE - except RecoveryError as error: - print(f"release recovery error: {error}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/cross_repository_lifecycle.py b/scripts/cross_repository_lifecycle.py deleted file mode 100644 index 6b1ac9e..0000000 --- a/scripts/cross_repository_lifecycle.py +++ /dev/null @@ -1,1214 +0,0 @@ -"""Aggregate public landing evidence for one authoritative cross-repository issue.""" - -from __future__ import annotations - -import hashlib -import json -import re -from collections.abc import Mapping, Sequence -from datetime import UTC, datetime -from typing import Any - -TARGET_HEADING = "### Required source targets" -TARGET_HEADING_PATTERN = re.compile(r"(?m)^#{2,3}[ \t]+Required source targets[ \t]*\r?$") -EVIDENCE_MARKER = "" -QUALIFICATION_LEDGER_MARKER = "durable-workflow-item-qualification-ledger:v1" -HTML_PULL_PATTERN = re.compile(r"https://github\.com/([^/]+)/([^/]+)/pull/([1-9][0-9]*)$") -COMMIT_PATTERN = re.compile(r"[0-9a-f]{40}") -REPOSITORY_PATTERN = re.compile(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+") -WORKFLOW_PATH_PATTERN = re.compile(r"[A-Za-z0-9_.-]+\.ya?ml") -TRUSTED_REPOSITORY_ASSOCIATIONS = {"COLLABORATOR", "MEMBER", "OWNER"} -RECORDED_REPOSITORY_ALIASES = {"github-control-plane": ".github"} -REFERENCE_SNAPSHOT_ATTEMPTS = 2 -QualificationClaim = tuple[str | None, str | None, int] -RecordedTarget = tuple[str, str, str, tuple[str, ...], tuple[QualificationClaim, ...], str | None] -RecordedCompletion = tuple[str, tuple[RecordedTarget, ...]] - - -class LifecycleError(RuntimeError): - """Cross-repository lifecycle evidence is malformed or unavailable.""" - - -def qualification_targets(policy: Mapping[str, Any]) -> dict[str, dict[str, Any]]: - """Reduce the qualification policy to the target identity used by issue intake.""" - - targets = policy.get("targets") - if not isinstance(targets, Mapping): - raise LifecycleError("target qualification policy has no target map") - reduced: dict[str, dict[str, Any]] = {} - for value in targets.values(): - if not isinstance(value, Mapping): - raise LifecycleError("target qualification policy contains a malformed target") - if value.get("public_audit", True) is False: - continue - repository = value.get("repository") - branch = value.get("branch") - workflows = value.get("workflows") - if ( - not isinstance(repository, str) - or not repository - or not isinstance(branch, str) - or not branch - or not isinstance(workflows, Sequence) - or isinstance(workflows, str | bytes) - ): - raise LifecycleError("target qualification policy contains an invalid target identity") - checks: list[str] = [] - required_workflows: list[dict[str, str]] = [] - for workflow in workflows: - check = workflow.get("required_check") if isinstance(workflow, Mapping) else None - path = workflow.get("path") if isinstance(workflow, Mapping) else None - if ( - not isinstance(check, str) - or not check - or not isinstance(path, str) - or WORKFLOW_PATH_PATTERN.fullmatch(path) is None - ): - raise LifecycleError(f"target qualification policy has no required check for {repository}@{branch}") - checks.append(check) - required_workflows.append({"path": path, "required_check": check}) - workflow_paths = [workflow["path"] for workflow in required_workflows] - if len(workflow_paths) != len(set(workflow_paths)) or len(checks) != len(set(checks)): - raise LifecycleError(f"target qualification policy repeats workflow identity for {repository}@{branch}") - if repository in reduced: - raise LifecycleError(f"target qualification policy repeats repository {repository}") - reduced[repository] = { - "branch": branch, - "repository": repository, - "required_checks": sorted(set(checks)), - "required_workflows": sorted(required_workflows, key=lambda workflow: workflow["path"]), - } - return reduced - - -def declared_targets( - body: str, - targets: Mapping[str, Mapping[str, Any]], - *, - organization: str, - required: bool = False, -) -> list[dict[str, Any]]: - """Read exact form selections from the bounded affected-repositories section.""" - - headings = list(TARGET_HEADING_PATTERN.finditer(body)) - if not headings: - if required: - raise LifecycleError("cross-repository authority must declare its required source targets") - return [] - if len(headings) != 1: - raise LifecycleError("cross-repository issue repeats its affected public repositories section") - section = body[headings[0].end() :] - section = re.split(r"(?m)^#{1,6}[ \t]+", section, maxsplit=1)[0] - selected: list[str] = [] - candidate_pattern = re.compile(rf"^(?:-\s*)?{re.escape(organization)}/([a-z0-9_.-]+)@(main|v2)\s*$") - for raw_line in section.splitlines(): - line = raw_line.strip() - if not line: - continue - if line.startswith("- "): - line = line[2:].strip() - for selection in line.split(","): - match = candidate_pattern.fullmatch(selection.strip()) - if match is None: - raise LifecycleError( - "affected public repositories must contain only exact organization/repository@branch selections" - ) - repository, branch = match.groups() - target = targets.get(repository) - if target is None or target["branch"] != branch: - raise LifecycleError(f"affected public repository {repository}@{branch} is not a qualified target") - selected.append(repository) - if len(selected) != len(set(selected)): - raise LifecycleError("affected public repositories contain a duplicate target") - if len(selected) < 2: - raise LifecycleError("cross-repository lifecycle aggregation requires at least two public targets") - return [dict(targets[repository]) for repository in sorted(selected)] - - -def _pull_identity(event: Mapping[str, Any], organization: str) -> tuple[str, int] | None: - if event.get("__typename") != "CrossReferencedEvent" or event.get("willCloseTarget") is not True: - return None - source = event.get("source") - repository = source.get("repository") if isinstance(source, Mapping) else None - name_with_owner = repository.get("nameWithOwner") if isinstance(repository, Mapping) else None - number = source.get("number") if isinstance(source, Mapping) else None - url = source.get("url") if isinstance(source, Mapping) else None - match = HTML_PULL_PATTERN.fullmatch(url) if isinstance(url, str) else None - if ( - not isinstance(source, Mapping) - or source.get("__typename") != "PullRequest" - or not isinstance(name_with_owner, str) - or REPOSITORY_PATTERN.fullmatch(name_with_owner) is None - or type(number) is not int - or number < 1 - or match is None - ): - return None - owner, repository_name = name_with_owner.split("/", 1) - if ( - owner.casefold() != organization.casefold() - or match.group(1).casefold() != organization.casefold() - or match.group(2) != repository_name - or int(match.group(3)) != number - ): - return None - return repository_name, number - - -def _utc_timestamp(value: Any) -> datetime | None: - if not isinstance(value, str) or not value: - return None - try: - timestamp = datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError: - return None - if timestamp.tzinfo is None: - return None - return timestamp.astimezone(UTC) - - -def _closing_reference_snapshot( - events: Sequence[Mapping[str, Any]], - organization: str, - target_repositories: set[str], -) -> tuple[tuple[str, int, str, str, str, bool], ...]: - """Bind every target-relevant positive closing reference to its exact authority fields.""" - - snapshot: list[tuple[str, int, str, str, str, bool]] = [] - for event in events: - identity = _pull_identity(event, organization) - if identity is None or identity[0] not in target_repositories: - continue - reference_id = event.get("id") - referenced_at = event.get("referencedAt") - actor = event.get("actor") - actor_login = actor.get("login") if isinstance(actor, Mapping) else None - if ( - not isinstance(reference_id, str) - or not reference_id - or _utc_timestamp(referenced_at) is None - or not isinstance(referenced_at, str) - or not isinstance(actor_login, str) - or not actor_login - ): - continue - snapshot.append((identity[0], identity[1], reference_id, referenced_at, actor_login, True)) - return tuple(sorted(snapshot)) - - -def pending_reference_change(assessment: Mapping[str, Any]) -> dict[str, Any]: - """Fail a stale assessment closed without carrying completed landing evidence forward.""" - - targets = [] - for value in assessment["targets"]: - target = dict(value) - target["commit"] = None - target["state"] = "pending:closing-reference-changed" - targets.append(target) - return { - "_authority_kind": assessment.get("_authority_kind"), - "_closing_reference_snapshot": assessment.get("_closing_reference_snapshot", ()), - "_completion_record_identity": assessment.get("_completion_record_identity"), - "complete": False, - "targets": targets, - } - - -def closing_references_are_current( - client: Any, - organization: str, - source_repository: str, - issue: Mapping[str, Any], - assessment: Mapping[str, Any], -) -> bool: - """Re-read GraphQL authority and compare it with the snapshot that produced an assessment.""" - - number = issue.get("number") - if not isinstance(number, int): - raise LifecycleError("cross-repository issue has no numeric identity") - repositories = {str(target["repository"]) for target in assessment["targets"]} - events = client.list_issue_closing_references(organization, source_repository, number) - return _closing_reference_snapshot(events, organization, repositories) == assessment.get( - "_closing_reference_snapshot" - ) - - -def _pipeline_completion_record( - body: str, - organization: str, - source_repository: str, - targets: Sequence[Mapping[str, Any]], -) -> RecordedCompletion | None: - """Read the exact immutable landing and run identities emitted by the protected merge gate.""" - - expected: dict[tuple[str, str], tuple[tuple[str, ...], tuple[str, ...]]] = {} - for target in targets: - required_workflows = target.get("required_workflows") - if not isinstance(required_workflows, Sequence) or isinstance(required_workflows, str | bytes): - return None - workflow_paths = tuple( - sorted( - str(workflow.get("path")) - for workflow in required_workflows - if isinstance(workflow, Mapping) - and isinstance(workflow.get("path"), str) - and WORKFLOW_PATH_PATTERN.fullmatch(str(workflow["path"])) is not None - ) - ) - if not workflow_paths or len(workflow_paths) != len(required_workflows): - return None - expected[(str(target["repository"]), str(target["branch"]))] = ( - tuple(sorted(set(target["required_checks"]))), - workflow_paths, - ) - source_targets = [identity for identity in expected if identity[0] == source_repository] - if len(source_targets) != 1: - return None - source_branch = source_targets[0][1] - lines = body.splitlines() - source_commit_url = rf"https://github\.com/{re.escape(organization)}/{re.escape(source_repository)}/commit/" - linked_commit = rf"\[`([0-9a-f]{{7,40}})`\]\({source_commit_url}({COMMIT_PATTERN.pattern})\)" - primary_pattern = re.compile(rf"Completed in {linked_commit} on `({re.escape(source_branch)})`\.") - primary_matches = [match for line in lines if (match := primary_pattern.fullmatch(line.strip())) is not None] - implementation_pattern = re.compile( - rf"Implemented in {linked_commit}(?: on `{re.escape(source_branch)}`)?\." - ) - completion_pattern = re.compile( - rf"Included by completion source {linked_commit}(?: on `{re.escape(source_branch)}`)?\." - ) - implementation_matches = [ - match for line in lines if (match := implementation_pattern.fullmatch(line.strip())) is not None - ] - completion_matches = [ - match for line in lines if (match := completion_pattern.fullmatch(line.strip())) is not None - ] - implementation_commit: str | None = None - if ( - len(primary_matches) == 1 - and not implementation_matches - and not completion_matches - and primary_matches[0].group(2).startswith(primary_matches[0].group(1)) - ): - source_commit = primary_matches[0].group(2) - elif ( - not primary_matches - and len(implementation_matches) == 1 - and len(completion_matches) == 1 - and implementation_matches[0].group(2).startswith(implementation_matches[0].group(1)) - and completion_matches[0].group(2).startswith(completion_matches[0].group(1)) - ): - implementation_commit = implementation_matches[0].group(2) - source_commit = completion_matches[0].group(2) - if implementation_commit == source_commit: - return None - else: - return None - - source_run_url = ( - rf"https://github\.com/{re.escape(organization)}/{re.escape(source_repository)}/actions/runs/" - ) - legacy_source_qualification_pattern = re.compile( - r"Required public qualification passed: " + source_run_url + r"([1-9][0-9]*)" - ) - named_source_qualification_patterns = ( - re.compile( - r"Required public qualification (?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?) " - r"\(`?(?P[A-Za-z0-9_.-]+\.ya?ml)`?\) passed in run " - r"\[(?P[1-9][0-9]*)\]\(" + source_run_url + r"(?P[1-9][0-9]*)\)\.?" - ), - re.compile( - r"Required public qualification (?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?) " - r"\(`?(?P[A-Za-z0-9_.-]+\.ya?ml)`?\) passed in run " - r"(?P[1-9][0-9]*) \(" + source_run_url + r"(?P[1-9][0-9]*)\)\.?" - ), - re.compile( - r"Required public qualification (?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?) " - r"\(`?(?P[A-Za-z0-9_.-]+\.ya?ml)`?\) passed in run " - r"(?P[1-9][0-9]*): " + source_run_url + r"(?P[1-9][0-9]*)\.?" - ), - re.compile( - r"Required public qualification `(?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?)` " - r"\(`?(?P[A-Za-z0-9_.-]+\.ya?ml)`?\) passed in " - r"\[run (?P[1-9][0-9]*)\]\(" + source_run_url + r"(?P[1-9][0-9]*)\)\.?" - ), - re.compile( - r"Required public qualification (?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?) " - r"passed in run \[(?P[1-9][0-9]*)\]\(" + source_run_url + r"(?P[1-9][0-9]*)\)\.?" - ), - re.compile( - r"Required public qualification `(?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?)` " - r"passed in \[run (?P[1-9][0-9]*)\]\(" + source_run_url + r"(?P[1-9][0-9]*)\)\.?" - ), - ) - legacy_source_runs = [ - match - for line in lines - if (match := legacy_source_qualification_pattern.fullmatch(line.strip())) is not None - ] - named_source_runs = [ - match - for line in lines - for pattern in named_source_qualification_patterns - if (match := pattern.fullmatch(line.strip())) is not None - ] - source_qualifications: tuple[QualificationClaim, ...] - if len(legacy_source_runs) == 1 and not named_source_runs: - source_qualifications = ((None, None, int(legacy_source_runs[0].group(1))),) - elif not legacy_source_runs and named_source_runs: - claims = [ - (match.group("name"), match.groupdict().get("path"), int(match.group("run"))) - for match in named_source_runs - if match.group("run") == match.group("url_run") - ] - if len(claims) != len(named_source_runs) or len(claims) != len(set(claims)): - return None - source_qualifications = tuple(sorted(claims, key=lambda claim: (claim[1], claim[2], claim[0] or ""))) - else: - return None - - peer_heading = "Required cross-repository target qualification passed:" - headings = [index for index, line in enumerate(lines) if line.strip() == peer_heading] - if len(headings) != 1: - return None - peer_pattern = re.compile( - rf"- `([a-z0-9_.-]+):(main|v2)` at \[`([0-9a-f]{{7,40}})`\]\(" - rf"https://github\.com/{re.escape(organization)}/([a-z0-9_.-]+)/commit/" - rf"({COMMIT_PATTERN.pattern})\) (?P.+)" - ) - peer_records: list[RecordedTarget] = [] - peer_lines_started = False - for line in lines[headings[0] + 1 :]: - stripped = line.strip() - if not stripped: - if peer_lines_started: - break - continue - if not stripped.startswith("- "): - if peer_lines_started: - break - return None - peer_lines_started = True - match = peer_pattern.fullmatch(stripped) - if match is None: - return None - recorded_repository, branch, short_commit, url_repository, commit = match.groups()[:5] - repository = RECORDED_REPOSITORY_ALIASES.get(recorded_repository, recorded_repository) - identity = (repository, branch) - if ( - repository != url_repository - or identity not in expected - or repository == source_repository - or not commit.startswith(short_commit) - ): - return None - required_checks, workflow_paths = expected[identity] - peer_run_url = rf"https://github\.com/{re.escape(organization)}/{re.escape(repository)}/actions/runs/" - legacy_peer_pattern = re.compile( - r"\(\[qualification\]\(" + peer_run_url + r"(?P[1-9][0-9]*)\)\)" - ) - named_peer_patterns = ( - re.compile( - r"(?:; |\()Required (?:public|target) qualification " - r"(?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?) " - r"\(`?(?P[A-Za-z0-9_.-]+\.ya?ml)`?\) passed in run " - r"\[(?P[1-9][0-9]*)\]\(" + peer_run_url + r"(?P[1-9][0-9]*)\)\.?\)?" - ), - re.compile( - r"\(\[(?P[A-Za-z0-9][A-Za-z0-9 ._+:/-]*?) " - r"\(`?(?P[A-Za-z0-9_.-]+\.ya?ml)`?\)(?: passed in)? run " - r"(?P[1-9][0-9]*)\]\(" + peer_run_url + r"(?P[1-9][0-9]*)\)\)" - ), - ) - qualification_text = match.group("qualification") - legacy_peer = legacy_peer_pattern.fullmatch(qualification_text) - named_peer = next( - (candidate for pattern in named_peer_patterns if (candidate := pattern.fullmatch(qualification_text))), - None, - ) - if legacy_peer is not None and named_peer is None: - qualifications = ((None, None, int(legacy_peer.group("run"))),) - elif legacy_peer is None and named_peer is not None: - if named_peer.group("run") != named_peer.group("url_run") or named_peer.group("path") not in workflow_paths: - return None - qualifications = ( - (named_peer.group("name"), named_peer.group("path"), int(named_peer.group("run"))), - ) - else: - return None - peer_records.append((repository, branch, commit, required_checks, qualifications, None)) - - expected_peers = set(expected) - set(source_targets) - observed_peers = [ - (repository, branch) - for repository, branch, _commit, _checks, _qualifications, _implementation_commit in peer_records - ] - if ( - not peer_lines_started - or len(observed_peers) != len(set(observed_peers)) - or set(observed_peers) != expected_peers - ): - return None - - marker_pattern = re.compile(rf"") - marker_matches = marker_pattern.findall(body) - if ( - len(marker_matches) != 1 - or body.count("durable-workflow-completion-source:") != 1 - or marker_matches[0] != source_commit - ): - return None - if implementation_commit is not None: - implementation_marker_pattern = re.compile( - rf"" - ) - implementation_marker_matches = implementation_marker_pattern.findall(body) - if ( - len(implementation_marker_matches) != 1 - or body.count("durable-workflow-implementation-source:") != 1 - or implementation_marker_matches[0] != implementation_commit - ): - return None - records = [ - ( - source_repository, - source_branch, - source_commit, - expected[(source_repository, source_branch)][0], - source_qualifications, - implementation_commit, - ) - ] - records.extend(peer_records) - return marker_matches[0], tuple(sorted(records)) - - -def _recorded_completion( - client: Any, - organization: str, - source_repository: str, - issue: Mapping[str, Any], - targets: Sequence[Mapping[str, Any]], -) -> RecordedCompletion | None: - """Select one semantically exact aggregate record from the authenticated lifecycle writer.""" - - number = issue.get("number") - if not isinstance(number, int): - raise LifecycleError("cross-repository issue has no numeric identity") - candidates = { - record - for comment in client.list_trusted_issue_comments(organization, source_repository, number) - if isinstance(comment, Mapping) - and isinstance(comment.get("body"), str) - and ( - record := _pipeline_completion_record( - comment["body"], - organization, - source_repository, - targets, - ) - ) - is not None - } - if len(candidates) > 1: - raise LifecycleError("trusted cross-repository completion records disagree") - return next(iter(candidates)) if candidates else None - - -def lifecycle_authority_is_current( - client: Any, - organization: str, - source_repository: str, - issue: Mapping[str, Any], - assessment: Mapping[str, Any], -) -> bool: - """Re-read the authority source that produced a complete lifecycle assessment.""" - - if not closing_references_are_current(client, organization, source_repository, issue, assessment): - return False - if assessment.get("_authority_kind") not in {"mixed-landing-record", "protected-branch-record"}: - return True - targets = [ - { - "branch": target["branch"], - "repository": target["repository"], - "required_checks": target["required_checks"], - "required_workflows": target["required_workflows"], - } - for target in assessment["targets"] - ] - return _recorded_completion(client, organization, source_repository, issue, targets) == assessment.get( - "_completion_record_identity" - ) - - -def _approved_head_reviewer( - client: Any, - organization: str, - repository: str, - number: int, - head_sha: str, - reference_at: datetime, - trusted_actors: set[str], -) -> tuple[str, str, int] | None: - """Return an authorized exact-head approval recorded after the closing reference.""" - - latest_by_reviewer: dict[tuple[int, str], tuple[tuple[datetime, int], Mapping[str, Any]]] = {} - for review in client.list_pull_request_reviews(organization, repository, number): - if not isinstance(review, Mapping) or review.get("commit_id") != head_sha: - continue - user = review.get("user") - identifier = user.get("id") if isinstance(user, Mapping) else None - login = user.get("login") if isinstance(user, Mapping) else None - review_id = review.get("id") - submitted_at = review.get("submitted_at") - submitted_timestamp = _utc_timestamp(submitted_at) - if ( - type(identifier) is not int - or identifier < 1 - or not isinstance(login, str) - or not login - or type(review_id) is not int - or review_id < 1 - or submitted_timestamp is None - ): - continue - reviewer = (identifier, login.casefold()) - ordering = (submitted_timestamp, review_id) - if reviewer not in latest_by_reviewer or ordering > latest_by_reviewer[reviewer][0]: - latest_by_reviewer[reviewer] = (ordering, review) - for reviewer in sorted(latest_by_reviewer): - ordering, review = latest_by_reviewer[reviewer] - user = review["user"] - login = str(user["login"]) - if ( - ordering[0] > reference_at - and review.get("state") == "APPROVED" - and ( - login.casefold() in trusted_actors - or review.get("author_association") in TRUSTED_REPOSITORY_ASSOCIATIONS - ) - ): - return login, str(review["submitted_at"]), int(review["id"]) - return None - - -def _trusted_pull_request( - client: Any, - organization: str, - repository: str, - number: int, - event: Mapping[str, Any], - reference_at: datetime, - pull: Mapping[str, Any], - trusted_actors: set[str], -) -> dict[str, Any] | None: - """Bind exact pull metadata and admit only trusted or explicitly approved work.""" - - target_repository = f"{organization}/{repository}" - expected_api_url = f"https://api.github.com/repos/{target_repository}/pulls/{number}" - expected_html_url = f"https://github.com/{target_repository}/pull/{number}" - actor = event.get("actor") - actor_login = actor.get("login") if isinstance(actor, Mapping) else None - user = pull.get("user") - author_login = user.get("login") if isinstance(user, Mapping) else None - base = pull.get("base") - base_repo = base.get("repo") if isinstance(base, Mapping) else None - head = pull.get("head") - head_repo = head.get("repo") if isinstance(head, Mapping) else None - base_ref = base.get("ref") if isinstance(base, Mapping) else None - base_sha = base.get("sha") if isinstance(base, Mapping) else None - head_ref = head.get("ref") if isinstance(head, Mapping) else None - head_sha = head.get("sha") if isinstance(head, Mapping) else None - head_repository = head_repo.get("full_name") if isinstance(head_repo, Mapping) else None - author_association = pull.get("author_association") - if ( - pull.get("number") != number - or pull.get("url") != expected_api_url - or pull.get("html_url") != expected_html_url - or not isinstance(actor_login, str) - or not actor_login - or not isinstance(author_login, str) - or not author_login - or not isinstance(author_association, str) - or not isinstance(base, Mapping) - or not isinstance(base_repo, Mapping) - or base_repo.get("full_name") != target_repository - or not isinstance(base_ref, str) - or not base_ref - or not isinstance(base_sha, str) - or COMMIT_PATTERN.fullmatch(base_sha) is None - or not isinstance(head, Mapping) - or not isinstance(head_repo, Mapping) - or not isinstance(head_repository, str) - or REPOSITORY_PATTERN.fullmatch(head_repository) is None - or not isinstance(head_ref, str) - or not head_ref - or not isinstance(head_sha, str) - or COMMIT_PATTERN.fullmatch(head_sha) is None - ): - return None - trusted_author = author_login.casefold() in trusted_actors or author_association in TRUSTED_REPOSITORY_ASSOCIATIONS - trusted_reference_actor = actor_login.casefold() in trusted_actors or ( - actor_login.casefold() == author_login.casefold() - ) - trusted_execution = head_repository == target_repository and trusted_author and trusted_reference_actor - approval = None - if not trusted_execution: - approval = _approved_head_reviewer( - client, - organization, - repository, - number, - head_sha, - reference_at, - trusted_actors, - ) - if not trusted_execution and approval is None: - return None - approved_by = approval[0] if approval is not None else None - provenance = f"trusted-author:{author_login}" if trusted_execution else f"approved:{approved_by}" - return { - **pull, - "_provenance": { - "actor": actor_login, - "approval_at": approval[1] if approval is not None else None, - "approval_review": approval[2] if approval is not None else None, - "author": author_login, - "base_ref": base_ref, - "base_repository": target_repository, - "base_sha": base_sha, - "head_ref": head_ref, - "head_repository": head_repository, - "head_sha": head_sha, - "kind": provenance, - "reference_at": event["referencedAt"], - "reference_event": event["id"], - }, - } - - -def _latest_attempts( - client: Any, - organization: str, - events: Sequence[Mapping[str, Any]], - target_repositories: set[str], - trusted_actors: set[str], -) -> dict[str, dict[str, Any]]: - attempts: dict[str, dict[str, Any]] = {} - references: dict[tuple[str, int], tuple[tuple[datetime, int], Mapping[str, Any]]] = {} - invalid_references: set[tuple[str, int]] = set() - for index, event in enumerate(events): - identity = _pull_identity(event, organization) - if identity is None: - continue - reference_at = _utc_timestamp(event.get("referencedAt")) - reference_id = event.get("id") - if reference_at is None or not isinstance(reference_id, str) or not reference_id: - invalid_references.add(identity) - references.pop(identity, None) - continue - if identity in invalid_references: - continue - ordering = (reference_at, index) - if identity not in references or ordering > references[identity][0]: - references[identity] = (ordering, event) - for (repository, number), (reference_ordering, event) in sorted(references.items()): - if repository not in target_repositories: - continue - pull = client.get_pull_request(organization, repository, number) - trusted_pull = _trusted_pull_request( - client, - organization, - repository, - number, - event, - reference_ordering[0], - pull, - trusted_actors, - ) - if trusted_pull is None: - continue - created_at = trusted_pull.get("created_at") - if not isinstance(created_at, str) or not created_at: - raise LifecycleError(f"linked pull request {repository}#{number} has no creation time") - current = attempts.get(repository) - ordering = (created_at, number) - if current is None or ordering > current["_ordering"]: - attempts[repository] = {**trusted_pull, "_ordering": ordering} - return attempts - - -def _evaluate_target( - client: Any, - organization: str, - target: Mapping[str, Any], - pull: Mapping[str, Any] | None, -) -> dict[str, Any]: - repository = str(target["repository"]) - branch = str(target["branch"]) - required_checks = list(target["required_checks"]) - required_workflows = [dict(workflow) for workflow in target.get("required_workflows", ())] - result: dict[str, Any] = { - "approval_at": None, - "approval_review": None, - "base_commit": None, - "base_ref": None, - "base_repository": None, - "branch": branch, - "commit": None, - "head_commit": None, - "head_ref": None, - "head_repository": None, - "missing_checks": required_checks, - "pull_author": None, - "provenance": None, - "pull_request": None, - "reference_actor": None, - "reference_at": None, - "reference_event": None, - "repository": repository, - "required_checks": required_checks, - "required_workflows": required_workflows, - "state": "pending:no-linked-pull-request", - } - if pull is None: - return result - number = pull.get("number") - html_url = pull.get("html_url") - base = pull.get("base") - base_repo = base.get("repo") if isinstance(base, Mapping) else None - provenance = pull.get("_provenance") - result["pull_request"] = html_url - if not isinstance(provenance, Mapping): - raise LifecycleError(f"linked pull request {repository}#{number} has no trusted provenance") - result["approval_at"] = provenance["approval_at"] - result["approval_review"] = provenance["approval_review"] - result["base_commit"] = provenance["base_sha"] - result["base_ref"] = provenance["base_ref"] - result["base_repository"] = provenance["base_repository"] - result["head_commit"] = provenance["head_sha"] - result["head_ref"] = provenance["head_ref"] - result["head_repository"] = provenance["head_repository"] - result["pull_author"] = provenance["author"] - result["provenance"] = provenance["kind"] - result["reference_actor"] = provenance["actor"] - result["reference_at"] = provenance["reference_at"] - result["reference_event"] = provenance["reference_event"] - if ( - not isinstance(number, int) - or not isinstance(html_url, str) - or not isinstance(base, Mapping) - or base.get("ref") != branch - or not isinstance(base_repo, Mapping) - or base_repo.get("full_name") != f"{organization}/{repository}" - ): - result["state"] = "pending:wrong-target" - return result - if pull.get("merged_at") is None: - result["state"] = "pending:rejected" if pull.get("state") == "closed" else "pending:open" - return result - commit = pull.get("merge_commit_sha") - if not isinstance(commit, str) or COMMIT_PATTERN.fullmatch(commit) is None: - result["state"] = "pending:invalid-merge-commit" - return result - result["commit"] = commit - if not client.commit_reaches_branch(organization, repository, commit, branch): - result["state"] = "pending:landing-not-on-target" - return result - successful_checks = client.successful_check_names(organization, repository, commit) - missing_checks = sorted(set(required_checks) - successful_checks) - result["missing_checks"] = missing_checks - if missing_checks: - result["state"] = "pending:qualification" - return result - result["state"] = "complete" - return result - - -def _evaluate_recorded_target( - client: Any, - organization: str, - target: Mapping[str, Any], - completion: RecordedTarget, - completion_source: str, -) -> dict[str, Any]: - """Independently revalidate one exact commit from a trusted aggregate completion record.""" - - repository, branch, commit, required_checks, qualifications, implementation_commit = completion - required_workflows = [dict(workflow) for workflow in target.get("required_workflows", ())] - result: dict[str, Any] = { - "approval_at": None, - "approval_review": None, - "base_commit": None, - "base_ref": None, - "base_repository": None, - "branch": branch, - "commit": commit, - "completion_source": completion_source, - "head_commit": None, - "head_ref": None, - "head_repository": None, - "implementation_commit": implementation_commit, - "missing_checks": list(required_checks), - "provenance": "authenticated-completion-record", - "pull_author": None, - "pull_request": None, - "qualification_runs": [ - {"name": name, "path": path, "run": run_id} for name, path, run_id in qualifications - ], - "reference_actor": None, - "reference_at": None, - "reference_event": None, - "repository": repository, - "required_check_runs": {}, - "required_checks": list(required_checks), - "required_workflows": required_workflows, - "state": "pending:landing-not-on-target", - } - if repository != target["repository"] or branch != target["branch"]: - result["state"] = "pending:wrong-target" - return result - if tuple(sorted(set(target["required_checks"]))) != required_checks: - result["state"] = "pending:qualification-identity" - return result - if not client.commit_reaches_branch(organization, repository, commit, branch): - return result - if implementation_commit is not None: - if not client.commit_reaches_branch( - organization, repository, implementation_commit, branch - ): - return result - if not client.commit_contains( - organization, - repository, - commit, - implementation_commit, - ): - result["state"] = "pending:completion-source-does-not-contain-implementation" - return result - if not qualifications or any( - not client.successful_workflow_run( - organization, - repository, - run_id, - commit, - path, - name, - ) - for name, path, run_id in qualifications - ): - result["state"] = "pending:qualification-identity" - return result - successful_checks = client.successful_check_run_ids(organization, repository, commit) - result["required_check_runs"] = { - check: successful_checks[check] for check in required_checks if check in successful_checks - } - missing_checks = sorted(set(required_checks) - set(successful_checks)) - result["missing_checks"] = missing_checks - if missing_checks: - result["state"] = "pending:qualification" - else: - result["state"] = "complete" - return result - - -def evaluate_lifecycle( - client: Any, - organization: str, - source_repository: str, - issue: Mapping[str, Any], - targets: Sequence[Mapping[str, Any]], - *, - trusted_actors: Sequence[str], -) -> dict[str, Any]: - """Evaluate attempts only from a bounded, convergent closing-reference snapshot.""" - - number = issue.get("number") - if not isinstance(number, int): - raise LifecycleError("cross-repository issue has no numeric identity") - repositories = {str(target["repository"]) for target in targets} - if len(repositories) != len(targets): - raise LifecycleError("cross-repository lifecycle target set is duplicated") - trusted = {actor.casefold() for actor in trusted_actors if isinstance(actor, str) and actor} - if not trusted: - raise LifecycleError("cross-repository lifecycle has no trusted execution actors") - assessment: dict[str, Any] | None = None - for _attempt in range(REFERENCE_SNAPSHOT_ATTEMPTS): - events = client.list_issue_closing_references(organization, source_repository, number) - snapshot = _closing_reference_snapshot(events, organization, repositories) - attempts = _latest_attempts(client, organization, events, repositories, trusted) - results = [ - _evaluate_target(client, organization, target, attempts.get(str(target["repository"]))) - for target in targets - ] - authority_kind = "closing-references" - completion_record = None - if results and any(result["state"] == "pending:no-linked-pull-request" for result in results): - completion_record = _recorded_completion( - client, - organization, - source_repository, - issue, - targets, - ) - if completion_record is not None: - completion_source, recorded_targets = completion_record - by_repository = {record[0]: record for record in recorded_targets} - recorded_results = [ - _evaluate_recorded_target( - client, - organization, - target, - by_repository[str(target["repository"])], - completion_source, - ) - for target in targets - ] - results = [ - recorded - if current["state"] == "pending:no-linked-pull-request" or recorded["state"] != "complete" - else current - for current, recorded in zip(results, recorded_results, strict=True) - ] - authority_kind = ( - "protected-branch-record" - if all(result["provenance"] == "authenticated-completion-record" for result in results) - else "mixed-landing-record" - ) - qualification_advancements = [ - { - "branch": result["branch"], - "implementation_source": result["implementation_commit"], - "qualified_source": result["commit"], - "qualification_runs": [dict(record) for record in result["qualification_runs"]], - "repository": result["repository"], - } - for result in results - if ( - result["state"] == "complete" - and isinstance(result.get("implementation_commit"), str) - and result["implementation_commit"] != result["commit"] - ) - ] - assessment = { - "_authority_kind": authority_kind, - "_closing_reference_snapshot": snapshot, - "_completion_record_identity": completion_record, - "_qualification_ledger_advancements": qualification_advancements, - "complete": bool(results) and all(result["state"] == "complete" for result in results), - "targets": results, - } - final_events = client.list_issue_closing_references(organization, source_repository, number) - if _closing_reference_snapshot(final_events, organization, repositories) == snapshot: - return assessment - if assessment is None: - raise LifecycleError("cross-repository lifecycle did not evaluate a closing-reference snapshot") - return pending_reference_change(assessment) - - -def evaluate_recorded_landings( - client: Any, - organization: str, - landings: Sequence[Mapping[str, Any]], -) -> dict[str, Any]: - """Revalidate immutable protected-branch evidence for an archived aggregate.""" - - repositories = {str(landing.get("repository")) for landing in landings} - if not landings or len(repositories) != len(landings): - raise LifecycleError("historical cross-repository landing evidence is empty or duplicated") - results: list[dict[str, Any]] = [] - for landing in landings: - repository = landing.get("repository") - branch = landing.get("branch") - commit = landing.get("commit") - required_checks = landing.get("required_checks") - if ( - not isinstance(repository, str) - or not repository - or not isinstance(branch, str) - or not branch - or not isinstance(commit, str) - or COMMIT_PATTERN.fullmatch(commit) is None - or not isinstance(required_checks, Sequence) - or isinstance(required_checks, str | bytes) - or any(not isinstance(check, str) or not check for check in required_checks) - ): - raise LifecycleError("historical cross-repository landing evidence is malformed") - result = { - "branch": branch, - "commit": commit, - "missing_checks": list(required_checks), - "repository": repository, - "state": "pending:landing-not-on-target", - } - if client.commit_reaches_branch(organization, repository, commit, branch): - missing_checks = sorted( - set(required_checks) - client.successful_check_names(organization, repository, commit) - ) - result["missing_checks"] = missing_checks - result["state"] = "pending:qualification" if missing_checks else "complete" - results.append(result) - return { - "complete": all(result["state"] == "complete" for result in results), - "targets": results, - } - - -def render_evidence(assessment: Mapping[str, Any]) -> str: - """Render one replaceable, public lifecycle evidence record.""" - - rows = [ - f"{EVIDENCE_MARKER}", - "Cross-repository landing evidence (generated from trusted implementation pull requests or authenticated " - "protected-branch completion records):", - ] - completion_record = assessment.get("_completion_record_identity") - if ( - assessment.get("_authority_kind") in {"mixed-landing-record", "protected-branch-record"} - and isinstance(completion_record, tuple) - and completion_record - ): - rows.append(f"Completion source: `{completion_record[0]}`") - rows.extend( - [ - "", - "| Target | Latest attempt | Bound provenance | Landing | Required qualification | State |", - "| --- | --- | --- | --- | --- | --- |", - ] - ) - for target in assessment["targets"]: - repository = target["repository"] - branch = target["branch"] - pull_url = target["pull_request"] - attempt = ( - f"[pull request]({pull_url})" - if pull_url - else "Authenticated protected-branch completion record" - if target.get("provenance") == "authenticated-completion-record" - else "Not linked" - ) - head_commit = target["head_commit"] - head_repository = target["head_repository"] - head_ref = target["head_ref"] - base_commit = target["base_commit"] - base_repository = target["base_repository"] - base_ref = target["base_ref"] - pull_author = target["pull_author"] - provenance = target["provenance"] - reference_actor = target["reference_actor"] - reference_at = target["reference_at"] - reference_event = target["reference_event"] - approval_at = target["approval_at"] - approval_review = target["approval_review"] - authority_binding = f"; reference event `{reference_event}` at `{reference_at}`" - if approval_at and approval_review: - authority_binding += f"; approval review `{approval_review}` at `{approval_at}`" - if provenance == "authenticated-completion-record": - bound_provenance = "Authenticated lifecycle-writer record; exact commit revalidated on protected branch" - elif ( - head_commit - and head_repository - and head_ref - and base_commit - and base_repository - and base_ref - and pull_author - and provenance - and reference_actor - and reference_at - and reference_event - ): - bound_provenance = ( - f"head [`{head_repository}@{head_ref}`]" - f"(https://github.com/{head_repository}/commit/{head_commit}) " - f"`{head_commit[:12]}` → base " - f"[`{base_repository}@{base_ref}`]" - f"(https://github.com/{base_repository}/commit/{base_commit}) " - f"`{base_commit[:12]}`; `{provenance}`; " - f"author `{pull_author}`; reference actor `{reference_actor}`" - f"{authority_binding}" - ) - else: - bound_provenance = "Pending" - commit = target["commit"] - implementation_commit = target.get("implementation_commit") - if commit and isinstance(implementation_commit, str): - landing = ( - f"completion [`{commit[:12]}`](https://github.com/durable-workflow/{repository}/commit/{commit}); " - f"implementation [`{implementation_commit[:12]}`]" - f"(https://github.com/durable-workflow/{repository}/commit/{implementation_commit})" - ) - elif commit: - landing = f"[`{commit[:12]}`](https://github.com/durable-workflow/{repository}/commit/{commit})" - else: - landing = "Pending" - missing = target["missing_checks"] - required_checks = target["required_checks"] - required_check_runs = target.get("required_check_runs", {}) - check_identity = ", ".join( - ( - f"`{name}` ([check run `{required_check_runs[name]}`]" - f"(https://github.com/durable-workflow/{repository}/actions/runs/{required_check_runs[name]}))" - if name in required_check_runs - else f"`{name}`" - ) - for name in required_checks - ) - qualification_runs = target.get("qualification_runs", []) - cited_runs = [] - for qualification_run in qualification_runs: - if not isinstance(qualification_run, Mapping) or not isinstance(qualification_run.get("run"), int): - continue - run_id = qualification_run["run"] - workflow_name = qualification_run.get("name") - workflow_path = qualification_run.get("path") - workflow_identity = ( - f"`{workflow_name}` (`{workflow_path}`)" - if isinstance(workflow_name, str) and isinstance(workflow_path, str) - else f"`{workflow_path}`" - if isinstance(workflow_path, str) - else f"`{workflow_name}` (authenticated workflow path)" - if isinstance(workflow_name, str) - else "legacy generic qualification" - ) - cited_runs.append( - f"{workflow_identity} [cited run `{run_id}`]" - f"(https://github.com/durable-workflow/{repository}/actions/runs/{run_id})" - ) - run_identity = f"; cited qualification: {', '.join(cited_runs)}" if cited_runs else "" - qualification = ( - "Pending: closing-reference authority changed" - if target["state"] == "pending:closing-reference-changed" - else f"Passed: {check_identity}{run_identity}" - if target["state"] == "complete" - else "Pending: " - + ", ".join(f"`{name}`" for name in missing) - + f"; required: {check_identity}{run_identity}" - if missing - else f"Required: {check_identity}{run_identity}" - ) - rows.append( - f"| `durable-workflow/{repository}@{branch}` | {attempt} | {bound_provenance} | {landing} | " - f"{qualification} | `{target['state']}` |" - ) - rows.extend( - [ - "", - ( - "Every declared target landing and required repository qualification is complete." - if assessment["complete"] - else "The parent remains open until every declared target is complete." - ), - ] - ) - qualification_advancements = assessment.get("_qualification_ledger_advancements", []) - if qualification_advancements: - ledger = { - "advancements": qualification_advancements, - "schema": "durable-workflow.item-qualification-ledger/v1", - } - digest = hashlib.sha256( - json.dumps(ledger, separators=(",", ":"), sort_keys=True).encode("utf-8") - ).hexdigest() - rows.extend(["", f""]) - return "\n".join(rows) + "\n" diff --git a/scripts/current_plan_publication.py b/scripts/current_plan_publication.py deleted file mode 100644 index e0d2dc3..0000000 --- a/scripts/current_plan_publication.py +++ /dev/null @@ -1,772 +0,0 @@ -#!/usr/bin/env python3 -"""Protect current-plan publication dispatch and its approved writer handoff.""" - -from __future__ import annotations - -import argparse -import base64 -import datetime as dt -import hashlib -import hmac -import json -import os -import re -import sys -import urllib.error -import urllib.parse -import urllib.request -from dataclasses import dataclass -from pathlib import Path -from typing import Any, Protocol - -CONTROL_REPOSITORY = "durable-workflow/.github" -AUTHORITY_REF = "main" -OBSERVER_WORKFLOW = "release-plan-observer.yml" -OBSERVER_WORKFLOW_PATH = f".github/workflows/{OBSERVER_WORKFLOW}" -OBSERVER_WORKFLOW_REF = f"{CONTROL_REPOSITORY}/{OBSERVER_WORKFLOW_PATH}@refs/heads/{AUTHORITY_REF}" -CURRENT_PLAN_WORKFLOW = "current-release-plan.yml" -CURRENT_PLAN_WORKFLOW_PATH = f".github/workflows/{CURRENT_PLAN_WORKFLOW}" -CURRENT_PLAN_WORKFLOW_REF = ( - f"{CONTROL_REPOSITORY}/{CURRENT_PLAN_WORKFLOW_PATH}@refs/heads/{AUTHORITY_REF}" -) -CURRENT_PLAN_PATH = "release-plans/current.json" -CURRENT_PLAN_SCHEMA = "durable-workflow.release-plan/v2" -PLAN_TAG_PREFIX = "release-plan/" -BETA_AUTHORIZATION_ENVIRONMENT = "beta-authorization" -GITHUB_API_VERSION = "2022-11-28" -ACTIVE_RUN_STATUSES = ("waiting", "queued", "in_progress", "pending", "requested") -CURRENT_PLAN_RUN_EVENTS = frozenset(("push", "workflow_dispatch")) -RUN_PAGE_SIZE = 100 -RUN_PAGE_LIMIT = 10 -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") -PLAN_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,55}$") -VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?$") -RFC3339_PATTERN = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$") -HANDOFF_DOMAIN = b"durable-workflow.current-plan-writer-approval/v1\0" - - -class CurrentPlanPublicationError(ValueError): - """The current-plan publication authority is absent, mismatched, or ambiguous.""" - - -class ActionsClient(Protocol): - def get(self, path: str) -> Any: ... - - def post(self, path: str, payload: Any | None = None) -> Any: ... - - -@dataclass(frozen=True) -class PlanIdentity: - tag: str - sha256: str - - -@dataclass(frozen=True) -class CandidateIdentity: - repository: str - workflow: str - ref: str - source_sha: str - plan: PlanIdentity - - -@dataclass(frozen=True) -class ActiveRun: - run_id: int - status: str - source_sha: str - created_at: dt.datetime - url: str - - -@dataclass(frozen=True) -class ReconciliationResult: - outcome: str - retained_run_url: str | None - cancelled_run_urls: tuple[str, ...] - - -class GitHubActionsClient: - """A bounded client for exact Actions discovery, cancellation, and dispatch.""" - - def __init__(self, token: str, api_url: str = "https://api.github.com") -> None: - if not token: - raise CurrentPlanPublicationError("current-plan publication GitHub token is absent") - if api_url.rstrip("/") != "https://api.github.com": - raise CurrentPlanPublicationError("current-plan publication GitHub API authority is mismatched") - self.token = token - self.api_url = api_url.rstrip("/") - self.headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "User-Agent": "durable-workflow-current-plan-publication/1", - "X-GitHub-Api-Version": GITHUB_API_VERSION, - } - - def request(self, method: str, path: str, payload: Any | None = None) -> Any: - data = None if payload is None else json.dumps(payload).encode() - request = urllib.request.Request( - f"{self.api_url}{path}", - data=data, - headers=self.headers, - method=method, - ) - try: - with urllib.request.urlopen(request, timeout=30) as response: - raw = response.read() - except urllib.error.HTTPError as error: - detail = error.read(2048).decode(errors="replace").replace(self.token, "") - raise CurrentPlanPublicationError( - f"GitHub {method} {path} failed ({error.code}): {detail}" - ) from error - except urllib.error.URLError as error: - detail = str(error.reason).replace(self.token, "") - raise CurrentPlanPublicationError(f"GitHub {method} {path} failed: {detail}") from error - if not raw: - return None - try: - return json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise CurrentPlanPublicationError(f"GitHub {method} {path} returned invalid JSON") from error - - def get(self, path: str) -> Any: - return self.request("GET", path) - - def post(self, path: str, payload: Any | None = None) -> Any: - return self.request("POST", path, payload) - - -def _identity(value: Any, label: str) -> str: - if not isinstance(value, str) or not value: - raise CurrentPlanPublicationError(f"current-plan publication {label} is absent") - return value - - -def validate_runtime_identity(repository: Any, ref: Any, workflow_ref: Any) -> None: - repository = _identity(repository, "repository") - ref = _identity(ref, "ref") - workflow_ref = _identity(workflow_ref, "workflow identity") - - if repository != CONTROL_REPOSITORY: - raise CurrentPlanPublicationError( - "current-plan publication repository mismatch: " - f"expected {CONTROL_REPOSITORY}, got {repository}" - ) - expected_ref = f"refs/heads/{AUTHORITY_REF}" - if ref != expected_ref: - raise CurrentPlanPublicationError( - f"current-plan publication ref mismatch: expected {expected_ref}, got {ref}" - ) - if workflow_ref != CURRENT_PLAN_WORKFLOW_REF: - raise CurrentPlanPublicationError( - "current-plan publication workflow mismatch: " - f"expected {CURRENT_PLAN_WORKFLOW_REF}, got {workflow_ref}" - ) - - -def validate_observer_runtime_identity(repository: Any, ref: Any, workflow_ref: Any) -> None: - repository = _identity(repository, "observer repository") - ref = _identity(ref, "observer ref") - workflow_ref = _identity(workflow_ref, "observer workflow identity") - expected_ref = f"refs/heads/{AUTHORITY_REF}" - if repository != CONTROL_REPOSITORY or ref != expected_ref or workflow_ref != OBSERVER_WORKFLOW_REF: - raise CurrentPlanPublicationError( - "current-plan observer authority mismatch: " - f"expected repository={CONTROL_REPOSITORY} workflow={OBSERVER_WORKFLOW_REF} ref={expected_ref}" - ) - - -def _positive_integer(value: Any, label: str) -> int: - if isinstance(value, bool): - raise CurrentPlanPublicationError(f"current-plan publication {label} must be a positive integer") - try: - parsed = int(value) - except (TypeError, ValueError) as error: - raise CurrentPlanPublicationError( - f"current-plan publication {label} must be a positive integer" - ) from error - if parsed < 1: - raise CurrentPlanPublicationError(f"current-plan publication {label} must be a positive integer") - return parsed - - -def approved_writer_handoff( - repository: Any, - ref: Any, - workflow_ref: Any, - source_sha: Any, - run_id: Any, - producer_attempt: Any, -) -> str: - """Bind environment-gated job success to one protected workflow run.""" - - validate_runtime_identity(repository, ref, workflow_ref) - source_sha = _identity(source_sha, "source SHA") - if COMMIT_PATTERN.fullmatch(source_sha) is None: - raise CurrentPlanPublicationError("current-plan publication source SHA must be a full lowercase commit") - run_id = _positive_integer(run_id, "run ID") - producer_attempt = _positive_integer(producer_attempt, "approval attempt") - identity = "\0".join( - ( - repository, - ref, - workflow_ref, - source_sha, - str(run_id), - str(producer_attempt), - ) - ).encode() - return hashlib.sha256(HANDOFF_DOMAIN + identity).hexdigest() - - -def validate_approved_writer_handoff( - handoff: Any, - repository: Any, - ref: Any, - workflow_ref: Any, - source_sha: Any, - run_id: Any, - current_attempt: Any, - producer_attempt: Any, -) -> None: - handoff = _identity(handoff, "approved writer handoff") - current_attempt = _positive_integer(current_attempt, "current attempt") - producer_attempt_value = _positive_integer(producer_attempt, "approval attempt") - if producer_attempt_value > current_attempt: - raise CurrentPlanPublicationError("current-plan publication approval attempt is newer than the writer attempt") - expected = approved_writer_handoff( - repository, - ref, - workflow_ref, - source_sha, - run_id, - producer_attempt_value, - ) - if not hmac.compare_digest(handoff, expected): - raise CurrentPlanPublicationError( - "current-plan publication approved writer handoff does not match this workflow run" - ) - - -def _canonical_json(value: Any) -> bytes: - return (json.dumps(value, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - - -def _plan_identity(value: Any, label: str) -> PlanIdentity: - expected_keys = {"schema", "plan", "channel", "foundation", "components", "beta_authorization"} - if not isinstance(value, dict) or set(value) != expected_keys: - raise CurrentPlanPublicationError(f"{label} has an invalid current-plan shape") - plan = value.get("plan") - if value.get("schema") != CURRENT_PLAN_SCHEMA or not isinstance(plan, str) or not PLAN_PATTERN.fullmatch(plan): - raise CurrentPlanPublicationError(f"{label} has an invalid current-plan identity") - if value.get("channel") not in {"alpha", "beta", "rc"}: - raise CurrentPlanPublicationError(f"{label} has an invalid current-plan channel") - foundation = value.get("foundation") - if ( - not isinstance(foundation, dict) - or set(foundation) != {"tag", "commit"} - or not isinstance(foundation.get("tag"), str) - or COMMIT_PATTERN.fullmatch(str(foundation.get("commit", ""))) is None - ): - raise CurrentPlanPublicationError(f"{label} has an invalid current-plan foundation") - components = value.get("components") - if not isinstance(components, dict) or not components: - raise CurrentPlanPublicationError(f"{label} has no current-plan components") - for name, component in components.items(): - if ( - not isinstance(name, str) - or not isinstance(component, dict) - or set(component) != {"version", "commit"} - or not isinstance(component.get("version"), str) - or VERSION_PATTERN.fullmatch(component["version"]) is None - or COMMIT_PATTERN.fullmatch(str(component.get("commit", ""))) is None - ): - raise CurrentPlanPublicationError(f"{label} has an invalid current-plan component") - return PlanIdentity( - tag=f"{PLAN_TAG_PREFIX}{plan}", - sha256=hashlib.sha256(_canonical_json(value)).hexdigest(), - ) - - -def _load_local_candidate( - path: Path, - repository: Any, - ref: Any, - workflow: Any, - source_sha: Any, - plan_tag: Any, - plan_sha256: Any, -) -> CandidateIdentity: - repository = _identity(repository, "target repository") - ref = _identity(ref, "target ref") - workflow = _identity(workflow, "target workflow") - source_sha = _identity(source_sha, "source SHA") - plan_tag = _identity(plan_tag, "current-plan tag") - plan_sha256 = _identity(plan_sha256, "current-plan digest") - if repository != CONTROL_REPOSITORY: - raise CurrentPlanPublicationError("current-plan dispatch target repository is mismatched") - if ref != AUTHORITY_REF: - raise CurrentPlanPublicationError("current-plan dispatch target ref is mismatched") - if workflow != CURRENT_PLAN_WORKFLOW: - raise CurrentPlanPublicationError("current-plan dispatch target workflow is mismatched") - if COMMIT_PATTERN.fullmatch(source_sha) is None: - raise CurrentPlanPublicationError("current-plan dispatch source SHA must be a full lowercase commit") - if ( - not plan_tag.startswith(PLAN_TAG_PREFIX) - or PLAN_PATTERN.fullmatch(plan_tag.removeprefix(PLAN_TAG_PREFIX)) is None - ): - raise CurrentPlanPublicationError("current-plan dispatch plan tag is invalid") - if DIGEST_PATTERN.fullmatch(plan_sha256) is None: - raise CurrentPlanPublicationError("current-plan dispatch plan digest is invalid") - try: - raw = path.read_bytes() - if len(raw) > 64 * 1024: - raise CurrentPlanPublicationError("local current plan exceeds the 64 KiB limit") - value = json.loads(raw) - except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error: - raise CurrentPlanPublicationError(f"cannot read local current plan {path}: {error}") from error - local_identity = _plan_identity(value, "local source revision") - if local_identity != PlanIdentity(plan_tag, plan_sha256): - raise CurrentPlanPublicationError( - "observed current-plan identity does not match the protected source revision" - ) - return CandidateIdentity( - repository=repository, - workflow=workflow, - ref=ref, - source_sha=source_sha, - plan=local_identity, - ) - - -def _workflow_metadata(client: ActionsClient, candidate: CandidateIdentity) -> int: - encoded = urllib.parse.quote(candidate.workflow, safe="") - value = client.get(f"/repos/{candidate.repository}/actions/workflows/{encoded}") - workflow_id = value.get("id") if isinstance(value, dict) else None - expected_html_url = ( - f"https://github.com/{candidate.repository}/blob/{candidate.ref}/{CURRENT_PLAN_WORKFLOW_PATH}" - ) - expected_api_url = ( - f"https://api.github.com/repos/{candidate.repository}/actions/workflows/{workflow_id}" - ) - if ( - not isinstance(value, dict) - or isinstance(workflow_id, bool) - or not isinstance(workflow_id, int) - or workflow_id < 1 - or value.get("path") != CURRENT_PLAN_WORKFLOW_PATH - or value.get("state") != "active" - or value.get("name") != "Current release plan" - or value.get("html_url") != expected_html_url - or value.get("url") != expected_api_url - ): - raise CurrentPlanPublicationError("current-plan workflow API authority is malformed or mismatched") - return workflow_id - - -def _require_authority_ref(client: ActionsClient, candidate: CandidateIdentity) -> None: - value = client.get(f"/repos/{candidate.repository}/git/ref/heads/{candidate.ref}") - target = value.get("object") if isinstance(value, dict) else None - if ( - not isinstance(value, dict) - or value.get("ref") != f"refs/heads/{candidate.ref}" - or not isinstance(target, dict) - or target.get("type") != "commit" - or target.get("sha") != candidate.source_sha - ): - raise CurrentPlanPublicationError( - "current-plan authority ref no longer resolves to the observed source revision" - ) - - -def _active_runs(client: ActionsClient, candidate: CandidateIdentity, workflow_id: int) -> list[ActiveRun]: - runs: list[ActiveRun] = [] - seen: set[int] = set() - for requested_status in ACTIVE_RUN_STATUSES: - encoded_status = urllib.parse.quote(requested_status, safe="") - status_count = 0 - for page in range(1, RUN_PAGE_LIMIT + 1): - path = ( - f"/repos/{candidate.repository}/actions/workflows/{workflow_id}/runs" - f"?branch={candidate.ref}&status={encoded_status}" - f"&per_page={RUN_PAGE_SIZE}&page={page}" - ) - payload = client.get(path) - page_runs = payload.get("workflow_runs") if isinstance(payload, dict) else None - total_count = payload.get("total_count") if isinstance(payload, dict) else None - if ( - not isinstance(page_runs, list) - or isinstance(total_count, bool) - or not isinstance(total_count, int) - or total_count < len(page_runs) - ): - raise CurrentPlanPublicationError("current-plan workflow-runs API response is malformed") - for value in page_runs: - run = _active_run(value, candidate, workflow_id, requested_status) - if run.run_id in seen: - raise CurrentPlanPublicationError("current-plan workflow-runs API contains a duplicate run") - seen.add(run.run_id) - runs.append(run) - status_count += 1 - if len(page_runs) < RUN_PAGE_SIZE or status_count >= total_count: - break - else: - raise CurrentPlanPublicationError( - f"current-plan {requested_status} workflow runs exceed the pagination bound" - ) - return runs - - -def _active_run(value: Any, candidate: CandidateIdentity, workflow_id: int, requested_status: str) -> ActiveRun: - repository = value.get("repository") if isinstance(value, dict) else None - head_repository = value.get("head_repository") if isinstance(value, dict) else None - run_id = value.get("id") if isinstance(value, dict) else None - run_attempt = value.get("run_attempt") if isinstance(value, dict) else None - created_at = value.get("created_at") if isinstance(value, dict) else None - expected_paths = {CURRENT_PLAN_WORKFLOW_PATH, f"{CURRENT_PLAN_WORKFLOW_PATH}@{candidate.ref}"} - expected_url = f"https://github.com/{candidate.repository}/actions/runs/{run_id}" - expected_api_url = f"https://api.github.com/repos/{candidate.repository}/actions/runs/{run_id}" - if ( - not isinstance(value, dict) - or isinstance(run_id, bool) - or not isinstance(run_id, int) - or run_id < 1 - or isinstance(run_attempt, bool) - or not isinstance(run_attempt, int) - or run_attempt < 1 - or value.get("workflow_id") != workflow_id - or value.get("event") not in CURRENT_PLAN_RUN_EVENTS - or value.get("path") not in expected_paths - or value.get("head_branch") != candidate.ref - or not isinstance(repository, dict) - or repository.get("full_name") != candidate.repository - or not isinstance(head_repository, dict) - or head_repository.get("full_name") != candidate.repository - or value.get("html_url") != expected_url - or value.get("url") != expected_api_url - or value.get("status") != requested_status - or value.get("conclusion") is not None - or not isinstance(created_at, str) - or RFC3339_PATTERN.fullmatch(created_at) is None - or COMMIT_PATTERN.fullmatch(str(value.get("head_sha", ""))) is None - ): - raise CurrentPlanPublicationError("active current-plan workflow run is malformed or mismatched") - try: - created_at_value = dt.datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=dt.UTC) - except ValueError as error: - raise CurrentPlanPublicationError("active current-plan workflow run has an invalid timestamp") from error - return ActiveRun( - run_id=run_id, - status=requested_status, - source_sha=value["head_sha"], - created_at=created_at_value, - url=expected_url, - ) - - -def _source_plan_identity(client: ActionsClient, candidate: CandidateIdentity, source_sha: str) -> PlanIdentity: - encoded_path = "/".join(urllib.parse.quote(part, safe="") for part in CURRENT_PLAN_PATH.split("/")) - value = client.get( - f"/repos/{candidate.repository}/contents/{encoded_path}?ref={source_sha}" - ) - encoded = value.get("content") if isinstance(value, dict) else None - if ( - not isinstance(value, dict) - or value.get("type") != "file" - or value.get("path") != CURRENT_PLAN_PATH - or value.get("encoding") != "base64" - or not isinstance(encoded, str) - or len(encoded) > 96 * 1024 - ): - raise CurrentPlanPublicationError("historical current-plan source response is malformed") - try: - raw = base64.b64decode("".join(encoded.split()), validate=True) - if len(raw) > 64 * 1024: - raise CurrentPlanPublicationError("historical current plan exceeds the 64 KiB limit") - plan = json.loads(raw) - except (ValueError, UnicodeDecodeError, json.JSONDecodeError) as error: - raise CurrentPlanPublicationError("historical current-plan source is malformed") from error - return _plan_identity(plan, f"source revision {source_sha}") - - -def _require_older_revision(client: ActionsClient, candidate: CandidateIdentity, source_sha: str) -> None: - value = client.get(f"/repos/{candidate.repository}/compare/{source_sha}...{candidate.source_sha}") - base = value.get("base_commit") if isinstance(value, dict) else None - merge_base = value.get("merge_base_commit") if isinstance(value, dict) else None - ahead_by = value.get("ahead_by") if isinstance(value, dict) else None - behind_by = value.get("behind_by") if isinstance(value, dict) else None - if ( - not isinstance(value, dict) - or value.get("status") != "ahead" - or not isinstance(base, dict) - or base.get("sha") != source_sha - or not isinstance(merge_base, dict) - or merge_base.get("sha") != source_sha - or isinstance(ahead_by, bool) - or not isinstance(ahead_by, int) - or ahead_by < 1 - or behind_by != 0 - ): - raise CurrentPlanPublicationError( - f"active run {source_sha} is not a verified ancestor of {candidate.source_sha}" - ) - - -def _is_unapproved_wait(client: ActionsClient, candidate: CandidateIdentity, run: ActiveRun) -> bool: - if run.status != "waiting": - return False - value = client.get( - f"/repos/{candidate.repository}/actions/runs/{run.run_id}/pending_deployments" - ) - if not isinstance(value, list): - raise CurrentPlanPublicationError( - f"pending deployments for current-plan run {run.run_id} are malformed" - ) - if not value: - return False - if len(value) != 1 or not isinstance(value[0], dict): - raise CurrentPlanPublicationError( - f"current-plan run {run.run_id} has ambiguous pending authorization" - ) - environment = value[0].get("environment") - environment_id = environment.get("id") if isinstance(environment, dict) else None - expected_url = ( - f"https://api.github.com/repos/{candidate.repository}/environments/" - f"{BETA_AUTHORIZATION_ENVIRONMENT}" - ) - if ( - not isinstance(environment, dict) - or isinstance(environment_id, bool) - or not isinstance(environment_id, int) - or environment_id < 1 - or environment.get("name") != BETA_AUTHORIZATION_ENVIRONMENT - or environment.get("url") != expected_url - ): - raise CurrentPlanPublicationError( - f"current-plan run {run.run_id} pending authorization is mismatched" - ) - return True - - -def reconcile_current_plan_dispatch( - client: ActionsClient, - *, - plan_path: Path, - repository: Any, - ref: Any, - workflow: Any, - source_sha: Any, - plan_tag: Any, - plan_sha256: Any, - observer_workflow_ref: Any, -) -> ReconciliationResult: - """Coalesce exact current-plan approval waits without crossing authorization.""" - - validate_observer_runtime_identity(repository, f"refs/heads/{ref}", observer_workflow_ref) - candidate = _load_local_candidate( - plan_path, - repository, - ref, - workflow, - source_sha, - plan_tag, - plan_sha256, - ) - workflow_id = _workflow_metadata(client, candidate) - _require_authority_ref(client, candidate) - runs = _active_runs(client, candidate, workflow_id) - - current_runs = [run for run in runs if run.source_sha == candidate.source_sha] - stale_runs = [run for run in runs if run.source_sha != candidate.source_sha] - source_identities = {candidate.source_sha: candidate.plan} - for run in stale_runs: - if run.source_sha not in source_identities: - source_identities[run.source_sha] = _source_plan_identity(client, candidate, run.source_sha) - _require_older_revision(client, candidate, run.source_sha) - - unapproved: dict[int, bool] = {} - for run in runs: - unapproved[run.run_id] = _is_unapproved_wait(client, candidate, run) - - protected_current = [run for run in current_runs if not unapproved[run.run_id]] - if len(protected_current) > 1: - raise CurrentPlanPublicationError( - "multiple exact-candidate runs may have passed protected authorization; refusing mutation" - ) - if any(not unapproved[run.run_id] for run in stale_runs): - raise CurrentPlanPublicationError( - "an older current-plan run may have passed protected authorization; refusing supersession" - ) - - retained: ActiveRun | None - if protected_current: - retained = protected_current[0] - elif current_runs: - retained = min(current_runs, key=lambda run: (run.created_at, run.run_id)) - else: - retained = None - - cancellations = [ - run - for run in runs - if unapproved[run.run_id] and run is not retained - ] - _require_authority_ref(client, candidate) - cancelled_urls: list[str] = [] - for run in sorted(cancellations, key=lambda item: (item.created_at, item.run_id)): - if not _is_unapproved_wait(client, candidate, run): - raise CurrentPlanPublicationError( - f"current-plan run {run.run_id} is no longer awaiting protected authorization" - ) - client.post(f"/repos/{candidate.repository}/actions/runs/{run.run_id}/cancel") - cancelled_urls.append(run.url) - plan = source_identities[run.source_sha] - print( - "Cancelled verified unapproved current-plan wait " - f"{run.url} (source={run.source_sha} plan={plan.tag} plan_sha256={plan.sha256})." - ) - - if retained is not None: - print( - "Current-plan publication is an idempotent no-op; retained " - f"{retained.url} for repository={candidate.repository} workflow={candidate.workflow} " - f"ref={candidate.ref} source={candidate.source_sha} plan={candidate.plan.tag} " - f"plan_sha256={candidate.plan.sha256}." - ) - return ReconciliationResult("retained", retained.url, tuple(cancelled_urls)) - - client.post( - f"/repos/{candidate.repository}/actions/workflows/{workflow_id}/dispatches", - {"ref": candidate.ref}, - ) - print( - "Dispatched exact current-plan candidate " - f"repository={candidate.repository} workflow={candidate.workflow} ref={candidate.ref} " - f"source={candidate.source_sha} plan={candidate.plan.tag} " - f"plan_sha256={candidate.plan.sha256}." - ) - return ReconciliationResult("dispatched", None, tuple(cancelled_urls)) - - -def write_github_output(path: Path, values: dict[str, str | int]) -> None: - with path.open("a", encoding="utf-8") as output: - for key, value in values.items(): - output.write(f"{key}={value}\n") - - -def add_runtime_arguments(parser: argparse.ArgumentParser) -> None: - parser.add_argument("--repository", required=True) - parser.add_argument("--ref", required=True) - parser.add_argument("--workflow-ref", required=True) - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - validate = subparsers.add_parser( - "validate-runtime", - help="fail unless the protected current-plan workflow is running on its exact authority", - ) - add_runtime_arguments(validate) - create_handoff = subparsers.add_parser( - "create-approved-writer-handoff", - help="bind a completed protected-environment job to its exact workflow run", - ) - add_runtime_arguments(create_handoff) - create_handoff.add_argument("--source-sha", required=True) - create_handoff.add_argument("--run-id", required=True) - create_handoff.add_argument("--run-attempt", required=True) - create_handoff.add_argument("--github-output", required=True, type=Path) - validate_handoff = subparsers.add_parser( - "validate-approved-writer-handoff", - help="fail unless the privileged writer follows an exact approved job", - ) - add_runtime_arguments(validate_handoff) - validate_handoff.add_argument("--source-sha", required=True) - validate_handoff.add_argument("--run-id", required=True) - validate_handoff.add_argument("--current-attempt", required=True) - validate_handoff.add_argument("--producer-attempt", required=True) - validate_handoff.add_argument("--handoff", required=True) - reconcile = subparsers.add_parser( - "reconcile-dispatch", - help="coalesce unapproved exact-candidate waits before dispatch", - ) - reconcile.add_argument("--plan", required=True, type=Path) - reconcile.add_argument("--repository", required=True) - reconcile.add_argument("--ref", required=True) - reconcile.add_argument("--workflow", required=True) - reconcile.add_argument("--observer-workflow-ref", required=True) - reconcile.add_argument("--source-sha", required=True) - reconcile.add_argument("--plan-tag", required=True) - reconcile.add_argument("--plan-sha256", required=True) - reconcile.add_argument("--github-output", type=Path) - return parser - - -def main(argv: list[str] | None = None) -> int: - arguments = build_parser().parse_args(argv) - try: - if arguments.command == "validate-runtime": - validate_runtime_identity( - arguments.repository, - arguments.ref, - arguments.workflow_ref, - ) - elif arguments.command == "create-approved-writer-handoff": - handoff = approved_writer_handoff( - arguments.repository, - arguments.ref, - arguments.workflow_ref, - arguments.source_sha, - arguments.run_id, - arguments.run_attempt, - ) - write_github_output( - arguments.github_output, - { - "handoff": handoff, - "producer-attempt": _positive_integer(arguments.run_attempt, "approval attempt"), - }, - ) - elif arguments.command == "validate-approved-writer-handoff": - validate_approved_writer_handoff( - arguments.handoff, - arguments.repository, - arguments.ref, - arguments.workflow_ref, - arguments.source_sha, - arguments.run_id, - arguments.current_attempt, - arguments.producer_attempt, - ) - elif arguments.command == "reconcile-dispatch": - token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") or "" - result = reconcile_current_plan_dispatch( - GitHubActionsClient(token, os.environ.get("GITHUB_API_URL", "https://api.github.com")), - plan_path=arguments.plan, - repository=arguments.repository, - ref=arguments.ref, - workflow=arguments.workflow, - source_sha=arguments.source_sha, - plan_tag=arguments.plan_tag, - plan_sha256=arguments.plan_sha256, - observer_workflow_ref=arguments.observer_workflow_ref, - ) - if arguments.github_output is not None: - write_github_output( - arguments.github_output, - { - "outcome": result.outcome, - "retained-run-url": result.retained_run_url or "", - "cancelled-run-count": len(result.cancelled_run_urls), - }, - ) - except CurrentPlanPublicationError as error: - print(error, file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/handoff_recovery.py b/scripts/handoff_recovery.py deleted file mode 100644 index 3e489a9..0000000 --- a/scripts/handoff_recovery.py +++ /dev/null @@ -1,428 +0,0 @@ -#!/usr/bin/env python3 -"""Create, select, and validate attempt-bound verifier handoffs.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import re -import sys -from dataclasses import dataclass -from pathlib import Path -from typing import Any - -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.beta_candidate import CandidateError, PublicClient, PublicInfrastructureError, canonical_json - -HANDOFF_SCHEMA = "durable-workflow.verifier-handoff/v1" -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -REPOSITORY_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$") -MAX_HANDOFF_BYTES = 64 * 1024 -MAX_ARTIFACTS = 1_000 - - -class HandoffError(RuntimeError): - """A verifier handoff cannot be recovered without changing its identity.""" - - -@dataclass(frozen=True) -class HandoffKind: - artifact_prefix: str - filenames: tuple[str, ...] - optional_filenames: tuple[str, ...] = () - - -KINDS = { - "candidate": HandoffKind( - "beta-candidate-verification", - ("candidate.json", "verification.json"), - ), - "release-plan-observation": HandoffKind( - "release-plan-observation", - ( - "release-plan.json", - "release-preparation.json", - "candidate-verifier-input.json", - "release-state.json", - ), - ("verification.json",), - ), -} - - -@dataclass(frozen=True) -class SelectedArtifact: - artifact_id: int - name: str - producer_attempt: int - - -def positive_integer(value: str | int, label: str) -> int: - try: - parsed = int(value) - except (TypeError, ValueError) as error: - raise HandoffError(f"{label} must be a positive integer") from error - if parsed < 1 or isinstance(value, bool): - raise HandoffError(f"{label} must be a positive integer") - return parsed - - -def validate_runtime_identity(repository: str, workflow_ref: str, source_sha: str) -> None: - if not REPOSITORY_PATTERN.fullmatch(repository): - raise HandoffError("handoff repository has an invalid identity") - if not workflow_ref.startswith(f"{repository}/.github/workflows/") or "@" not in workflow_ref: - raise HandoffError("handoff workflow ref has an invalid identity") - if len(workflow_ref) > 512 or any(character in workflow_ref for character in "\r\n"): - raise HandoffError("handoff workflow ref has an invalid identity") - if not COMMIT_PATTERN.fullmatch(source_sha): - raise HandoffError("handoff source SHA must be a full lowercase commit") - - -def artifact_name(kind: str, run_id: int, run_attempt: int) -> str: - contract = KINDS[kind] - return f"{contract.artifact_prefix}-{run_id}-{run_attempt}" - - -def hash_file(path: Path) -> str: - if path.is_symlink() or not path.is_file(): - raise HandoffError(f"handoff file is missing or is not a regular file: {path.name}") - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def create_handoff( - kind: str, - directory: Path, - output: Path, - *, - repository: str, - workflow_ref: str, - source_sha: str, - run_id: int, - run_attempt: int, -) -> dict[str, Any]: - validate_runtime_identity(repository, workflow_ref, source_sha) - run_id = positive_integer(run_id, "handoff run ID") - run_attempt = positive_integer(run_attempt, "handoff run attempt") - contract = KINDS[kind] - files = {filename: hash_file(directory / filename) for filename in contract.filenames} - files.update( - { - filename: hash_file(directory / filename) - for filename in contract.optional_filenames - if (directory / filename).exists() - } - ) - handoff = { - "schema": HANDOFF_SCHEMA, - "kind": kind, - "artifact_name": artifact_name(kind, run_id, run_attempt), - "producer": { - "repository": repository, - "workflow_ref": workflow_ref, - "source_sha": source_sha, - "run_id": run_id, - "run_attempt": run_attempt, - }, - "files": files, - } - output.write_bytes(canonical_json(handoff)) - return handoff - - -def _load_handoff(path: Path) -> tuple[dict[str, Any], bytes]: - try: - raw = path.read_bytes() - except OSError as error: - raise HandoffError(f"cannot read verifier handoff: {error}") from error - if len(raw) > MAX_HANDOFF_BYTES: - raise HandoffError("verifier handoff exceeds the 64 KiB limit") - try: - handoff = json.loads(raw) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise HandoffError("verifier handoff is not valid JSON") from error - if not isinstance(handoff, dict): - raise HandoffError("verifier handoff must be a JSON object") - return handoff, raw - - -def validate_handoff( - kind: str, - directory: Path, - manifest: Path, - *, - repository: str, - workflow_ref: str, - source_sha: str, - run_id: int, - current_attempt: int, - producer_attempt: int, -) -> dict[str, Any]: - validate_runtime_identity(repository, workflow_ref, source_sha) - run_id = positive_integer(run_id, "handoff run ID") - current_attempt = positive_integer(current_attempt, "current run attempt") - producer_attempt = positive_integer(producer_attempt, "handoff producer attempt") - if producer_attempt > current_attempt: - raise HandoffError("handoff producer attempt is newer than the recorder attempt") - - handoff, raw = _load_handoff(manifest) - if raw != canonical_json(handoff): - raise HandoffError("verifier handoff is not canonical JSON") - if set(handoff) != {"schema", "kind", "artifact_name", "producer", "files"}: - raise HandoffError("verifier handoff has unexpected or missing fields") - if handoff["schema"] != HANDOFF_SCHEMA or handoff["kind"] != kind: - raise HandoffError("verifier handoff has a mismatched contract identity") - expected_name = artifact_name(kind, run_id, producer_attempt) - if handoff["artifact_name"] != expected_name: - raise HandoffError("verifier handoff artifact name does not bind the selected producing attempt") - - producer = handoff["producer"] - if not isinstance(producer, dict) or set(producer) != { - "repository", - "workflow_ref", - "source_sha", - "run_id", - "run_attempt", - }: - raise HandoffError("verifier handoff has an invalid producer identity") - expected_producer = { - "repository": repository, - "workflow_ref": workflow_ref, - "source_sha": source_sha, - "run_id": run_id, - "run_attempt": producer_attempt, - } - if producer != expected_producer: - raise HandoffError("verifier handoff producer identity does not match the recorder selection") - - contract = KINDS[kind] - files = handoff["files"] - required_files = set(contract.filenames) - optional_files = set(contract.optional_filenames) - if ( - not isinstance(files, dict) - or not required_files.issubset(files) - or not set(files).issubset(required_files | optional_files) - ): - raise HandoffError("verifier handoff file set does not match its contract") - expected_entries = {*files, manifest.name} - try: - actual_entries = {path.name for path in directory.iterdir()} - except OSError as error: - raise HandoffError(f"cannot inspect verifier handoff directory: {error}") from error - if actual_entries != expected_entries: - raise HandoffError("verifier handoff artifact contains unexpected or missing files") - for filename in files: - digest = files[filename] - if not isinstance(digest, str) or not re.fullmatch(r"[0-9a-f]{64}", digest): - raise HandoffError(f"verifier handoff has an invalid digest for {filename}") - if hash_file(directory / filename) != digest: - raise HandoffError(f"verifier handoff digest does not match {filename}") - return handoff - - -def list_run_artifacts(client: PublicClient, repository: str, run_id: int) -> list[dict[str, Any]]: - run_id = positive_integer(run_id, "handoff run ID") - if not REPOSITORY_PATTERN.fullmatch(repository): - raise HandoffError("handoff repository has an invalid identity") - artifacts: list[dict[str, Any]] = [] - expected_total: int | None = None - page = 1 - while True: - url = f"https://api.github.com/repos/{repository}/actions/runs/{run_id}/artifacts?per_page=100&page={page}" - payload = client.json(url) - if not isinstance(payload, dict): - raise HandoffError("GitHub Actions artifact listing is not a JSON object") - total = payload.get("total_count") - page_artifacts = payload.get("artifacts") - if type(total) is not int or total < 0 or not isinstance(page_artifacts, list): - raise HandoffError("GitHub Actions artifact listing has an invalid shape") - if expected_total is None: - expected_total = total - if expected_total > MAX_ARTIFACTS: - raise HandoffError("GitHub Actions artifact listing exceeds the recovery bound") - elif total != expected_total: - raise HandoffError("GitHub Actions artifact listing changed during recovery") - if not all(isinstance(artifact, dict) for artifact in page_artifacts): - raise HandoffError("GitHub Actions artifact listing contains an invalid artifact") - artifacts.extend(page_artifacts) - if len(artifacts) > expected_total: - raise HandoffError("GitHub Actions artifact listing contains duplicate pagination evidence") - if len(artifacts) == expected_total: - break - if not page_artifacts: - raise HandoffError("GitHub Actions artifact listing ended before its declared total") - page += 1 - return artifacts - - -def select_handoff_artifact( - kind: str, - artifacts: list[dict[str, Any]], - *, - run_id: int, - current_attempt: int, - producer_attempt: int, - source_sha: str, -) -> SelectedArtifact: - run_id = positive_integer(run_id, "handoff run ID") - current_attempt = positive_integer(current_attempt, "current run attempt") - producer_attempt = positive_integer(producer_attempt, "handoff producer attempt") - if producer_attempt > current_attempt: - raise HandoffError("handoff producer attempt is newer than the recorder attempt") - if not COMMIT_PATTERN.fullmatch(source_sha): - raise HandoffError("handoff source SHA must be a full lowercase commit") - prefix = KINDS[kind].artifact_prefix - pattern = re.compile(rf"^{re.escape(prefix)}-{run_id}-([1-9][0-9]*)$") - by_attempt: dict[int, list[dict[str, Any]]] = {} - for artifact in artifacts: - name = artifact.get("name") - match = pattern.fullmatch(name) if isinstance(name, str) else None - if match is None: - continue - attempt = int(match.group(1)) - if attempt > current_attempt: - raise HandoffError("verifier handoff artifact names a future producing attempt") - workflow_run = artifact.get("workflow_run") - if ( - not isinstance(workflow_run, dict) - or workflow_run.get("id") != run_id - or workflow_run.get("head_sha") != source_sha - ): - raise HandoffError("verifier handoff artifact has a mismatched workflow-run identity") - by_attempt.setdefault(attempt, []).append(artifact) - - if not by_attempt: - raise HandoffError("no verifier handoff artifact exists for this workflow run") - if producer_attempt not in by_attempt: - raise HandoffError(f"no verifier handoff artifact exists for producing attempt {producer_attempt}") - if producer_attempt != max(by_attempt): - raise HandoffError("retained producer attempt does not identify the newest verifier handoff") - selected = by_attempt[producer_attempt] - if len(selected) != 1: - raise HandoffError(f"verifier handoff attempt {producer_attempt} is ambiguous") - artifact = selected[0] - if artifact.get("expired") is not False: - raise HandoffError(f"verifier handoff attempt {producer_attempt} is expired or has unknown retention state") - artifact_id = artifact.get("id") - if type(artifact_id) is not int or artifact_id < 1: - raise HandoffError("verifier handoff artifact has an invalid ID") - return SelectedArtifact(artifact_id, artifact_name(kind, run_id, producer_attempt), producer_attempt) - - -def write_github_output(path: Path, values: dict[str, str | int]) -> None: - with path.open("a", encoding="utf-8") as output: - for key, value in values.items(): - output.write(f"{key}={value}\n") - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - - create = subparsers.add_parser("create", help="create an attempt-bound handoff manifest") - create.add_argument("kind", choices=KINDS) - create.add_argument("output", type=Path) - create.add_argument("--directory", type=Path, required=True) - create.add_argument("--repository", required=True) - create.add_argument("--workflow-ref", required=True) - create.add_argument("--source-sha", required=True) - create.add_argument("--run-id", required=True) - create.add_argument("--run-attempt", required=True) - create.add_argument("--github-output", type=Path) - - select = subparsers.add_parser("select", help="select the newest exact handoff from this workflow run") - select.add_argument("kind", choices=KINDS) - select.add_argument("--repository", required=True) - select.add_argument("--source-sha", required=True) - select.add_argument("--run-id", required=True) - select.add_argument("--run-attempt", required=True) - select.add_argument("--producer-attempt", required=True) - select.add_argument("--github-output", type=Path, required=True) - - validate = subparsers.add_parser("validate", help="validate downloaded handoff identity and bytes") - validate.add_argument("kind", choices=KINDS) - validate.add_argument("manifest", type=Path) - validate.add_argument("--directory", type=Path, required=True) - validate.add_argument("--repository", required=True) - validate.add_argument("--workflow-ref", required=True) - validate.add_argument("--source-sha", required=True) - validate.add_argument("--run-id", required=True) - validate.add_argument("--current-attempt", required=True) - validate.add_argument("--producer-attempt", required=True) - return parser - - -def main() -> int: - arguments = build_parser().parse_args() - try: - if arguments.command == "create": - result = create_handoff( - arguments.kind, - arguments.directory, - arguments.output, - repository=arguments.repository, - workflow_ref=arguments.workflow_ref, - source_sha=arguments.source_sha, - run_id=arguments.run_id, - run_attempt=arguments.run_attempt, - ) - if arguments.github_output is not None: - write_github_output( - arguments.github_output, - { - "artifact_name": result["artifact_name"], - "producer_attempt": result["producer"]["run_attempt"], - }, - ) - print(json.dumps(result, sort_keys=True)) - elif arguments.command == "select": - token = os.environ.get("GITHUB_TOKEN") - if not token: - raise HandoffError("GITHUB_TOKEN is required to select a verifier handoff") - artifacts = list_run_artifacts(PublicClient(token), arguments.repository, arguments.run_id) - selected = select_handoff_artifact( - arguments.kind, - artifacts, - run_id=arguments.run_id, - current_attempt=arguments.run_attempt, - producer_attempt=arguments.producer_attempt, - source_sha=arguments.source_sha, - ) - values = { - "artifact_id": selected.artifact_id, - "artifact_name": selected.name, - "producer_attempt": selected.producer_attempt, - } - write_github_output(arguments.github_output, values) - print(json.dumps(values, sort_keys=True)) - else: - result = validate_handoff( - arguments.kind, - arguments.directory, - arguments.manifest, - repository=arguments.repository, - workflow_ref=arguments.workflow_ref, - source_sha=arguments.source_sha, - run_id=arguments.run_id, - current_attempt=arguments.current_attempt, - producer_attempt=arguments.producer_attempt, - ) - print(json.dumps(result, sort_keys=True)) - except PublicInfrastructureError as error: - print(f"verifier handoff infrastructure failed: {error}", file=sys.stderr) - return 75 - except CandidateError as error: - print(f"verifier handoff API failed: {error}", file=sys.stderr) - return 1 - except HandoffError as error: - print(f"verifier handoff error: {error}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/issue_authority.py b/scripts/issue_authority.py deleted file mode 100644 index cee90cf..0000000 --- a/scripts/issue_authority.py +++ /dev/null @@ -1,5637 +0,0 @@ -#!/usr/bin/env python3 -"""Validate and operate the GitHub-authoritative public product backlog.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import re -import sys -import time -import urllib.error -import urllib.parse -import urllib.request -from collections.abc import Collection, Mapping, Sequence -from datetime import UTC, datetime -from pathlib import Path -from typing import Any - -from jsonschema import Draft202012Validator, FormatChecker - -# GitHub Actions invokes this file directly from the repository root. In that -# mode Python adds scripts/, rather than the repository root, to sys.path. -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts import cross_repository_lifecycle -from scripts.beta_candidate import COMPONENTS - -POLICY_SCHEMA = "durable-workflow.github-issue-authority/v1" -BACKLOG_SCHEMA = "durable-workflow.github-beta-backlog/v1" -INTAKE_SCHEMA = "durable-workflow.github-issue-intake/v11" -LEGACY_TARGET_SCHEMA = "durable-workflow.legacy-cross-repository-targets/v5" -MARKER_PATTERN = re.compile(r"") -WORK_MARKER_PATTERN = re.compile(r"") -CONSOLIDATED_FINDING_PATTERN = re.compile( - r"" -) -LEGACY_TARGET_HEADING = "### Affected public repositories" -UNBLOCK_CONTEXT_START = "" -UNBLOCK_CONTEXT_END = "" -UNBLOCK_CONTEXT_MARKERS = (UNBLOCK_CONTEXT_START, UNBLOCK_CONTEXT_END) -NON_PUBLIC_CONTEXT_PATTERNS = ( - re.compile(r"(?\]]+"), - re.compile(r"\b(?:localhost|127\.0\.0\.1)(?::[0-9]+)?\b", re.I), -) -SUPERSESSION_EVIDENCE_MARKER = "" -FROZEN_LIFECYCLE_EVIDENCE_MARKER = "" -PUBLIC_LIFECYCLE_MARKER = "" -PUBLIC_RETIREMENT_RECORD_MARKER = "" -PUBLIC_LIFECYCLE_PROJECTION_SCHEMA = "durable-workflow.public-lifecycle-projection/v1" -SUPERSESSION_ACTIVATION_CONTEXT_PREFIX = "issue-authority/prerelease-supersession" -SUPERSESSION_ACTIVATION_DESCRIPTION_PREFIX = "sha256:" -GITHUB_ACTIONS_BOT_ID = 41_898_282 -GITHUB_ACTIONS_BOT_LOGIN = "github-actions[bot]" -SUPERSEDED_STATUS_LABEL = "status:superseded" -STATUS_LABELS = { - "status:triage", - "status:ready", - "status:in-progress", - "status:blocked", - "status:done", - SUPERSEDED_STATUS_LABEL, -} -OPEN_STATUS_LABELS = STATUS_LABELS - {"status:done", SUPERSEDED_STATUS_LABEL} -COMPLETION_REQUIRED_LABEL = "completion:evidence-required" -COMPLETION_VERIFIED_LABEL = "completion:evidence-verified" -COMPLETION_LABELS = {COMPLETION_REQUIRED_LABEL, COMPLETION_VERIFIED_LABEL} -KIND_LABELS = {"kind:defect", "kind:feature", "kind:release-blocker", "kind:cross-repository"} -PUBLIC_EXECUTION_STATES = { - "blocked", - "built", - "claimed", - "completed", - "failed", - "integrated", - "integrating", - "pending", - "superseded", -} -PUBLIC_CONDITIONS = { - "dependency-pending": "A required public dependency or decision is still pending.", - "qualification-failed": "Required public qualification has not completed successfully.", - "release-evidence-pending": "Changes have landed; release or completion evidence is still pending.", -} -PRIORITY_LABELS = {"priority:P0", "priority:P1", "priority:P2", "priority:P3", "priority:untriaged"} -CLASSIFICATION_LABELS = {"beta:blocker", "beta:compatible", "post-2.0"} -OWNER_LABELS = { - ".github": "repo:github-control-plane", - "workflow": "repo:workflow", - "waterline": "repo:waterline", - "server": "repo:server", - "cli": "repo:cli", - "ai": "repo:ai", - "sample-app": "repo:sample-app", - "sdk-php": "repo:sdk-php", - "sdk-python": "repo:sdk-python", - "sdk-rust": "repo:sdk-rust", - "durable-workflow.github.io": "repo:documentation", -} -GITHUB_API_ATTEMPTS = 4 -GITHUB_API_RETRY_SECONDS = 2.0 -PRODUCT_TRAIN_IDENTIFIER_PATTERN = re.compile( - r"^(?P[0-9]+\.[0-9]+\.[0-9]+)-(?Palpha|beta|rc)\.(?P[0-9]+)$" -) - -ISSUE_INTAKE_QUERY = """ -query IssueIntake($owner: String!, $repository: String!, $cursor: String) { - repository(owner: $owner, name: $repository) { - issues( - first: 100 - after: $cursor - orderBy: {field: CREATED_AT, direction: ASC} - states: [OPEN, CLOSED] - ) { - nodes { - number - createdAt - closedAt - lastEditedAt - updatedAt - url - state - stateReason - author { login } - milestone { title } - labels(first: 100) { - nodes { name } - pageInfo { hasNextPage } - } - timelineItems( - last: 100 - itemTypes: [CLOSED_EVENT, LABELED_EVENT, RENAMED_TITLE_EVENT, REOPENED_EVENT, UNLABELED_EVENT] - ) { - nodes { - __typename - ... on ClosedEvent { createdAt } - ... on LabeledEvent { - createdAt - actor { login } - label { name } - } - ... on UnlabeledEvent { - createdAt - actor { login } - label { name } - } - ... on RenamedTitleEvent { - createdAt - } - ... on ReopenedEvent { createdAt } - } - } - } - pageInfo { - hasNextPage - endCursor - } - } - } -} -""" - -ISSUE_REVISION_QUERY = """ -query IssueRevision($owner: String!, $repository: String!, $number: Int!) { - repository(owner: $owner, name: $repository) { - issue(number: $number) { - number - title - body - createdAt - closedAt - lastEditedAt - updatedAt - url - state - stateReason - author { login } - milestone { title } - labels(first: 100) { - nodes { name } - pageInfo { hasNextPage } - } - timelineItems( - last: 100 - itemTypes: [CLOSED_EVENT, LABELED_EVENT, RENAMED_TITLE_EVENT, REOPENED_EVENT, UNLABELED_EVENT] - ) { - nodes { - __typename - ... on ClosedEvent { createdAt } - ... on LabeledEvent { - createdAt - actor { login } - label { name } - } - ... on UnlabeledEvent { - createdAt - actor { login } - label { name } - } - ... on RenamedTitleEvent { - createdAt - } - ... on ReopenedEvent { createdAt } - } - } - } - } -} -""" - -PULL_REQUEST_METADATA_QUERY = """ -query PullRequestMetadata($owner: String!, $repository: String!, $cursor: String) { - repository(owner: $owner, name: $repository) { - pullRequests( - first: 100 - after: $cursor - orderBy: {field: CREATED_AT, direction: ASC} - states: [OPEN, CLOSED, MERGED] - ) { - nodes { - number - createdAt - closedAt - mergedAt - updatedAt - url - state - } - pageInfo { - hasNextPage - endCursor - } - } - } -} -""" - -CLOSING_REFERENCE_QUERY = """ -query ClosingReferences($owner: String!, $repository: String!, $number: Int!, $cursor: String) { - repository(owner: $owner, name: $repository) { - issue(number: $number) { - timelineItems(first: 100, after: $cursor, itemTypes: [CROSS_REFERENCED_EVENT]) { - nodes { - __typename - ... on CrossReferencedEvent { - actor { login } - id - referencedAt - source { - __typename - ... on PullRequest { - number - repository { nameWithOwner } - url - } - } - willCloseTarget - } - } - pageInfo { - hasNextPage - endCursor - } - } - } - } -} -""" - - -class AuthorityError(RuntimeError): - """The public issue-authority contract cannot be satisfied.""" - - -class LifecycleAuditError(AuthorityError): - """Lifecycle reconciliation applied safe changes but retained isolated failures.""" - - def __init__(self, message: str, evidence: Mapping[str, Any]) -> None: - super().__init__(message) - self.evidence = dict(evidence) - - -def _load_json(path: Path, label: str) -> dict[str, Any]: - try: - value = json.loads(path.read_text(encoding="utf-8")) - except (OSError, json.JSONDecodeError) as error: - raise AuthorityError(f"cannot read {label} {path}: {error}") from error - if not isinstance(value, dict): - raise AuthorityError(f"{label} {path} must contain a JSON object") - return value - - -def _validate_schema(instance: dict[str, Any], schema: dict[str, Any], label: str) -> None: - validator = Draft202012Validator(schema, format_checker=FormatChecker()) - errors = sorted(validator.iter_errors(instance), key=lambda error: list(error.absolute_path)) - if not errors: - return - error = errors[0] - location = ".".join(str(part) for part in error.absolute_path) or "" - raise AuthorityError(f"{label} schema validation failed at {location}: {error.message}") - - -def _public_safe(values: Sequence[str]) -> None: - for value in values: - if any(marker in value for marker in UNBLOCK_CONTEXT_MARKERS): - raise AuthorityError("selective backlog contains a reserved unblock condition marker") - for pattern in NON_PUBLIC_CONTEXT_PATTERNS: - match = pattern.search(value) - if match: - raise AuthorityError(f"selective backlog contains non-public context matching {match.group(0)!r}") - - -def load_public_lifecycle_projection( - path: Path, - policy: Mapping[str, Any], - actor: str | None, -) -> tuple[dict[tuple[str, int], dict[str, Any]], list[str]]: - """Validate an authenticated, public-safe execution-state projection issue by issue.""" - - projection_actors = {str(value).casefold() for value in policy["lifecycle"]["projection_actors"]} - if not isinstance(actor, str) or actor.casefold() not in projection_actors: - raise AuthorityError("public lifecycle projection actor is not allowlisted") - payload = _load_json(path, "public lifecycle projection") - if set(payload) != {"generated_at", "issues", "schema"}: - raise AuthorityError("public lifecycle projection envelope has unexpected fields") - if payload.get("schema") != PUBLIC_LIFECYCLE_PROJECTION_SCHEMA: - raise AuthorityError("public lifecycle projection uses an unsupported schema") - generated_at = _parse_timestamp(payload.get("generated_at"), "projection generation timestamp") - raw_issues = payload.get("issues") - if not isinstance(raw_issues, list) or len(raw_issues) > 1000: - raise AuthorityError("public lifecycle projection has an invalid issue bound") - - admitted_repositories = set(policy["repositories"]) - projections: dict[tuple[str, int], dict[str, Any]] = {} - failures: list[str] = [] - quarantined: set[tuple[str, int]] = set() - for index, raw in enumerate(raw_issues): - if not isinstance(raw, Mapping): - failures.append(f"projection entry {index} is not an object") - continue - repository = raw.get("repository") - number = raw.get("number") - identity = ( - (repository, number) - if isinstance(repository, str) and type(number) is int and number > 0 - else None - ) - location = ( - f"{repository}#{number}" - if identity is not None and repository in admitted_repositories - else f"projection entry {index}" - ) - allowed_fields = { - "completion_evidence", - "implementation_source", - "number", - "public_condition", - "repository", - "state", - "transition_at", - "verified_release", - } - try: - if identity is None or repository not in admitted_repositories: - raise AuthorityError("has an invalid public issue identity") - if set(raw) - allowed_fields: - raise AuthorityError("has unexpected fields") - state = raw.get("state") - if state not in PUBLIC_EXECUTION_STATES: - raise AuthorityError("has an unsupported execution state") - transition_at = _parse_timestamp(raw.get("transition_at"), "projection transition timestamp") - if transition_at > generated_at: - raise AuthorityError("has a transition after the projection generation time") - condition = raw.get("public_condition") - if state in {"blocked", "failed"}: - if condition not in {"dependency-pending", "qualification-failed"}: - raise AuthorityError("must select a bounded public blocker condition") - elif state == "integrated": - if condition not in {None, "release-evidence-pending"}: - raise AuthorityError("has an incompatible public condition") - condition = "release-evidence-pending" - elif condition is not None: - raise AuthorityError("has a public condition outside a conditional state") - completion_evidence = raw.get("completion_evidence") - if state == "completed": - if completion_evidence != "verified": - raise AuthorityError("cannot complete without verified public evidence") - elif completion_evidence is not None: - raise AuthorityError("has completion evidence outside the completed state") - implementation_source = raw.get("implementation_source") - verified_release = raw.get("verified_release") - if implementation_source is not None or verified_release is not None: - if state != "completed" or completion_evidence != "verified": - raise AuthorityError("has release completion evidence outside the completed state") - if ( - not isinstance(implementation_source, str) - or re.fullmatch(r"[0-9a-f]{40}", implementation_source) is None - ): - raise AuthorityError("has an invalid implementation source") - if not isinstance(verified_release, Mapping) or set(verified_release) != { - "repository", - "source_shas", - "version", - }: - raise AuthorityError("has malformed verified release evidence") - release_repository = verified_release.get("repository") - release_version = verified_release.get("version") - source_shas = verified_release.get("source_shas") - if ( - not isinstance(release_repository, str) - or release_repository not in admitted_repositories - or not isinstance(release_version, str) - or re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._+-]{0,99}", release_version) is None - or not isinstance(source_shas, list) - or not 1 <= len(source_shas) <= 1000 - or any( - not isinstance(source_sha, str) - or re.fullmatch(r"[0-9a-f]{40}", source_sha) is None - for source_sha in source_shas - ) - or len(source_shas) != len(set(source_shas)) - ): - raise AuthorityError("has invalid verified release identity") - if implementation_source not in source_shas: - raise AuthorityError("verified release does not contain the bound implementation source") - if identity in projections or identity in quarantined: - projections.pop(identity, None) - quarantined.add(identity) - raise AuthorityError("is duplicated and was quarantined") - projections[identity] = { - "completion_evidence": completion_evidence, - "implementation_source": implementation_source, - "public_condition": condition, - "state": state, - "transition_at": transition_at.astimezone(UTC).isoformat().replace("+00:00", "Z"), - "verified_release": dict(verified_release) if verified_release is not None else None, - } - except AuthorityError as error: - failures.append(f"{location} {error}") - return projections, failures - - -def validate_contract( - policy: dict[str, Any], - backlog: dict[str, Any], - policy_schema: dict[str, Any], - backlog_schema: dict[str, Any], -) -> None: - _validate_schema(policy, policy_schema, "issue-authority policy") - _validate_schema(backlog, backlog_schema, "selective backlog") - if policy.get("schema") != POLICY_SCHEMA or backlog.get("schema") != BACKLOG_SCHEMA: - raise AuthorityError("issue-authority documents use an unsupported schema") - if policy.get("state_direction") != "github-to-mirrors": - raise AuthorityError("issue state must flow from GitHub to consumers only") - - intake = policy["intake"] - trusted_actors = intake["trusted_actors"] - if trusted_actors != list(dict.fromkeys(trusted_actors)) or set(trusted_actors) != { - "durable-workflow-ops", - "rmcdaniel", - }: - raise AuthorityError("issue-intake trusted actors must be the reviewed maintainer identities") - - repositories = policy["repositories"] - if repositories != list(dict.fromkeys(repositories)) or set(repositories) != set(OWNER_LABELS): - raise AuthorityError("issue-authority repository inventory is incomplete or duplicated") - if "cloud" in repositories: - raise AuthorityError("private Cloud implementation work cannot enter the public issue-authority inventory") - - labels = policy["labels"] - label_names = [label["name"] for label in labels] - if len(label_names) != len(set(label_names)): - raise AuthorityError("issue-authority labels must be unique") - required_labels = { - "authority:github", - "authority:conflict", - *STATUS_LABELS, - *COMPLETION_LABELS, - *KIND_LABELS, - *PRIORITY_LABELS, - *CLASSIFICATION_LABELS, - *OWNER_LABELS.values(), - intake["approval_label"], - } - missing_labels = required_labels - set(label_names) - if missing_labels: - raise AuthorityError(f"issue-authority policy is missing labels {sorted(missing_labels)}") - - milestones = policy["milestones"] - milestone_titles = [milestone["title"] for milestone in milestones] - if len(milestone_titles) != len(set(milestone_titles)): - raise AuthorityError("issue-authority milestones must be unique") - for milestone in milestones: - unknown = set(milestone["repositories"]) - set(repositories) - if unknown: - raise AuthorityError(f"milestone {milestone['title']!r} names unknown repositories {sorted(unknown)}") - if backlog["milestone"] not in milestone_titles: - raise AuthorityError("selective backlog milestone is not declared by issue-authority policy") - - retired_identities: set[tuple[str, int]] = set() - for supersession in policy["prerelease_supersessions"]: - retired = supersession["retired"] - successor = supersession["successor"] - retired_identity = (retired["repository"], retired["number"]) - if retired["repository"] not in repositories or successor["repository"] not in repositories: - raise AuthorityError("prerelease supersession names an unknown public repository") - if retired_identity in retired_identities: - raise AuthorityError( - f"prerelease supersession repeats retired issue {retired['repository']}#{retired['number']}" - ) - retired_identities.add(retired_identity) - successor_number = successor.get("number") - if type(successor_number) is int: - successor_identity = (successor["repository"], successor_number) - if retired_identity == successor_identity: - raise AuthorityError("prerelease supersession must name a distinct successor") - elif ( - successor["repository"] != policy["authority_repository"] - or successor["commit"] != supersession["activation_commit"] - ): - raise AuthorityError( - "immutable prerelease successor must be an exact authority-repository activation commit" - ) - - successor_by_retired = { - (record["retired"]["repository"], record["retired"]["number"]): record["successor"] - for record in policy["prerelease_supersessions"] - } - for start in retired_identities: - seen: set[tuple[str, int]] = set() - current = start - while current in successor_by_retired: - if current in seen: - raise AuthorityError("prerelease supersession issue chain contains a cycle") - seen.add(current) - successor = successor_by_retired[current] - successor_number = successor.get("number") - if type(successor_number) is not int: - break - current = (successor["repository"], successor_number) - - items = backlog["items"] - item_ids = [item["id"] for item in items] - if len(item_ids) != len(set(item_ids)): - raise AuthorityError("selective backlog item ids must be unique") - known_ids: set[str] = set() - for item in items: - if item["repository"] not in repositories: - raise AuthorityError(f"backlog item {item['id']} names an unknown public repository") - unblock_condition = item.get("unblock_condition") - if ( - item["status"] == "blocked" - and not item["depends_on"] - and (not unblock_condition or not unblock_condition.strip()) - ): - raise AuthorityError( - f"blocked backlog item {item['id']} must name a dependency or explicit unblock condition" - ) - future_dependencies = set(item["depends_on"]) - known_ids - if future_dependencies: - raise AuthorityError( - f"backlog item {item['id']} dependencies must precede it: {sorted(future_dependencies)}" - ) - known_ids.add(item["id"]) - if MARKER_PATTERN.search(item["body"]): - raise AuthorityError(f"backlog item {item['id']} body must not supply its own authority marker") - - review = backlog["review"] - review_ids = [record["review_id"] for record in review] - if len(review_ids) != len(set(review_ids)): - raise AuthorityError("selective backlog review ids must be unique") - migrated = {record["review_id"] for record in review if record["disposition"] == "migrate"} - if migrated != set(item_ids): - raise AuthorityError( - f"reviewed migration set differs from backlog items; reviewed={sorted(migrated)}, items={sorted(item_ids)}" - ) - - public_values = [backlog["milestone"]] - for record in review: - public_values.extend((record["title"], record["reason"])) - for item in items: - public_values.extend((item["title"], item["body"])) - if unblock_condition := item.get("unblock_condition"): - public_values.append(unblock_condition) - public_values.extend(supersession["reason"] for supersession in policy["prerelease_supersessions"]) - _public_safe(public_values) - - -def load_contract( - policy_path: Path, - backlog_path: Path, - policy_schema_path: Path | None = None, - backlog_schema_path: Path | None = None, -) -> tuple[dict[str, Any], dict[str, Any]]: - policy = _load_json(policy_path, "issue-authority policy") - backlog = _load_json(backlog_path, "selective backlog") - policy_schema = _load_json( - policy_schema_path or policy_path.with_name("policy-schema.json"), - "issue-authority policy schema", - ) - backlog_schema = _load_json( - backlog_schema_path or backlog_path.with_name("backlog-schema.json"), - "selective backlog schema", - ) - validate_contract(policy, backlog, policy_schema, backlog_schema) - return policy, backlog - - -def load_legacy_cross_repository_targets( - path: Path, - target_qualification: Mapping[str, Any], - schema_path: Path | None = None, -) -> dict[str, Any]: - migration = _load_json(path, "legacy cross-repository target migration") - schema = _load_json( - schema_path or path.with_name("legacy-cross-repository-targets-schema.json"), - "legacy cross-repository target migration schema", - ) - _validate_schema(migration, schema, "legacy cross-repository target migration") - if migration.get("schema") != LEGACY_TARGET_SCHEMA: - raise AuthorityError("legacy cross-repository target migration uses an unsupported schema") - _parse_timestamp(migration.get("created_before"), "legacy target migration cutoff") - - target_map = cross_repository_lifecycle.qualification_targets(target_qualification) - landing_map: dict[str, Mapping[str, Any]] = {} - for landing in migration["protected_branch_landings"]: - repository = landing["repository"] - target = target_map.get(repository) - if target is None or target["branch"] != landing["branch"]: - raise AuthorityError( - f"legacy protected-branch landing names unqualified target {repository}@{landing['branch']}" - ) - if repository in landing_map: - raise AuthorityError(f"legacy protected-branch landing repeats repository {repository}") - landing_map[repository] = landing - - identities: set[tuple[str, str]] = set() - for authority in migration["authorities"]: - identity = (authority["marker"], authority["id"]) - if identity in identities: - raise AuthorityError(f"legacy cross-repository target migration repeats {authority['id']}") - identities.add(identity) - try: - cross_repository_lifecycle.declared_targets( - f"{cross_repository_lifecycle.TARGET_HEADING}\n\n" + "\n".join(authority["targets"]), - target_map, - organization="durable-workflow", - required=True, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - - immutable_identities: set[tuple[str, int]] = set() - for binding in migration["immutable_issue_targets"]: - repository = binding["repository"] - identity = (repository, binding["number"]) - if repository not in target_map: - raise AuthorityError( - f"immutable issue target binding names unqualified source repository {repository}" - ) - if identity in immutable_identities: - raise AuthorityError( - f"immutable issue target binding repeats {repository}#{binding['number']}" - ) - immutable_identities.add(identity) - try: - bound_targets = cross_repository_lifecycle.declared_targets( - f"{cross_repository_lifecycle.TARGET_HEADING}\n\n" + "\n".join(binding["targets"]), - target_map, - organization="durable-workflow", - required=True, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - if repository not in {target["repository"] for target in bound_targets}: - raise AuthorityError( - f"immutable issue target binding for {repository}#{binding['number']} omits its source repository" - ) - - completed_identities: set[tuple[str, int]] = set() - for completion in migration["historical_completions"]: - if completion["repository"] not in target_map: - raise AuthorityError( - f"legacy historical completion names unqualified source repository {completion['repository']}" - ) - identity = (completion["repository"], completion["number"]) - if identity in completed_identities: - raise AuthorityError( - f"legacy historical completion repeats {completion['repository']}#{completion['number']}" - ) - completed_identities.add(identity) - try: - completed_targets = cross_repository_lifecycle.declared_targets( - f"{cross_repository_lifecycle.TARGET_HEADING}\n\n" + "\n".join(completion["targets"]), - target_map, - organization="durable-workflow", - required=True, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - missing_landings = sorted( - target["repository"] for target in completed_targets if target["repository"] not in landing_map - ) - if missing_landings: - raise AuthorityError( - f"legacy historical completion has no protected-branch landings for {missing_landings}" - ) - - frozen_identities: set[tuple[str, int]] = set() - frozen_snapshots: set[str] = set() - for frozen in migration["frozen_lifecycle_migrations"]: - repository = frozen["repository"] - identity = (repository, frozen["number"]) - if repository not in target_map: - raise AuthorityError( - f"frozen lifecycle migration names unqualified source repository {repository}" - ) - if identity in frozen_identities or identity in completed_identities: - raise AuthorityError( - f"frozen lifecycle migration repeats {repository}#{frozen['number']}" - ) - frozen_identities.add(identity) - snapshot = frozen["authority_snapshot_sha256"] - if snapshot in frozen_snapshots: - raise AuthorityError("frozen lifecycle migrations repeat an approved authority snapshot") - frozen_snapshots.add(snapshot) - _parse_timestamp(frozen["approval_at"], "frozen lifecycle approval timestamp") - try: - declared = cross_repository_lifecycle.declared_targets( - f"{cross_repository_lifecycle.TARGET_HEADING}\n\n" - + "\n".join(frozen["declared_targets"]), - target_map, - organization="durable-workflow", - required=True, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - declared_contract = { - (str(target["repository"]), str(target["branch"])) for target in declared - } - if not any(target["repository"] == repository for target in declared): - raise AuthorityError( - f"frozen lifecycle migration for {repository}#{frozen['number']} omits its source repository" - ) - if frozen["outcome"] == "missing-evidence": - if ( - frozen["completion_source"] is not None - or frozen["landings"] - or not isinstance(frozen["missing_evidence"], str) - or not frozen["missing_evidence"].strip() - ): - raise AuthorityError( - f"frozen missing-evidence migration for {repository}#{frozen['number']} " - "must contain only its bounded reason" - ) - continue - if frozen["missing_evidence"] is not None or not frozen["landings"]: - raise AuthorityError( - f"frozen completed migration for {repository}#{frozen['number']} has no exact landings" - ) - landing_contract: set[tuple[str, str]] = set() - source_commits: list[str] = [] - for landing in frozen["landings"]: - landing_identity = (landing["repository"], landing["branch"]) - if landing_identity not in declared_contract or landing_identity in landing_contract: - raise AuthorityError( - f"frozen lifecycle migration for {repository}#{frozen['number']} " - "has duplicated or undeclared landing evidence" - ) - landing_contract.add(landing_identity) - if landing["repository"] == repository: - source_commits.append(landing["commit"]) - qualification = landing["qualification"] - checks = qualification["checks"] - check_names = [check["name"] for check in checks] - check_jobs = [check["job"] for check in checks] - if ( - len(check_names) != len(set(check_names)) - or len(check_jobs) != len(set(check_jobs)) - or any(check["run"] != qualification["run"] for check in checks) - ): - raise AuthorityError( - f"frozen lifecycle migration for {repository}#{frozen['number']} " - "has ambiguous qualification check identity" - ) - if landing_contract != declared_contract: - raise AuthorityError( - f"frozen lifecycle migration for {repository}#{frozen['number']} " - "does not cover its exact declared target set" - ) - if source_commits != [frozen["completion_source"]]: - raise AuthorityError( - f"frozen lifecycle migration for {repository}#{frozen['number']} " - "does not bind its source completion commit" - ) - return migration - - -def validate_backlog_cross_repository_targets( - backlog: Mapping[str, Any], - target_qualification: Mapping[str, Any], - *, - organization: str, -) -> None: - target_map = cross_repository_lifecycle.qualification_targets(target_qualification) - for item in backlog["items"]: - selections = item.get("required_source_targets") - if item["kind"] != "cross-repository": - if selections is not None: - raise AuthorityError(f"non-cross-repository backlog item {item['id']} declares multiple source targets") - continue - if not isinstance(selections, list): - raise AuthorityError(f"cross-repository backlog item {item['id']} has no required source targets") - try: - cross_repository_lifecycle.declared_targets( - f"{cross_repository_lifecycle.TARGET_HEADING}\n\n" + "\n".join(selections), - target_map, - organization=organization, - required=True, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(f"cross-repository backlog item {item['id']}: {error}") from error - - -def _object_digest(value: Any) -> str: - encoded = json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True).encode("utf-8") - return hashlib.sha256(encoded).hexdigest() - - -def issue_revision_digest(title: str, body: str) -> str: - """Bind the complete instruction-bearing issue revision to one stable digest.""" - - return _object_digest({"body": body, "title": title}) - - -def _parse_timestamp(value: Any, label: str) -> datetime: - if not isinstance(value, str) or not value: - raise AuthorityError(f"GitHub issue intake has no valid {label}") - try: - parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError as error: - raise AuthorityError(f"GitHub issue intake has invalid {label}") from error - if parsed.tzinfo is None: - raise AuthorityError(f"GitHub issue intake {label} must include a timezone") - return parsed - - -def _intake_label_names(issue: Mapping[str, Any]) -> set[str]: - names: set[str] = set() - labels = issue.get("labels") - if not isinstance(labels, Sequence) or isinstance(labels, str | bytes): - return names - for label in labels: - if isinstance(label, str): - names.add(label) - elif isinstance(label, Mapping) and isinstance(label.get("name"), str): - names.add(str(label["name"])) - return names - - -def assess_issue_intake( - issue: Mapping[str, Any], - timeline: Sequence[Mapping[str, Any]], - *, - approval_label: str, - trusted_actors: Sequence[str], - bind_revision: bool = True, -) -> dict[str, Any]: - """Reconstruct whether the current title/body revision has trusted authority.""" - - number = issue.get("number") - author = issue.get("author") - author_login = author.get("login") if isinstance(author, Mapping) else None - if not isinstance(number, int): - raise AuthorityError("GitHub issue intake returned an issue without a numeric identity") - - def approved_record(actor: str, approved_at: Any, mode: str) -> dict[str, Any]: - record = { - "approved": True, - "approval_actor": actor, - "approval_at": approved_at, - "approval_mode": mode, - "reason": mode, - } - if bind_revision: - title = issue.get("title") - body = issue.get("body") - if not isinstance(title, str) or not isinstance(body, str): - raise AuthorityError(f"GitHub issue {number} has no complete title/body revision") - record["revision"] = issue_revision_digest(title, body) - return record - - trusted = {actor.casefold() for actor in trusted_actors} - last_edited_value = issue.get("last_edited_at") - edit_times: list[datetime] = [] - if last_edited_value is not None: - edit_times.append(_parse_timestamp(last_edited_value, "last body edit timestamp")) - edit_times.extend( - _parse_timestamp(event.get("created_at"), "title edit timestamp") - for event in timeline - if event.get("event") == "renamed" - ) - last_edit = max(edit_times) if edit_times else None - if isinstance(author_login, str) and author_login.casefold() in trusted and last_edit is None: - created_at = issue.get("created_at") - _parse_timestamp(created_at, "creation timestamp") - return approved_record(author_login, created_at, "trusted-creation") - - if approval_label not in _intake_label_names(issue): - return {"approved": False, "reason": "approval-label-absent"} - - transitions: list[tuple[datetime, int, Mapping[str, Any]]] = [] - for index, event in enumerate(timeline): - if event.get("label") != approval_label or event.get("event") not in {"labeled", "unlabeled"}: - continue - transitions.append((_parse_timestamp(event.get("created_at"), "label event timestamp"), index, event)) - if not transitions: - return {"approved": False, "reason": "approval-event-absent"} - - approval_time, _index, latest = max(transitions, key=lambda record: (record[0], record[1])) - if latest.get("event") != "labeled": - return {"approved": False, "reason": "approval-label-removed"} - actor = latest.get("actor") - if not isinstance(actor, str) or actor.casefold() not in trusted: - return {"approved": False, "reason": "approval-actor-untrusted"} - if last_edit is not None and approval_time <= last_edit: - return {"approved": False, "reason": "approval-predates-revision"} - - return approved_record(actor, latest["created_at"], "trusted-label") - - -def _normalize_intake_issue(node: Mapping[str, Any]) -> tuple[dict[str, Any], list[dict[str, Any]]]: - labels_connection = node.get("labels") - if not isinstance(labels_connection, Mapping): - raise AuthorityError("GitHub issue intake returned malformed labels") - page_info = labels_connection.get("pageInfo") - if isinstance(page_info, Mapping) and page_info.get("hasNextPage"): - raise AuthorityError(f"GitHub issue {node.get('number')} exceeds the intake label bound") - labels = labels_connection.get("nodes") - if not isinstance(labels, list): - raise AuthorityError("GitHub issue intake returned malformed label nodes") - - timeline_connection = node.get("timelineItems") - timeline_nodes = timeline_connection.get("nodes") if isinstance(timeline_connection, Mapping) else None - if not isinstance(timeline_nodes, list): - raise AuthorityError("GitHub issue intake returned malformed approval history") - timeline: list[dict[str, Any]] = [] - for event in timeline_nodes: - if not isinstance(event, Mapping): - continue - event_type = { - "ClosedEvent": "closed", - "LabeledEvent": "labeled", - "RenamedTitleEvent": "renamed", - "ReopenedEvent": "reopened", - "UnlabeledEvent": "unlabeled", - }.get(event.get("__typename")) - if event_type in {"closed", "renamed", "reopened"}: - timeline.append({"created_at": event.get("createdAt"), "event": event_type}) - continue - label = event.get("label") - actor = event.get("actor") - if event_type is None or not isinstance(label, Mapping): - continue - timeline.append( - { - "actor": actor.get("login") if isinstance(actor, Mapping) else None, - "created_at": event.get("createdAt"), - "event": event_type, - "label": label.get("name"), - } - ) - - milestone = node.get("milestone") - issue = { - "author": node.get("author"), - "body": node.get("body"), - "closed_at": node.get("closedAt"), - "created_at": node.get("createdAt"), - "html_url": node.get("url"), - "labels": [label for label in labels if isinstance(label, Mapping)], - "last_edited_at": node.get("lastEditedAt"), - "milestone": {"title": milestone.get("title")} if isinstance(milestone, Mapping) else None, - "number": node.get("number"), - "state": str(node.get("state", "")).lower(), - "state_reason": (str(node["stateReason"]).lower() if isinstance(node.get("stateReason"), str) else None), - "title": node.get("title"), - "updated_at": node.get("updatedAt"), - } - return issue, timeline - - -class GitHubDiscovery: - """Read-only GraphQL client that reconstructs issue revision authority.""" - - def __init__( - self, - token: str, - graphql_url: str = "https://api.github.com/graphql", - *, - api_url: str = "https://api.github.com", - ) -> None: - if not token: - raise AuthorityError("GITHUB_TOKEN is required for read-only issue discovery") - self.api_url = api_url.rstrip("/") - self.graphql_url = graphql_url - self.headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - "User-Agent": "durable-workflow-issue-intake/1", - "X-GitHub-Api-Version": "2022-11-28", - } - - def graphql(self, query: str, variables: Mapping[str, Any]) -> dict[str, Any]: - body = json.dumps({"query": query, "variables": variables}, separators=(",", ":")).encode("utf-8") - request = urllib.request.Request(self.graphql_url, data=body, headers=self.headers, method="POST") - for attempt in range(1, GITHUB_API_ATTEMPTS + 1): - try: - with urllib.request.urlopen(request, timeout=30) as response: - payload = json.loads(response.read()) - if not isinstance(payload, dict) or payload.get("errors") or not isinstance(payload.get("data"), dict): - raise AuthorityError("GitHub GraphQL issue discovery returned errors") - return payload["data"] - except urllib.error.HTTPError as error: - retryable = error.code == 429 or 500 <= error.code <= 599 - if not retryable or attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError(f"GitHub GraphQL issue discovery returned {error.code}") from error - except (urllib.error.URLError, ConnectionError, TimeoutError, json.JSONDecodeError) as error: - if attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError("GitHub GraphQL issue discovery failed after bounded retries") from error - time.sleep(GITHUB_API_RETRY_SECONDS * (2 ** (attempt - 1))) - raise AssertionError("GitHub GraphQL retry loop ended unexpectedly") - - def _rest(self, path: str) -> Any: - request = urllib.request.Request( - f"{self.api_url}{path}", - headers=self.headers, - method="GET", - ) - for attempt in range(1, GITHUB_API_ATTEMPTS + 1): - try: - with urllib.request.urlopen(request, timeout=30) as response: - response_body = response.read() - return json.loads(response_body) if response_body else None - except urllib.error.HTTPError as error: - retryable = error.code == 429 or 500 <= error.code <= 599 - if not retryable or attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError(f"GitHub REST issue discovery returned {error.code}") from error - except (urllib.error.URLError, ConnectionError, TimeoutError, json.JSONDecodeError) as error: - if attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError("GitHub REST issue discovery failed after bounded retries") from error - time.sleep(GITHUB_API_RETRY_SECONDS * (2 ** (attempt - 1))) - raise AssertionError("GitHub REST issue discovery retry loop ended unexpectedly") - - def _bytes(self, path: str) -> bytes: - request = urllib.request.Request( - f"{self.api_url}{path}", - headers={**self.headers, "Accept": "application/vnd.github.raw+json"}, - method="GET", - ) - for attempt in range(1, GITHUB_API_ATTEMPTS + 1): - try: - with urllib.request.urlopen(request, timeout=30) as response: - return response.read() - except urllib.error.HTTPError as error: - retryable = error.code == 429 or 500 <= error.code <= 599 - if not retryable or attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError(f"GitHub REST content discovery returned {error.code}") from error - except (urllib.error.URLError, ConnectionError, TimeoutError) as error: - if attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError("GitHub REST content discovery failed after bounded retries") from error - time.sleep(GITHUB_API_RETRY_SECONDS * (2 ** (attempt - 1))) - raise AssertionError("GitHub REST content discovery retry loop ended unexpectedly") - - def read_file( - self, - organization: str, - repository: str, - commit: str, - path: str, - ) -> bytes: - encoded_path = urllib.parse.quote(path, safe="/") - encoded_commit = urllib.parse.quote(commit, safe="") - return self._bytes(f"/repos/{organization}/{repository}/contents/{encoded_path}?ref={encoded_commit}") - - def list_commit_statuses( - self, - organization: str, - repository: str, - commit: str, - ) -> list[dict[str, Any]]: - encoded_commit = urllib.parse.quote(commit, safe="") - statuses: list[dict[str, Any]] = [] - for page in range(1, 11): - payload = self._rest( - f"/repos/{organization}/{repository}/commits/{encoded_commit}/statuses?per_page=100&page={page}" - ) - if not isinstance(payload, list): - raise AuthorityError("GitHub REST issue discovery returned malformed commit statuses") - statuses.extend(dict(status) for status in payload if isinstance(status, Mapping)) - if len(payload) < 100: - return statuses - raise AuthorityError("GitHub REST issue discovery commit statuses exceeded the pagination bound") - - def list_issues(self, organization: str, repository: str) -> list[tuple[dict[str, Any], list[dict[str, Any]]]]: - issues: list[tuple[dict[str, Any], list[dict[str, Any]]]] = [] - cursor: str | None = None - for _page in range(10): - data = self.graphql( - ISSUE_INTAKE_QUERY, - {"cursor": cursor, "owner": organization, "repository": repository}, - ) - repository_node = data.get("repository") - connection = repository_node.get("issues") if isinstance(repository_node, Mapping) else None - if not isinstance(connection, Mapping) or not isinstance(connection.get("nodes"), list): - raise AuthorityError(f"GitHub issue discovery cannot read {organization}/{repository}") - for node in connection["nodes"]: - if isinstance(node, Mapping): - issues.append(_normalize_intake_issue(node)) - page_info = connection.get("pageInfo") - if not isinstance(page_info, Mapping): - raise AuthorityError("GitHub issue discovery returned malformed pagination") - if not page_info.get("hasNextPage"): - return issues - cursor = page_info.get("endCursor") - if not isinstance(cursor, str) or not cursor: - raise AuthorityError("GitHub issue discovery omitted its next cursor") - raise AuthorityError(f"GitHub issue discovery for {repository} exceeded the pagination bound") - - def list_pull_requests(self, organization: str, repository: str) -> list[dict[str, Any]]: - """Read bounded pull-request metadata without fetching titles, bodies, or comments.""" - - pulls: list[dict[str, Any]] = [] - cursor: str | None = None - for _page in range(10): - data = self.graphql( - PULL_REQUEST_METADATA_QUERY, - {"cursor": cursor, "owner": organization, "repository": repository}, - ) - repository_node = data.get("repository") - connection = repository_node.get("pullRequests") if isinstance(repository_node, Mapping) else None - if not isinstance(connection, Mapping) or not isinstance(connection.get("nodes"), list): - raise AuthorityError(f"GitHub pull-request metadata discovery cannot read {organization}/{repository}") - for node in connection["nodes"]: - if not isinstance(node, Mapping): - continue - pulls.append( - { - "closed_at": node.get("closedAt"), - "created_at": node.get("createdAt"), - "merged_at": node.get("mergedAt"), - "number": node.get("number"), - "repository": repository, - "state": str(node.get("state", "")).lower(), - "type": "pull_request", - "updated_at": node.get("updatedAt"), - "url": node.get("url"), - } - ) - page_info = connection.get("pageInfo") - if not isinstance(page_info, Mapping): - raise AuthorityError("GitHub pull-request metadata discovery returned malformed pagination") - if not page_info.get("hasNextPage"): - return pulls - cursor = page_info.get("endCursor") - if not isinstance(cursor, str) or not cursor: - raise AuthorityError("GitHub pull-request metadata discovery omitted its next cursor") - raise AuthorityError(f"GitHub pull-request metadata for {repository} exceeded the pagination bound") - - def get_issue( - self, - organization: str, - repository: str, - number: int, - ) -> tuple[dict[str, Any], list[dict[str, Any]]]: - data = self.graphql( - ISSUE_REVISION_QUERY, - {"number": number, "owner": organization, "repository": repository}, - ) - repository_node = data.get("repository") - node = repository_node.get("issue") if isinstance(repository_node, Mapping) else None - if not isinstance(node, Mapping): - raise AuthorityError(f"GitHub issue discovery cannot read {repository}/{number}") - return _normalize_intake_issue(node) - - -def _manifest_core(manifest: Mapping[str, Any]) -> dict[str, Any]: - return { - key: manifest.get(key) - for key in ( - "schema", - "organization", - "policy_digest", - "legacy_target_migration_digest", - "issues", - "public_metadata", - "rejected_issues", - ) - } - - -def _manifest_public_metadata(manifest: Mapping[str, Any]) -> list[dict[str, Any]]: - records = manifest.get("public_metadata") - if not isinstance(records, list): - raise AuthorityError("issue-intake manifest has no metadata-only public inventory") - identities: set[tuple[str, str, int]] = set() - normalized: list[dict[str, Any]] = [] - issue_keys = { - "approved", - "approval_reason", - "closed_at", - "created_at", - "label_transition_at", - "labels", - "last_transition_at", - "number", - "repository", - "specialized_lifecycle", - "state", - "type", - "updated_at", - "url", - } - pull_request_keys = { - "closed_at", - "created_at", - "merged_at", - "number", - "repository", - "state", - "type", - "updated_at", - "url", - } - quarantine_keys = {"quarantined", "reconciliation_failure"} - for record in records: - if not isinstance(record, Mapping): - raise AuthorityError("issue-intake manifest contains malformed public metadata") - record_type = record.get("type") - repository = record.get("repository") - number = record.get("number") - if ( - record_type not in {"issue", "pull_request"} - or not isinstance(repository, str) - or type(number) is not int - or number < 1 - ): - raise AuthorityError("issue-intake manifest contains invalid public metadata identity") - keys = set(record) - expected_keys = issue_keys if record_type == "issue" else pull_request_keys - if not expected_keys <= keys or keys - expected_keys - quarantine_keys: - raise AuthorityError("issue-intake manifest public metadata exceeds its metadata-only field set") - expected_url = ( - f"https://github.com/{manifest.get('organization')}/{repository}/" - f"{'issues' if record_type == 'issue' else 'pull'}/{number}" - ) - if record.get("url") != expected_url: - raise AuthorityError("issue-intake manifest contains a non-public metadata URL") - labels = record.get("labels") if record_type == "issue" else [] - if not isinstance(labels, list) or not all(isinstance(label, str) and label for label in labels): - raise AuthorityError("issue-intake manifest contains invalid public label metadata") - identity = (repository, str(record_type), number) - if identity in identities: - raise AuthorityError("issue-intake manifest repeats a public metadata identity") - identities.add(identity) - normalized.append(dict(record)) - return normalized - - -def _manifest_completion_holds(manifest: Mapping[str, Any]) -> set[tuple[str, int]]: - return { - (record["repository"], record["number"]) - for record in manifest["issues"] - if record["completion_evidence_required"] is True - } - - -def _manifest_cross_repository_targets( - manifest: Mapping[str, Any], -) -> dict[tuple[str, int], list[dict[str, Any]]]: - return { - (record["repository"], record["number"]): list(record["cross_repository_targets"]) - for record in manifest["issues"] - if record["cross_repository_targets"] - } - - -def _manifest_historical_cross_repository_completions( - manifest: Mapping[str, Any], -) -> dict[tuple[str, int], list[dict[str, Any]]]: - return { - (record["repository"], record["number"]): list(record["historical_cross_repository_completion"]) - for record in manifest["issues"] - if record["historical_cross_repository_completion"] - } - - -def _manifest_frozen_cross_repository_lifecycles( - manifest: Mapping[str, Any], -) -> dict[tuple[str, int], dict[str, Any]]: - return { - (record["repository"], record["number"]): dict(record["frozen_cross_repository_lifecycle"]) - for record in manifest["issues"] - if record["frozen_cross_repository_lifecycle"] is not None - } - - -def _manifest_prerelease_supersessions( - manifest: Mapping[str, Any], -) -> dict[tuple[str, int], dict[str, Any]]: - return { - (record["repository"], record["number"]): dict(record["superseded_by"]) - for record in manifest["issues"] - if record["superseded_by"] is not None - } - - -def _is_issue_successor(successor: Mapping[str, Any]) -> bool: - return type(successor.get("number")) is int - - -def _validate_immutable_product_train_successor( - successor: Mapping[str, Any], - raw: bytes, -) -> None: - if hashlib.sha256(raw).hexdigest() != successor["sha256"]: - raise AuthorityError("immutable prerelease successor product-train digest changed") - try: - product_train = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise AuthorityError("immutable prerelease successor product train is not valid UTF-8 JSON") from error - - train = successor["train"] - train_match = PRODUCT_TRAIN_IDENTIFIER_PATTERN.fullmatch(train) - trains = product_train.get("trains") if isinstance(product_train, Mapping) else None - selected = trains.get(train) if isinstance(trains, Mapping) else None - components = product_train.get("components") if isinstance(product_train, Mapping) else None - versions = selected.get("versions") if isinstance(selected, Mapping) else None - progression = product_train.get("progression") if isinstance(product_train, Mapping) else None - prerelease_progression = progression.get("prerelease") if isinstance(progression, Mapping) else None - component_version_pattern = ( - re.compile(rf"^{re.escape(train_match['base'])}-{train_match['channel']}\.[0-9]+$") - if train_match is not None - else None - ) - synchronized = isinstance(versions, Mapping) and all( - isinstance(version, str) and version == train for version in versions.values() - ) - if ( - not isinstance(product_train, Mapping) - or product_train.get("schema") != "durable-workflow.product-train/v2" - or train_match is None - or product_train.get("current") != train - or components != list(COMPONENTS) - or not isinstance(versions, Mapping) - or set(versions) != set(COMPONENTS) - or component_version_pattern is None - or any( - not isinstance(version, str) or component_version_pattern.fullmatch(version) is None - for version in versions.values() - ) - or selected.get("channel") != train_match["channel"] - or selected.get("status") != "supported" - or selected.get("release_plan") != successor["release_plan"] - or not isinstance(progression, Mapping) - or progression.get("stable") != "semantic_versioning" - or progression.get("compatibility_shims") != "forbidden_between_2_0_prereleases" - or prerelease_progression - not in { - "synchronized_beta_increment", - "synchronized_prerelease_increment", - "independent_prerelease_components", - } - or (prerelease_progression == "synchronized_beta_increment" and train_match["channel"] != "beta") - or (prerelease_progression != "independent_prerelease_components" and not synchronized) - ): - raise AuthorityError("immutable prerelease successor is not one coherent supported public product train") - - -def _supersession_activation( - supersession: Mapping[str, Any], - retired_record: Mapping[str, Any], - successor_record: Mapping[str, Any] | None, -) -> dict[str, str]: - retired = supersession["retired"] - successor = supersession["successor"] - context = f"{SUPERSESSION_ACTIVATION_CONTEXT_PREFIX}/{retired['repository']}/{retired['number']}" - if len(context) > 100: - raise AuthorityError("prerelease supersession activation context exceeds the GitHub status bound") - if _is_issue_successor(successor): - assert successor_record is not None - successor_authority = { - **successor, - "revision": successor_record["revision"], - } - else: - successor_authority = dict(successor) - activation = { - "commit": supersession["activation_commit"], - "context": context, - "digest": _object_digest( - { - "activation_commit": supersession["activation_commit"], - "reason": supersession["reason"], - "retired": { - **retired, - "revision": retired_record["revision"], - }, - "successor": successor_authority, - } - ), - } - return activation - - -def _activation_status_is_trusted(status: Mapping[str, Any]) -> bool: - creator = status.get("creator") - return ( - isinstance(creator, Mapping) - and creator.get("id") == GITHUB_ACTIONS_BOT_ID - and isinstance(creator.get("login"), str) - and creator["login"].casefold() == GITHUB_ACTIONS_BOT_LOGIN - and creator.get("type") == "Bot" - ) - - -def _supersession_activation_is_recorded( - statuses: Sequence[Mapping[str, Any]], - activation: Mapping[str, str], -) -> bool: - trusted_authorities = [ - status - for status in statuses - if ( - status.get("context") == activation["context"] - and _activation_status_is_trusted(status) - ) - ] - expected_description = SUPERSESSION_ACTIVATION_DESCRIPTION_PREFIX + activation["digest"] - authorities = { - (status.get("state"), status.get("description")) - for status in trusted_authorities - } - if authorities and authorities != {("success", expected_description)}: - raise AuthorityError("prerelease supersession has conflicting immutable activation authority") - return authorities == {("success", expected_description)} - - -def _require_supersession_activation( - policy: Mapping[str, Any], - client: Any, - activation: Mapping[str, str], -) -> None: - statuses = client.list_commit_statuses( - policy["organization"], - ".github", - activation["commit"], - ) - if not _supersession_activation_is_recorded(statuses, activation): - raise AuthorityError("completed prerelease successor has no immutable active-successor activation") - - -def _bind_prerelease_supersessions( - policy: Mapping[str, Any], - records: Sequence[dict[str, Any]], - inventory: Mapping[str, Sequence[Mapping[str, Any]]], - client: Any, - *, - require_activations: bool = True, -) -> None: - record_by_identity = {(record["repository"], record["number"]): record for record in records} - issue_by_identity = { - (repository, int(issue["number"])): issue for repository, issues in inventory.items() for issue in issues - } - retired_identities = { - (record["retired"]["repository"], record["retired"]["number"]) for record in policy["prerelease_supersessions"] - } - product_train_files: dict[tuple[str, str, str, str], bytes] = {} - milestone_titles = {milestone["title"] for milestone in policy["milestones"]} - blocker_labels = { - "authority:github", - "beta:blocker", - } - active_labels = { - *blocker_labels, - COMPLETION_REQUIRED_LABEL, - } - - for supersession in policy["prerelease_supersessions"]: - retired = supersession["retired"] - successor = supersession["successor"] - retired_identity = (retired["repository"], retired["number"]) - retired_record = record_by_identity.get(retired_identity) - retired_issue = issue_by_identity.get(retired_identity) - if retired_record is None or retired_issue is None: - raise AuthorityError( - f"retired prerelease issue {retired['repository']}#{retired['number']} " - "does not have trusted current-revision intake" - ) - - retired_labels = _intake_label_names(retired_issue) - retired_statuses = retired_labels & STATUS_LABELS - retired_milestone = retired_issue.get("milestone") - retired_milestone_title = retired_milestone.get("title") if isinstance(retired_milestone, Mapping) else None - if ( - not blocker_labels <= retired_labels - or len(retired_labels & KIND_LABELS) != 1 - or not retired_statuses - or retired_issue.get("state") not in {"open", "closed"} - or retired_milestone_title not in milestone_titles - ): - raise AuthorityError( - f"retired prerelease issue {retired['repository']}#{retired['number']} " - "does not retain evidence-required blocker authority" - ) - - if _is_issue_successor(successor): - successor_identity = (successor["repository"], successor["number"]) - successor_record = record_by_identity.get(successor_identity) - successor_issue = issue_by_identity.get(successor_identity) - if successor_record is None or successor_issue is None: - raise AuthorityError( - f"prerelease successor {successor['repository']}#{successor['number']} " - "does not have trusted current-revision intake" - ) - - successor_labels = _intake_label_names(successor_issue) - successor_statuses = successor_labels & STATUS_LABELS - successor_milestone = successor_issue.get("milestone") - successor_milestone_title = ( - successor_milestone.get("title") if isinstance(successor_milestone, Mapping) else None - ) - successor_is_active = ( - active_labels <= successor_labels - and COMPLETION_VERIFIED_LABEL not in successor_labels - and len(successor_labels & KIND_LABELS) == 1 - and len(successor_statuses) == 1 - and successor_statuses <= OPEN_STATUS_LABELS - and successor_issue.get("state") == "open" - and successor_milestone_title == retired_milestone_title - ) - successor_is_completed = ( - {"authority:github", "beta:blocker", COMPLETION_VERIFIED_LABEL} <= successor_labels - and len(successor_labels & KIND_LABELS) == 1 - and successor_statuses == {"status:done"} - and successor_issue.get("state") == "closed" - and successor_milestone_title == retired_milestone_title - ) - successor_is_retiring = ( - successor_identity in retired_identities - and blocker_labels <= successor_labels - and len(successor_labels & KIND_LABELS) == 1 - and bool(successor_statuses) - and successor_issue.get("state") in {"open", "closed"} - and successor_milestone_title == retired_milestone_title - ) - if not successor_is_active and not successor_is_completed and not successor_is_retiring: - raise AuthorityError( - f"prerelease successor {successor['repository']}#{successor['number']} " - "is neither active, verified completed, nor bound to a later retirement " - "in the same release milestone" - ) - - activation = _supersession_activation( - supersession, - retired_record, - successor_record, - ) - if ( - retired_issue.get("state") == "closed" - or SUPERSEDED_STATUS_LABEL in retired_statuses - or successor_is_completed - or successor_is_retiring - ) and require_activations: - _require_supersession_activation(policy, client, activation) - retired_record["superseded_by"] = { - "activation": activation, - "number": successor["number"], - "reason": supersession["reason"], - "repository": successor["repository"], - "revision": successor_record["revision"], - } - continue - - product_train_identity = ( - successor["repository"], - successor["commit"], - successor["path"], - successor["sha256"], - ) - raw = product_train_files.get(product_train_identity) - if raw is None: - raw = client.read_file( - policy["organization"], - successor["repository"], - successor["commit"], - successor["path"], - ) - product_train_files[product_train_identity] = raw - _validate_immutable_product_train_successor(successor, raw) - activation = _supersession_activation( - supersession, - retired_record, - None, - ) - if ( - retired_issue.get("state") == "closed" - or SUPERSEDED_STATUS_LABEL in retired_statuses - ) and require_activations: - _require_supersession_activation(policy, client, activation) - retired_record["superseded_by"] = { - "activation": activation, - "commit": successor["commit"], - "path": successor["path"], - "reason": supersession["reason"], - "release_plan": dict(successor["release_plan"]), - "repository": successor["repository"], - "sha256": successor["sha256"], - "train": successor["train"], - } - - -def _legacy_form_targets( - body: str, - targets: Mapping[str, Mapping[str, Any]], - *, - organization: str, -) -> list[dict[str, Any]]: - heading_count = body.count(LEGACY_TARGET_HEADING) - if heading_count == 0: - return [] - if heading_count != 1: - raise AuthorityError("legacy cross-repository issue repeats its affected public repositories section") - section = body.split(LEGACY_TARGET_HEADING, 1)[1] - section = re.split(r"(?m)^### ", section, maxsplit=1)[0] - mentioned = set(re.findall(rf"\b{re.escape(organization)}/([a-z0-9_.-]+)\b", section)) - unknown = mentioned - set(targets) - if unknown: - raise AuthorityError(f"legacy cross-repository issue names unqualified targets {sorted(unknown)}") - if len(mentioned) < 2: - raise AuthorityError("legacy cross-repository issue does not bind at least two public targets") - return [dict(targets[repository]) for repository in sorted(mentioned)] - - -def _legacy_migrated_targets( - issue: Mapping[str, Any], - timeline: Sequence[Mapping[str, Any]], - assessment: Mapping[str, Any], - migration: Mapping[str, Any] | None, - targets: Mapping[str, Mapping[str, Any]], - *, - organization: str, -) -> list[dict[str, Any]]: - if not _legacy_revision_is_eligible(issue, timeline, assessment, migration): - return [] - assert migration is not None - body = str(issue.get("body", "")) - matches: list[Mapping[str, Any]] = [] - patterns = { - "beta-work-id": MARKER_PATTERN, - "durable-workflow-work-id": WORK_MARKER_PATTERN, - } - for authority in migration["authorities"]: - pattern = patterns[authority["marker"]] - if authority["id"] in pattern.findall(body): - matches.append(authority) - if len(matches) > 1: - raise AuthorityError("legacy cross-repository revision matches multiple target migrations") - if matches: - authority = matches[0] - try: - return cross_repository_lifecycle.declared_targets( - f"{cross_repository_lifecycle.TARGET_HEADING}\n\n" + "\n".join(authority["targets"]), - targets, - organization=organization, - required=True, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - return _legacy_form_targets(body, targets, organization=organization) - - -def _immutable_issue_targets( - source_repository: str, - issue: Mapping[str, Any], - migration: Mapping[str, Any] | None, - targets: Mapping[str, Mapping[str, Any]], - *, - organization: str, -) -> list[dict[str, Any]]: - if migration is None: - return [] - matches = [ - binding - for binding in migration["immutable_issue_targets"] - if binding["repository"] == source_repository and binding["number"] == issue.get("number") - ] - if not matches: - return [] - if len(matches) != 1: - raise AuthorityError("cross-repository issue matches multiple immutable target bindings") - try: - return cross_repository_lifecycle.declared_targets( - f"{cross_repository_lifecycle.TARGET_HEADING}\n\n" + "\n".join(matches[0]["targets"]), - targets, - organization=organization, - required=True, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - - -def _legacy_revision_is_eligible( - issue: Mapping[str, Any], - timeline: Sequence[Mapping[str, Any]], - assessment: Mapping[str, Any], - migration: Mapping[str, Any] | None, -) -> bool: - if migration is None or assessment.get("approval_mode") != "trusted-creation": - return False - cutoff = _parse_timestamp(migration.get("created_before"), "legacy target migration cutoff") - created_at = _parse_timestamp(issue.get("created_at"), "creation timestamp") - if created_at >= cutoff or issue.get("last_edited_at") is not None: - return False - for event in timeline: - if event.get("label") != "kind:cross-repository": - continue - if _parse_timestamp(event.get("created_at"), "cross-repository label timestamp") >= cutoff: - return False - return True - - -def _legacy_historical_completion( - source_repository: str, - issue: Mapping[str, Any], - timeline: Sequence[Mapping[str, Any]], - assessment: Mapping[str, Any], - declared: Sequence[Mapping[str, Any]], - migration: Mapping[str, Any] | None, -) -> list[dict[str, Any]]: - if not _legacy_revision_is_eligible(issue, timeline, assessment, migration): - return [] - assert migration is not None - matches = [ - completion - for completion in migration["historical_completions"] - if completion["repository"] == source_repository - and completion["number"] == issue.get("number") - and completion["revision"] == assessment.get("revision") - ] - if not matches: - return [] - if len(matches) != 1: - raise AuthorityError("legacy cross-repository revision matches multiple historical completions") - completion = matches[0] - expected_targets = sorted(f"durable-workflow/{target['repository']}@{target['branch']}" for target in declared) - if sorted(completion["targets"]) != expected_targets: - raise AuthorityError( - f"GitHub issue {issue['number']}: historical completion targets differ from its migrated target set" - ) - landing_map = { - (landing["repository"], landing["branch"]): landing for landing in migration["protected_branch_landings"] - } - return [dict(landing_map[(str(target["repository"]), str(target["branch"]))]) for target in declared] - - -def _frozen_lifecycle_migration( - source_repository: str, - issue: Mapping[str, Any], - assessment: Mapping[str, Any], - declared: Sequence[Mapping[str, Any]], - migration: Mapping[str, Any] | None, -) -> dict[str, Any] | None: - """Bind one reviewed migration to the exact trusted issue revision.""" - - if migration is None: - return None - matches = [ - record - for record in migration["frozen_lifecycle_migrations"] - if record["repository"] == source_repository and record["number"] == issue.get("number") - ] - if not matches: - return None - if len(matches) != 1: - raise AuthorityError("cross-repository issue matches multiple frozen lifecycle migrations") - frozen = matches[0] - approval_actor = assessment.get("approval_actor") - approval_actor_sha256 = ( - hashlib.sha256(approval_actor.casefold().encode("utf-8")).hexdigest() - if isinstance(approval_actor, str) - else None - ) - if ( - approval_actor_sha256 != frozen["approval_actor_sha256"] - or assessment.get("approval_at") != frozen["approval_at"] - or assessment.get("approval_mode") != frozen["approval_mode"] - ): - raise AuthorityError( - f"GitHub issue {issue['number']}: frozen lifecycle authority differs from its trusted intake" - ) - if assessment.get("revision") != frozen["approved_issue_revision_sha256"]: - raise AuthorityError( - f"GitHub issue {issue['number']}: current revision differs from reviewed frozen authority" - ) - expected_targets = sorted( - f"durable-workflow/{target['repository']}@{target['branch']}" for target in declared - ) - if sorted(frozen["declared_targets"]) != expected_targets: - raise AuthorityError( - f"GitHub issue {issue['number']}: frozen lifecycle targets differ from its declared target set" - ) - return json.loads(json.dumps(frozen)) - - -def _issue_cross_repository_targets( - source_repository: str, - issue: Mapping[str, Any], - timeline: Sequence[Mapping[str, Any]], - assessment: Mapping[str, Any], - targets: Mapping[str, Mapping[str, Any]], - migration: Mapping[str, Any] | None, - *, - organization: str, -) -> list[dict[str, Any]]: - labels = _intake_label_names(issue) - is_cross_repository = "kind:cross-repository" in labels - immutable_targets = _immutable_issue_targets( - source_repository, - issue, - migration, - targets, - organization=organization, - ) - try: - declared = cross_repository_lifecycle.declared_targets( - str(issue["body"]), - targets, - organization=organization, - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(f"GitHub issue {issue['number']}: {error}") from error - if declared: - if immutable_targets: - if declared != immutable_targets: - raise AuthorityError( - f"GitHub issue {issue['number']}: declared source targets differ from its immutable target binding" - ) - return declared - if not is_cross_repository: - raise AuthorityError( - f"GitHub issue {issue['number']} declares multiple source targets without cross-repository authority" - ) - return declared - if immutable_targets: - return immutable_targets - if not is_cross_repository: - return [] - migrated = _legacy_migrated_targets( - issue, - timeline, - assessment, - migration, - targets, - organization=organization, - ) - if migrated: - return migrated - raise AuthorityError( - f"GitHub issue {issue['number']}: cross-repository authority must declare its required source targets" - ) - - -def _target_rejection( - repository: str, - number: int, - assessment: Mapping[str, Any], - error: AuthorityError, -) -> dict[str, Any]: - return { - "approval_actor": assessment["approval_actor"], - "approval_at": assessment["approval_at"], - "approval_mode": assessment["approval_mode"], - "number": number, - "reason": str(error), - "repository": repository, - "revision": assessment["revision"], - } - - -def _latest_lifecycle_transition( - issue: Mapping[str, Any], - timeline: Sequence[Mapping[str, Any]], - *, - approval_label: str, -) -> str: - """Return the latest metadata-only timestamp that can change public lifecycle meaning.""" - - candidates = [issue.get("created_at")] - if issue.get("closed_at") is not None: - candidates.append(issue.get("closed_at")) - for event in timeline: - if event.get("event") in {"closed", "reopened"} or ( - event.get("event") in {"labeled", "unlabeled"} - and event.get("label") in {*STATUS_LABELS, *COMPLETION_LABELS, approval_label} - ): - candidates.append(event.get("created_at")) - parsed = [ - (_parse_timestamp(value, "public lifecycle transition timestamp"), str(value)) - for value in candidates - if value is not None - ] - if not parsed: - raise AuthorityError(f"GitHub issue {issue.get('number')} has no lifecycle timestamp") - return max(parsed, key=lambda record: record[0])[1] - - -def _issue_metadata_record( - repository: str, - issue: Mapping[str, Any], - timeline: Sequence[Mapping[str, Any]], - assessment: Mapping[str, Any], - *, - approval_label: str, -) -> dict[str, Any]: - labels = sorted(_intake_label_names(issue)) - label_transition_at: dict[str, str] = {} - for label in (*STATUS_LABELS, *COMPLETION_LABELS, approval_label): - transitions = [ - (event.get("created_at"), event.get("event")) - for event in timeline - if event.get("label") == label and event.get("event") in {"labeled", "unlabeled"} - ] - if transitions: - latest_at, latest_event = max( - transitions, - key=lambda record: _parse_timestamp(record[0], "label transition timestamp"), - ) - if latest_event == "labeled" and label in labels: - label_transition_at[label] = str(latest_at) - return { - "approved": assessment.get("approved") is True, - "approval_reason": str(assessment.get("reason", "unknown")), - "closed_at": issue.get("closed_at"), - "created_at": issue.get("created_at"), - "label_transition_at": label_transition_at, - "labels": labels, - "last_transition_at": _latest_lifecycle_transition( - issue, - timeline, - approval_label=approval_label, - ), - "number": issue.get("number"), - "repository": repository, - "specialized_lifecycle": False, - "state": issue.get("state"), - "type": "issue", - "updated_at": issue.get("updated_at"), - "url": issue.get("html_url"), - } - - -def discover_public_issue_metadata(policy: Mapping[str, Any], client: Any) -> list[dict[str, Any]]: - """Collect only public issue metadata; never request instruction-bearing prose.""" - - intake_policy = policy["intake"] - metadata: list[dict[str, Any]] = [] - for repository in policy["repositories"]: - for issue, timeline in client.list_issues(policy["organization"], repository): - assessment = assess_issue_intake( - issue, - timeline, - approval_label=intake_policy["approval_label"], - trusted_actors=intake_policy["trusted_actors"], - bind_revision=False, - ) - metadata.append( - _issue_metadata_record( - repository, - issue, - timeline, - assessment, - approval_label=intake_policy["approval_label"], - ) - ) - return sorted( - metadata, - key=lambda record: (str(record["repository"]), int(record["number"])), - ) - - -def discover_public_metadata(policy: Mapping[str, Any], client: Any) -> list[dict[str, Any]]: - """Collect only public issue/PR metadata; never request instruction-bearing prose.""" - - metadata = discover_public_issue_metadata(policy, client) - for repository in policy["repositories"]: - metadata.extend(client.list_pull_requests(policy["organization"], repository)) - return sorted( - metadata, - key=lambda record: (str(record["repository"]), str(record["type"]), int(record["number"])), - ) - - -def reconstruct_intake( - policy: dict[str, Any], - client: Any, - *, - pre_intake_release_completions: Collection[tuple[str, int]] = (), - target_qualification: Mapping[str, Any] | None = None, - legacy_cross_repository_targets: Mapping[str, Any] | None = None, - trigger_repository: str | None = None, - trigger_number: int | None = None, - trigger_action: str | None = None, - trigger_actor: str | None = None, - trigger_label: str | None = None, - require_supersession_activations: bool = True, -) -> tuple[dict[str, Any], dict[str, list[dict[str, Any]]]]: - """Build a deterministic manifest and an inventory containing only vetted revisions.""" - - intake_policy = policy["intake"] - lifecycle_targets = ( - cross_repository_lifecycle.qualification_targets(target_qualification) - if target_qualification is not None - else {} - ) - records: list[dict[str, Any]] = [] - rejected_records: list[dict[str, Any]] = [] - public_metadata: list[dict[str, Any]] = [] - metadata_by_identity: dict[tuple[str, int], dict[str, Any]] = {} - inventory: dict[str, list[dict[str, Any]]] = {repository: [] for repository in policy["repositories"]} - trigger_assessment: dict[str, Any] | None = None - terminal_release_completions = set(pre_intake_release_completions) - for repository in policy["repositories"]: - for issue, timeline in client.list_issues(policy["organization"], repository): - number = issue["number"] - is_trigger = repository == trigger_repository and number == trigger_number - if is_trigger and trigger_action == "edited": - trigger_assessment = {"approved": False, "reason": "revision-edited"} - continue - if ( - is_trigger - and trigger_action == "unlabeled" - and trigger_label == intake_policy["approval_label"] - ): - trigger_assessment = {"approved": False, "reason": "approval-label-removed"} - continue - if ( - is_trigger - and trigger_action == "labeled" - and trigger_label == intake_policy["approval_label"] - and ( - not isinstance(trigger_actor, str) - or trigger_actor.casefold() - not in {actor.casefold() for actor in intake_policy["trusted_actors"]} - ) - ): - trigger_assessment = {"approved": False, "reason": "approval-actor-untrusted"} - continue - preliminary = assess_issue_intake( - issue, - timeline, - approval_label=intake_policy["approval_label"], - trusted_actors=intake_policy["trusted_actors"], - bind_revision=False, - ) - metadata = _issue_metadata_record( - repository, - issue, - timeline, - preliminary, - approval_label=intake_policy["approval_label"], - ) - public_metadata.append(metadata) - metadata_by_identity[(repository, number)] = metadata - if (repository, number) in terminal_release_completions: - labels = _intake_label_names(issue) - terminal = ( - preliminary["approved"] - and issue.get("state") == "closed" - and labels & STATUS_LABELS == {"status:done"} - and COMPLETION_VERIFIED_LABEL in labels - ) - if not terminal: - metadata["quarantined"] = True - metadata["reconciliation_failure"] = "release-completion-not-terminal" - else: - metadata["approved"] = True - metadata["approval_reason"] = str(preliminary["reason"]) - metadata["specialized_lifecycle"] = True - if is_trigger: - trigger_assessment = ( - dict(preliminary) - if terminal - else {"approved": False, "reason": "release-completion-not-terminal"} - ) - # A verified release completion is terminal before any approved - # revision is fetched or prerelease supersession is bound. This - # keeps the completed issue out of successor reservation. - continue - if not preliminary["approved"]: - if is_trigger: - trigger_assessment = dict(preliminary) - continue - issue, timeline = client.get_issue(policy["organization"], repository, number) - assessment = assess_issue_intake( - issue, - timeline, - approval_label=intake_policy["approval_label"], - trusted_actors=intake_policy["trusted_actors"], - ) - if is_trigger: - trigger_assessment = dict(assessment) - if not assessment["approved"]: - continue - try: - cross_repository_targets = _issue_cross_repository_targets( - repository, - issue, - timeline, - assessment, - lifecycle_targets, - legacy_cross_repository_targets, - organization=policy["organization"], - ) - historical_completion = _legacy_historical_completion( - repository, - issue, - timeline, - assessment, - cross_repository_targets, - legacy_cross_repository_targets, - ) - frozen_lifecycle = _frozen_lifecycle_migration( - repository, - issue, - assessment, - cross_repository_targets, - legacy_cross_repository_targets, - ) - except AuthorityError as error: - rejected_records.append(_target_rejection(repository, number, assessment, error)) - metadata["quarantined"] = True - metadata["reconciliation_failure"] = "approved-intake-invalid" - if is_trigger: - trigger_assessment = {"approved": False, "reason": "source-targets-invalid"} - continue - inventory[repository].append(issue) - metadata["approved"] = True - metadata["approval_reason"] = str(assessment["reason"]) - metadata["labels"] = sorted(_intake_label_names(issue)) - metadata["specialized_lifecycle"] = bool( - cross_repository_targets or frozen_lifecycle - ) - records.append( - { - "approval_actor": assessment["approval_actor"], - "approval_at": assessment["approval_at"], - "approval_mode": assessment["approval_mode"], - "completion_evidence_required": COMPLETION_REQUIRED_LABEL in _intake_label_names(issue), - "cross_repository_targets": cross_repository_targets, - "frozen_cross_repository_lifecycle": frozen_lifecycle, - "historical_cross_repository_completion": historical_completion, - "number": number, - "repository": repository, - "revision": assessment["revision"], - "superseded_by": None, - } - ) - - list_pull_requests = getattr(client, "list_pull_requests", None) - if callable(list_pull_requests): - public_metadata.extend(list_pull_requests(policy["organization"], repository)) - - _bind_prerelease_supersessions( - policy, - records, - inventory, - client, - require_activations=require_supersession_activations, - ) - for record in records: - if record["superseded_by"] is not None: - metadata_by_identity[(record["repository"], record["number"])]["specialized_lifecycle"] = True - manifest: dict[str, Any] = { - "schema": INTAKE_SCHEMA, - "organization": policy["organization"], - "policy_digest": _object_digest(policy), - "legacy_target_migration_digest": _object_digest(legacy_cross_repository_targets or {}), - "issues": records, - "public_metadata": sorted( - public_metadata, - key=lambda record: (str(record["repository"]), str(record["type"]), int(record["number"])), - ), - "rejected_issues": rejected_records, - } - if trigger_repository is not None or trigger_number is not None: - approved = bool(trigger_assessment and trigger_assessment["approved"]) - reason = trigger_assessment["reason"] if trigger_assessment else "trigger-issue-not-found" - manifest["trigger"] = { - "action": trigger_action, - "approved": approved, - "number": trigger_number, - "reason": reason, - "repository": trigger_repository, - } - return manifest, inventory - - -def activate_prerelease_supersessions( - policy: dict[str, Any], - discovery: Any, - client: Any, - *, - target_qualification: Mapping[str, Any] | None = None, - legacy_cross_repository_targets: Mapping[str, Any] | None = None, -) -> dict[str, Any]: - """Create only missing status authority derived from the current trusted intake.""" - - manifest, _inventory = reconstruct_intake( - policy, - discovery, - target_qualification=target_qualification, - legacy_cross_repository_targets=legacy_cross_repository_targets, - require_supersession_activations=False, - ) - supersessions = _manifest_prerelease_supersessions(manifest) - activations: list[dict[str, str]] = [] - for supersession in policy["prerelease_supersessions"]: - retired = supersession["retired"] - identity = (retired["repository"], retired["number"]) - bound = supersessions.get(identity) - if bound is None: - raise AuthorityError( - f"prerelease supersession {retired['repository']}#{retired['number']} " - "did not bind to trusted current-revision intake" - ) - activations.append(dict(bound["activation"])) - - authority_repository = policy["authority_repository"] - statuses_by_commit: dict[str, list[dict[str, Any]]] = {} - recorded: list[bool] = [] - for activation in activations: - commit = activation["commit"] - if commit not in statuses_by_commit: - statuses_by_commit[commit] = client.list_commit_statuses( - policy["organization"], - authority_repository, - commit, - ) - recorded.append( - _supersession_activation_is_recorded( - statuses_by_commit[commit], - activation, - ) - ) - - evidence_activations: list[dict[str, Any]] = [] - for activation, already_recorded in zip(activations, recorded, strict=True): - created = False - if not already_recorded: - created = client.ensure_supersession_activation( - policy["organization"], - authority_repository, - activation, - ) - evidence_activations.append( - { - **activation, - "creator": { - "id": GITHUB_ACTIONS_BOT_ID, - "login": GITHUB_ACTIONS_BOT_LOGIN, - "type": "Bot", - }, - "result": "created" if created else "existing", - } - ) - - return { - "schema": "durable-workflow.github-issue-authority-evidence/v1", - "mode": "activate", - "outcome": "pass", - "activations": evidence_activations, - "intake": _manifest_core(manifest), - } - - -def _valid_superseded_by( - value: Any, - repositories: Collection[str], -) -> bool: - if not isinstance(value, Mapping): - return False - activation = value.get("activation") - if ( - not isinstance(activation, Mapping) - or set(activation) != {"commit", "context", "digest"} - or not re.fullmatch(r"[0-9a-f]{40}", str(activation.get("commit", ""))) - or not isinstance(activation.get("context"), str) - or not activation["context"].startswith(SUPERSESSION_ACTIVATION_CONTEXT_PREFIX + "/") - or not re.fullmatch(r"[0-9a-f]{64}", str(activation.get("digest", ""))) - or not isinstance(value.get("reason"), str) - or not value["reason"] - or not isinstance(value.get("repository"), str) - or value["repository"] not in repositories - ): - return False - - if "number" in value: - return ( - set(value) == {"activation", "number", "reason", "repository", "revision"} - and type(value.get("number")) is int - and value["number"] > 0 - and isinstance(value.get("revision"), str) - and re.fullmatch(r"[0-9a-f]{64}", value["revision"]) is not None - ) - - release_plan = value.get("release_plan") - return ( - set(value) - == { - "activation", - "commit", - "path", - "reason", - "release_plan", - "repository", - "sha256", - "train", - } - and re.fullmatch(r"[0-9a-f]{40}", str(value.get("commit", ""))) is not None - and value.get("path") == "product-train/current.json" - and re.fullmatch(r"[0-9a-f]{64}", str(value.get("sha256", ""))) is not None - and PRODUCT_TRAIN_IDENTIFIER_PATTERN.fullmatch(str(value.get("train", ""))) is not None - and isinstance(release_plan, Mapping) - and set(release_plan) == {"sha256", "tag"} - and re.fullmatch(r"[0-9a-f]{64}", str(release_plan.get("sha256", ""))) is not None - and re.fullmatch(r"release-plan/[a-z0-9][a-z0-9-]{2,79}", str(release_plan.get("tag", ""))) is not None - ) - - -def verify_intake_manifest( - policy: dict[str, Any], - manifest: Mapping[str, Any], - client: Any, - *, - target_qualification: Mapping[str, Any] | None = None, - legacy_cross_repository_targets: Mapping[str, Any] | None = None, -) -> dict[str, list[dict[str, Any]]]: - if manifest.get("schema") != INTAKE_SCHEMA: - raise AuthorityError("issue-intake manifest uses an unsupported schema") - if ( - manifest.get("organization") != policy["organization"] - or manifest.get("policy_digest") != _object_digest(policy) - or manifest.get("legacy_target_migration_digest") != _object_digest(legacy_cross_repository_targets or {}) - ): - raise AuthorityError("vetted issue revisions changed after read-only discovery") - - records = manifest.get("issues") - rejected_records = manifest.get("rejected_issues") - if not isinstance(records, list) or not isinstance(rejected_records, list): - raise AuthorityError("issue-intake manifest has no complete issue record lists") - - record_keys = { - "approval_actor", - "approval_at", - "approval_mode", - "completion_evidence_required", - "cross_repository_targets", - "frozen_cross_repository_lifecycle", - "historical_cross_repository_completion", - "number", - "repository", - "revision", - "superseded_by", - } - inventory: dict[str, list[dict[str, Any]]] = {repository: [] for repository in policy["repositories"]} - identities: set[tuple[str, int]] = set() - intake_policy = policy["intake"] - lifecycle_targets = ( - cross_repository_lifecycle.qualification_targets(target_qualification) - if target_qualification is not None - else {} - ) - current_records: list[dict[str, Any]] = [] - for record in records: - if not isinstance(record, Mapping) or set(record) != record_keys: - raise AuthorityError("issue-intake manifest contains a malformed issue record") - repository = record.get("repository") - number = record.get("number") - superseded_by = record.get("superseded_by") - if ( - not isinstance(repository, str) - or repository not in inventory - or not isinstance(number, int) - or isinstance(number, bool) - or number < 1 - or not isinstance(record.get("completion_evidence_required"), bool) - or not isinstance(record.get("cross_repository_targets"), list) - or ( - record.get("frozen_cross_repository_lifecycle") is not None - and not isinstance(record.get("frozen_cross_repository_lifecycle"), Mapping) - ) - or not isinstance(record.get("historical_cross_repository_completion"), list) - or (superseded_by is not None and not _valid_superseded_by(superseded_by, inventory)) - ): - raise AuthorityError("issue-intake manifest contains invalid issue authority") - identity = (repository, number) - if identity in identities: - raise AuthorityError("issue-intake manifest contains a duplicate issue identity") - identities.add(identity) - - issue, timeline = client.get_issue(policy["organization"], repository, number) - assessment = assess_issue_intake( - issue, - timeline, - approval_label=intake_policy["approval_label"], - trusted_actors=intake_policy["trusted_actors"], - ) - if not assessment["approved"]: - raise AuthorityError("vetted issue revisions changed after read-only discovery") - expected_targets = _issue_cross_repository_targets( - repository, - issue, - timeline, - assessment, - lifecycle_targets, - legacy_cross_repository_targets, - organization=policy["organization"], - ) - expected_historical_completion = _legacy_historical_completion( - repository, - issue, - timeline, - assessment, - expected_targets, - legacy_cross_repository_targets, - ) - expected_frozen_lifecycle = _frozen_lifecycle_migration( - repository, - issue, - assessment, - expected_targets, - legacy_cross_repository_targets, - ) - current = { - "approval_actor": assessment.get("approval_actor"), - "approval_at": assessment.get("approval_at"), - "approval_mode": assessment.get("approval_mode"), - "completion_evidence_required": record["completion_evidence_required"], - "cross_repository_targets": expected_targets, - "frozen_cross_repository_lifecycle": expected_frozen_lifecycle, - "historical_cross_repository_completion": expected_historical_completion, - "number": issue.get("number"), - "repository": repository, - "revision": assessment.get("revision"), - "superseded_by": None, - } - current_records.append(current) - inventory[repository].append(issue) - - rejection_keys = { - "approval_actor", - "approval_at", - "approval_mode", - "number", - "reason", - "repository", - "revision", - } - current_rejections: list[dict[str, Any]] = [] - for rejection in rejected_records: - if not isinstance(rejection, Mapping) or set(rejection) != rejection_keys: - raise AuthorityError("issue-intake manifest contains a malformed rejected issue record") - repository = rejection.get("repository") - number = rejection.get("number") - if ( - not isinstance(repository, str) - or repository not in inventory - or type(number) is not int - or number < 1 - or not isinstance(rejection.get("reason"), str) - or not rejection["reason"] - ): - raise AuthorityError("issue-intake manifest contains invalid rejected issue authority") - identity = (repository, number) - if identity in identities: - raise AuthorityError("issue-intake manifest contains a duplicate issue identity") - identities.add(identity) - - issue, timeline = client.get_issue(policy["organization"], repository, number) - assessment = assess_issue_intake( - issue, - timeline, - approval_label=intake_policy["approval_label"], - trusted_actors=intake_policy["trusted_actors"], - ) - if not assessment["approved"]: - raise AuthorityError("vetted issue revisions changed after read-only discovery") - try: - expected_targets = _issue_cross_repository_targets( - repository, - issue, - timeline, - assessment, - lifecycle_targets, - legacy_cross_repository_targets, - organization=policy["organization"], - ) - _legacy_historical_completion( - repository, - issue, - timeline, - assessment, - expected_targets, - legacy_cross_repository_targets, - ) - _frozen_lifecycle_migration( - repository, - issue, - assessment, - expected_targets, - legacy_cross_repository_targets, - ) - except AuthorityError as error: - current_rejections.append(_target_rejection(repository, number, assessment, error)) - else: - raise AuthorityError("vetted issue revisions changed after read-only discovery") - - if [dict(rejection) for rejection in rejected_records] != current_rejections: - raise AuthorityError("vetted issue revisions changed after read-only discovery") - - _bind_prerelease_supersessions(policy, current_records, inventory, client) - if [dict(record) for record in records] != current_records: - raise AuthorityError("vetted issue revisions changed after read-only discovery") - return inventory - - -class GitHubApi: - """Bounded GitHub client for public issue metadata and lifecycle labels.""" - - def __init__( - self, - token: str, - api_url: str = "https://api.github.com", - *, - activation_token: str | None = None, - read_token: str | None = None, - graphql_url: str = "https://api.github.com/graphql", - ) -> None: - if not token: - raise AuthorityError("BETA_PRODUCT_WORK_TOKEN is required for cross-repository issue authority") - self.api_url = api_url.rstrip("/") - self.graphql_url = graphql_url - self.headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "User-Agent": "durable-workflow-issue-authority/1", - "X-GitHub-Api-Version": "2022-11-28", - } - self.read_headers = { - **self.headers, - "Authorization": f"Bearer {read_token if read_token is not None else token}", - } - self.activation_headers = { - **self.headers, - "Authorization": f"Bearer {activation_token if activation_token is not None else token}", - } - self._writer_identity: tuple[int, str] | None = None - - @staticmethod - def _error_detail(error: urllib.error.HTTPError) -> str: - try: - return error.read().decode("utf-8", errors="replace")[:600] - except OSError: - return "response body unavailable" - - def _request( - self, - method: str, - path: str, - payload: Mapping[str, Any] | None = None, - *, - writer_authenticated_read: bool = False, - ) -> Any: - if ( - payload is not None - and isinstance(payload.get("labels"), Sequence) - and not isinstance(payload.get("labels"), str | bytes) - and re.fullmatch(r"/repos/[^/]+/[^/]+/issues(?:/[1-9][0-9]*)?(?:/labels)?", path) - ): - raw_labels = payload["labels"] - if not all(isinstance(label, str) for label in raw_labels): - raise AuthorityError(f"GitHub issue writer {path} received invalid labels") - _require_exact_kind_label(raw_labels, f"GitHub issue writer {path}") - body = None - headers = dict(self.read_headers if method == "GET" and not writer_authenticated_read else self.headers) - if payload is not None: - body = json.dumps(payload, separators=(",", ":")).encode("utf-8") - headers["Content-Type"] = "application/json" - request = urllib.request.Request(f"{self.api_url}{path}", data=body, headers=headers, method=method) - # A lost response to POST may mean GitHub accepted the mutation. Never - # repeat a create request: the next workflow run rediscovers it by its - # stable marker or unique metadata name before trying again. - attempts = 1 if method == "POST" else GITHUB_API_ATTEMPTS - for attempt in range(1, attempts + 1): - try: - with urllib.request.urlopen(request, timeout=30) as response: - response_body = response.read() - return json.loads(response_body) if response_body else None - except urllib.error.HTTPError as error: - detail = self._error_detail(error) - retryable = error.code == 429 or 500 <= error.code <= 599 - if not retryable or attempt == attempts: - raise AuthorityError(f"GitHub API {method} {path} returned {error.code}: {detail}") from error - except (urllib.error.URLError, ConnectionError, TimeoutError) as error: - if attempt == attempts: - raise AuthorityError(f"GitHub API {method} {path} failed after bounded retries: {error}") from error - time.sleep(GITHUB_API_RETRY_SECONDS * (2 ** (attempt - 1))) - raise AssertionError("GitHub API retry loop ended unexpectedly") - - def request(self, method: str, path: str, payload: Mapping[str, Any] | None = None) -> Any: - return self._request(method, path, payload) - - def _graphql(self, query: str, variables: Mapping[str, Any]) -> dict[str, Any]: - body = json.dumps({"query": query, "variables": variables}, separators=(",", ":")).encode("utf-8") - headers = {**self.read_headers, "Content-Type": "application/json"} - request = urllib.request.Request(self.graphql_url, data=body, headers=headers, method="POST") - for attempt in range(1, GITHUB_API_ATTEMPTS + 1): - try: - with urllib.request.urlopen(request, timeout=30) as response: - payload = json.loads(response.read()) - if not isinstance(payload, dict) or payload.get("errors") or not isinstance(payload.get("data"), dict): - raise AuthorityError("GitHub GraphQL lifecycle authority returned errors") - return payload["data"] - except urllib.error.HTTPError as error: - retryable = error.code == 429 or 500 <= error.code <= 599 - if not retryable or attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError(f"GitHub GraphQL lifecycle authority returned {error.code}") from error - except (urllib.error.URLError, ConnectionError, TimeoutError, json.JSONDecodeError) as error: - if attempt == GITHUB_API_ATTEMPTS: - raise AuthorityError("GitHub GraphQL lifecycle authority failed after bounded retries") from error - time.sleep(GITHUB_API_RETRY_SECONDS * (2 ** (attempt - 1))) - raise AssertionError("GitHub GraphQL retry loop ended unexpectedly") - - def _authenticated_writer(self) -> tuple[int, str]: - if self._writer_identity is not None: - return self._writer_identity - user = self._request("GET", "/user", writer_authenticated_read=True) - identifier = user.get("id") if isinstance(user, Mapping) else None - login = user.get("login") if isinstance(user, Mapping) else None - if type(identifier) is not int or identifier < 1 or not isinstance(login, str) or not login: - raise AuthorityError("BETA_PRODUCT_WORK_TOKEN did not identify an authenticated GitHub writer") - self._writer_identity = (identifier, login) - return self._writer_identity - - def list_collection(self, path: str) -> list[dict[str, Any]]: - records: list[dict[str, Any]] = [] - separator = "&" if "?" in path else "?" - for page in range(1, 11): - payload = self.request("GET", f"{path}{separator}per_page=100&page={page}") - if not isinstance(payload, list): - raise AuthorityError(f"GitHub API collection {path} did not return a list") - records.extend(record for record in payload if isinstance(record, dict)) - if len(payload) < 100: - return records - raise AuthorityError(f"GitHub API collection {path} exceeded the pagination bound") - - def list_commit_statuses( - self, - organization: str, - repository: str, - commit: str, - ) -> list[dict[str, Any]]: - encoded_commit = urllib.parse.quote(commit, safe="") - return self.list_collection(f"/repos/{organization}/{repository}/commits/{encoded_commit}/statuses") - - def ensure_supersession_activation( - self, - organization: str, - repository: str, - activation: Mapping[str, str], - ) -> bool: - statuses = self.list_commit_statuses( - organization, - repository, - activation["commit"], - ) - if _supersession_activation_is_recorded(statuses, activation): - return False - - encoded_commit = urllib.parse.quote(activation["commit"], safe="") - payload = { - "context": activation["context"], - "description": SUPERSESSION_ACTIVATION_DESCRIPTION_PREFIX + activation["digest"], - "state": "success", - } - request = urllib.request.Request( - f"{self.api_url}/repos/{organization}/{repository}/statuses/{encoded_commit}", - data=json.dumps(payload, separators=(",", ":")).encode("utf-8"), - headers={**self.activation_headers, "Content-Type": "application/json"}, - method="POST", - ) - try: - with urllib.request.urlopen(request, timeout=30) as response: - result = json.loads(response.read()) - except ( - urllib.error.HTTPError, - urllib.error.URLError, - ConnectionError, - TimeoutError, - json.JSONDecodeError, - ) as error: - raise AuthorityError("GitHub could not persist immutable prerelease supersession activation") from error - if not isinstance(result, Mapping) or not _supersession_activation_is_recorded([result], activation): - raise AuthorityError("GitHub returned untrusted prerelease supersession activation") - return True - - def ensure_labels( - self, - organization: str, - repository: str, - desired: Sequence[dict[str, str]], - ) -> list[str]: - base = f"/repos/{organization}/{repository}/labels" - existing = {record.get("name"): record for record in self.list_collection(base)} - changes: list[str] = [] - for label in desired: - current = existing.get(label["name"]) - if current is None: - self.request("POST", base, label) - changes.append(f"created:{label['name']}") - continue - current_matches = ( - current.get("color", "").lower() == label["color"] - and current.get("description") == label["description"] - ) - if current_matches: - continue - encoded_name = urllib.parse.quote(label["name"], safe="") - update = { - "new_name": label["name"], - "color": label["color"], - "description": label["description"], - } - self.request("PATCH", f"{base}/{encoded_name}", update) - changes.append(f"updated:{label['name']}") - return changes - - def ensure_milestone( - self, - organization: str, - repository: str, - desired: dict[str, Any], - ) -> tuple[int, str | None]: - base = f"/repos/{organization}/{repository}/milestones" - existing = {record.get("title"): record for record in self.list_collection(f"{base}?state=all")} - current = existing.get(desired["title"]) - payload = { - "title": desired["title"], - "description": desired["description"], - "state": desired["state"], - } - if current is None: - created = self.request("POST", base, payload) - return int(created["number"]), "created" - number = int(current["number"]) - if current.get("description") == desired["description"] and current.get("state") == desired["state"]: - return number, None - self.request("PATCH", f"{base}/{number}", payload) - return number, "updated" - - def list_issues(self, organization: str, repository: str) -> list[dict[str, Any]]: - records = self.list_collection(f"/repos/{organization}/{repository}/issues?state=all&direction=asc") - return [record for record in records if "pull_request" not in record] - - def create_issue( - self, - organization: str, - repository: str, - *, - title: str, - body: str, - labels: Sequence[str], - milestone: int, - ) -> dict[str, Any]: - _require_exact_kind_label(labels, f"new GitHub issue {repository}/{title}") - result = self.request( - "POST", - f"/repos/{organization}/{repository}/issues", - {"title": title, "body": body, "labels": list(labels), "milestone": milestone}, - ) - if not isinstance(result, dict): - raise AuthorityError(f"GitHub did not return the created issue for {repository}/{title}") - return result - - def replace_issue_labels( - self, - organization: str, - repository: str, - number: int, - labels: Sequence[str], - ) -> None: - _require_exact_kind_label(labels, f"GitHub issue {repository}#{number}") - self.request( - "PUT", - f"/repos/{organization}/{repository}/issues/{number}/labels", - {"labels": sorted(set(labels))}, - ) - - def update_issue_body( - self, - organization: str, - repository: str, - number: int, - body: str, - ) -> None: - self.request("PATCH", f"/repos/{organization}/{repository}/issues/{number}", {"body": body}) - - def update_issue_state( - self, - organization: str, - repository: str, - number: int, - state: str, - *, - state_reason: str, - ) -> None: - valid_state_reasons = { - "closed": {"completed", "not_planned"}, - "open": {"reopened"}, - } - if state_reason not in valid_state_reasons.get(state, set()): - raise AuthorityError(f"invalid GitHub issue state reason {state_reason!r} for {state!r} state") - self.request( - "PATCH", - f"/repos/{organization}/{repository}/issues/{number}", - {"state": state, "state_reason": state_reason}, - ) - - def list_issue_closing_references( - self, - organization: str, - repository: str, - number: int, - ) -> list[dict[str, Any]]: - records: list[dict[str, Any]] = [] - cursor: str | None = None - for _page in range(10): - data = self._graphql( - CLOSING_REFERENCE_QUERY, - { - "cursor": cursor, - "number": number, - "owner": organization, - "repository": repository, - }, - ) - repository_node = data.get("repository") - issue = repository_node.get("issue") if isinstance(repository_node, Mapping) else None - connection = issue.get("timelineItems") if isinstance(issue, Mapping) else None - if not isinstance(connection, Mapping) or not isinstance(connection.get("nodes"), list): - raise AuthorityError(f"GitHub GraphQL lifecycle authority cannot read {repository}/{number}") - records.extend(dict(node) for node in connection["nodes"] if isinstance(node, Mapping)) - page_info = connection.get("pageInfo") - if not isinstance(page_info, Mapping): - raise AuthorityError("GitHub GraphQL lifecycle authority returned malformed pagination") - if not page_info.get("hasNextPage"): - return records - cursor = page_info.get("endCursor") - if not isinstance(cursor, str) or not cursor: - raise AuthorityError("GitHub GraphQL lifecycle authority omitted its next cursor") - raise AuthorityError( - f"GitHub GraphQL lifecycle authority for {repository}/{number} exceeded the pagination bound" - ) - - def get_pull_request(self, organization: str, repository: str, number: int) -> dict[str, Any]: - result = self.request("GET", f"/repos/{organization}/{repository}/pulls/{number}") - if not isinstance(result, dict): - raise AuthorityError(f"GitHub did not return linked pull request {repository}#{number}") - return result - - def list_pull_request_reviews( - self, - organization: str, - repository: str, - number: int, - ) -> list[dict[str, Any]]: - return self.list_collection(f"/repos/{organization}/{repository}/pulls/{number}/reviews") - - def commit_reaches_branch( - self, - organization: str, - repository: str, - commit: str, - branch: str, - ) -> bool: - encoded_commit = urllib.parse.quote(commit, safe="") - encoded_branch = urllib.parse.quote(branch, safe="") - comparison = self.request( - "GET", - f"/repos/{organization}/{repository}/compare/{encoded_commit}...{encoded_branch}", - ) - return isinstance(comparison, dict) and comparison.get("status") in {"ahead", "identical"} - - def commit_contains( - self, - organization: str, - repository: str, - descendant: str, - ancestor: str, - ) -> bool: - encoded_ancestor = urllib.parse.quote(ancestor, safe="") - encoded_descendant = urllib.parse.quote(descendant, safe="") - comparison = self.request( - "GET", - f"/repos/{organization}/{repository}/compare/{encoded_ancestor}...{encoded_descendant}", - ) - return isinstance(comparison, dict) and comparison.get("status") in {"ahead", "identical"} - - def _latest_check_runs(self, organization: str, repository: str, commit: str) -> dict[str, dict[str, Any]]: - encoded_commit = urllib.parse.quote(commit, safe="") - runs: list[dict[str, Any]] = [] - for page in range(1, 11): - payload = self.request( - "GET", - f"/repos/{organization}/{repository}/commits/{encoded_commit}/check-runs?per_page=100&page={page}", - ) - page_runs = payload.get("check_runs") if isinstance(payload, dict) else None - if not isinstance(page_runs, list): - raise AuthorityError(f"GitHub did not return check runs for {repository}@{commit}") - runs.extend(run for run in page_runs if isinstance(run, dict)) - if len(page_runs) < 100: - break - else: - raise AuthorityError(f"GitHub check runs for {repository}@{commit} exceeded the pagination bound") - latest: dict[str, tuple[tuple[str, int], dict[str, Any]]] = {} - for run in runs: - name = run.get("name") - identifier = run.get("id") - if not isinstance(name, str) or not isinstance(identifier, int): - continue - timestamp = run.get("completed_at") or run.get("started_at") or "" - ordering = (timestamp if isinstance(timestamp, str) else "", identifier) - if name not in latest or ordering > latest[name][0]: - latest[name] = (ordering, run) - return {name: run for name, (_ordering, run) in latest.items()} - - def successful_check_names(self, organization: str, repository: str, commit: str) -> set[str]: - return { - name - for name, run in self._latest_check_runs(organization, repository, commit).items() - if run.get("status") == "completed" and run.get("conclusion") == "success" - } - - def successful_check_run_ids(self, organization: str, repository: str, commit: str) -> dict[str, int]: - """Bind each latest green check name to its immutable GitHub Actions run identity.""" - - pattern = re.compile( - rf"https://github\.com/{re.escape(organization)}/{re.escape(repository)}/actions/runs/" - r"([1-9][0-9]*)(?:/job/[1-9][0-9]*)?(?:\?[^#\s]*)?" - ) - identities: dict[str, int] = {} - for name, run in self._latest_check_runs(organization, repository, commit).items(): - details_url = run.get("details_url") - match = pattern.fullmatch(details_url) if isinstance(details_url, str) else None - if run.get("status") == "completed" and run.get("conclusion") == "success" and match is not None: - identities[name] = int(match.group(1)) - return identities - - def successful_workflow_run( - self, - organization: str, - repository: str, - run_id: int, - commit: str, - workflow_path: str | None, - workflow_name: str | None, - ) -> bool: - """Verify one cited run's repository, workflow, commit, and successful conclusion.""" - - run = self.request("GET", f"/repos/{organization}/{repository}/actions/runs/{run_id}") - run_repository = run.get("repository") if isinstance(run, Mapping) else None - run_workflow_name = run.get("name") if isinstance(run, Mapping) else None - run_workflow_path = run.get("path") if isinstance(run, Mapping) else None - run_workflow_id = run.get("workflow_id") if isinstance(run, Mapping) else None - actual_workflow_is_identified = ( - isinstance(run_workflow_name, str) - and bool(run_workflow_name.strip()) - and isinstance(run_workflow_path, str) - and re.fullmatch(r"\.github/workflows/[A-Za-z0-9_.-]+\.ya?ml", run_workflow_path) is not None - and type(run_workflow_id) is int - and run_workflow_id > 0 - ) - expected_workflow_path = f".github/workflows/{workflow_path}" if workflow_path is not None else None - run_matches = ( - isinstance(run, Mapping) - and type(run.get("id")) is int - and run["id"] == run_id - and run.get("status") == "completed" - and run.get("conclusion") == "success" - and run.get("head_sha") == commit - and actual_workflow_is_identified - and (expected_workflow_path is None or run_workflow_path == expected_workflow_path) - and run.get("html_url") == f"https://github.com/{organization}/{repository}/actions/runs/{run_id}" - and isinstance(run_repository, Mapping) - and run_repository.get("full_name") == f"{organization}/{repository}" - ) - if not run_matches: - return False - - workflow = self.request( - "GET", - f"/repos/{organization}/{repository}/actions/workflows/{run_workflow_id}", - ) - definition_name = workflow.get("name") if isinstance(workflow, Mapping) else None - return ( - isinstance(workflow, Mapping) - and type(workflow.get("id")) is int - and workflow["id"] == run_workflow_id - and isinstance(definition_name, str) - and bool(definition_name.strip()) - and workflow.get("path") == run_workflow_path - and (workflow_name is None or definition_name == workflow_name) - ) - - def successful_historical_workflow_run( - self, - organization: str, - repository: str, - run_id: int, - commit: str, - branch: str, - workflow_path: str, - workflow_name: str, - ) -> bool: - """Verify immutable run metadata without consulting a renamed current workflow.""" - - run = self.request("GET", f"/repos/{organization}/{repository}/actions/runs/{run_id}") - run_repository = run.get("repository") if isinstance(run, Mapping) else None - return ( - isinstance(run, Mapping) - and type(run.get("id")) is int - and run["id"] == run_id - and run.get("status") == "completed" - and run.get("conclusion") == "success" - and run.get("head_sha") == commit - and run.get("event") == "push" - and run.get("head_branch") == branch - and run.get("name") == workflow_name - and run.get("path") == f".github/workflows/{workflow_path}" - and run.get("html_url") - == f"https://github.com/{organization}/{repository}/actions/runs/{run_id}" - and isinstance(run_repository, Mapping) - and run_repository.get("full_name") == f"{organization}/{repository}" - ) - - def successful_historical_workflow_jobs( - self, - organization: str, - repository: str, - run_id: int, - ) -> dict[str, int]: - """Return successful latest-attempt jobs bound to one immutable workflow run.""" - - jobs: list[dict[str, Any]] = [] - for page in range(1, 11): - payload = self.request( - "GET", - f"/repos/{organization}/{repository}/actions/runs/{run_id}/jobs" - f"?filter=latest&per_page=100&page={page}", - ) - page_jobs = payload.get("jobs") if isinstance(payload, Mapping) else None - if not isinstance(page_jobs, list) or not all(isinstance(job, dict) for job in page_jobs): - raise AuthorityError("GitHub Actions run jobs did not return a complete collection") - jobs.extend(page_jobs) - if len(page_jobs) < 100: - successful: dict[str, int] = {} - for job in jobs: - if ( - isinstance(job.get("name"), str) - and type(job.get("id")) is int - and job["id"] > 0 - and job.get("status") == "completed" - and job.get("conclusion") == "success" - and isinstance(job.get("html_url"), str) - and re.fullmatch( - rf"https://github\.com/{re.escape(organization)}/" - rf"{re.escape(repository)}/actions/runs/{run_id}/job/[1-9][0-9]*", - job["html_url"], - ) - is not None - ): - name = str(job["name"]) - if name in successful: - raise AuthorityError( - "GitHub Actions run contains duplicate successful job names" - ) - successful[name] = int(job["id"]) - return successful - raise AuthorityError("GitHub Actions run jobs exceeded the pagination bound") - - def list_trusted_issue_comments( - self, - organization: str, - repository: str, - number: int, - ) -> list[dict[str, Any]]: - """Return comments owned by the exact authenticated lifecycle writer identity.""" - - comments = self.list_collection(f"/repos/{organization}/{repository}/issues/{number}/comments") - writer_id, writer_login = self._authenticated_writer() - return [ - comment - for comment in comments - if ( - isinstance(comment.get("body"), str) - and type(comment.get("id")) is int - and isinstance(comment.get("user"), Mapping) - and type(comment["user"].get("id")) is int - and comment["user"]["id"] == writer_id - and isinstance(comment["user"].get("login"), str) - and comment["user"]["login"].casefold() == writer_login.casefold() - ) - ] - - def upsert_lifecycle_comment( - self, - organization: str, - repository: str, - number: int, - marker: str, - body: str, - ) -> bool: - comment = self._managed_lifecycle_comment(organization, repository, number, marker) - if comment is not None: - if comment["body"] == body: - return False - comment_id = comment.get("id") - if not isinstance(comment_id, int): - raise AuthorityError(f"GitHub issue {repository}#{number} has lifecycle evidence without an identity") - self.request("PATCH", f"/repos/{organization}/{repository}/issues/comments/{comment_id}", {"body": body}) - return True - self.request("POST", f"/repos/{organization}/{repository}/issues/{number}/comments", {"body": body}) - return True - - def _managed_lifecycle_comment( - self, - organization: str, - repository: str, - number: int, - marker: str, - ) -> dict[str, Any] | None: - comments = self.list_trusted_issue_comments(organization, repository, number) - matches = [comment for comment in comments if marker in comment["body"]] - if len(matches) > 1: - raise AuthorityError( - f"GitHub issue {repository}#{number} has duplicate cross-repository lifecycle evidence" - ) - return matches[0] if matches else None - - def managed_lifecycle_comment_body( - self, - organization: str, - repository: str, - number: int, - marker: str, - ) -> str | None: - comment = self._managed_lifecycle_comment(organization, repository, number, marker) - return str(comment["body"]) if comment is not None else None - - def has_managed_lifecycle_comment( - self, - organization: str, - repository: str, - number: int, - marker: str | None = None, - ) -> bool: - markers = ( - {marker} - if marker is not None - else { - FROZEN_LIFECYCLE_EVIDENCE_MARKER, - PUBLIC_LIFECYCLE_MARKER, - SUPERSESSION_EVIDENCE_MARKER, - cross_repository_lifecycle.EVIDENCE_MARKER, - } - ) - return any( - any(marker in comment["body"] for marker in markers) - for comment in self.list_trusted_issue_comments(organization, repository, number) - ) - - -def _label_names(issue: dict[str, Any]) -> set[str]: - names: set[str] = set() - for label in issue.get("labels") or []: - if isinstance(label, str): - names.add(label) - elif isinstance(label, dict) and isinstance(label.get("name"), str): - names.add(label["name"]) - return names - - -def _kind_label_names(labels: Collection[str]) -> set[str]: - return {label for label in labels if label.startswith("kind:")} - - -def _has_trusted_public_retirement( - client: Any, - organization: str, - repository: str, - number: int, -) -> bool: - """Recognize only the authenticated lifecycle writer's retirement record.""" - - records = [] - for comment in client.list_trusted_issue_comments(organization, repository, number): - lines = comment["body"].splitlines() - if PUBLIC_LIFECYCLE_MARKER in lines: - records.append(lines) - if len(records) > 1: - raise AuthorityError(f"GitHub issue {repository}#{number} has duplicate public lifecycle records") - if not records: - return False - marker_count = records[0].count(PUBLIC_RETIREMENT_RECORD_MARKER) - if marker_count > 1: - raise AuthorityError(f"GitHub issue {repository}#{number} has duplicate public retirement markers") - return marker_count == 1 - - -def _require_exact_kind_label(labels: Collection[str], location: str) -> str: - kinds = _kind_label_names(labels) - if len(kinds) != 1: - raise AuthorityError(f"{location} label write must contain exactly one kind:* label, got {sorted(kinds)}") - kind = next(iter(kinds)) - if kind not in KIND_LABELS: - raise AuthorityError(f"{location} label write contains unsupported lifecycle kind {kind!r}") - return kind - - -def _replace_kind_label(labels: Collection[str], kind: str) -> set[str]: - """Replace, rather than append, the kind at completion and release writer boundaries.""" - - if kind not in KIND_LABELS: - raise AuthorityError(f"unsupported lifecycle kind transition {kind!r}") - replacement = {label for label in labels if not label.startswith("kind:")} - replacement.add(kind) - _require_exact_kind_label(replacement, "lifecycle kind transition") - return replacement - - -def _issue_url(issue: dict[str, Any], organization: str, repository: str) -> str: - url = issue.get("html_url") - if isinstance(url, str) and url.startswith("https://github.com/"): - return url - number = issue.get("number") - if not isinstance(number, int): - raise AuthorityError(f"GitHub issue in {repository} has no numeric identity") - return f"https://github.com/{organization}/{repository}/issues/{number}" - - -def _render_supersession_evidence( - organization: str, - retired_repository: str, - retired_number: int, - successor: Mapping[str, Any], -) -> str: - successor_repository = str(successor["repository"]) - if "number" in successor: - successor_number = int(successor["number"]) - successor_url = f"https://github.com/{organization}/{successor_repository}/issues/{successor_number}" - successor_line = f"[{organization}/{successor_repository}#{successor_number}]({successor_url})" - else: - successor_commit = str(successor["commit"]) - successor_path = str(successor["path"]) - successor_url = ( - f"https://github.com/{organization}/{successor_repository}/blob/{successor_commit}/{successor_path}" - ) - successor_line = ( - f"[{successor['train']} immutable product train]({successor_url}) (`{successor['release_plan']['tag']}`)" - ) - return ( - f"{SUPERSESSION_EVIDENCE_MARKER}\n" - "This prerelease authority is retired without recording completion of its original acceptance criteria.\n\n" - f"- Successor: {successor_line}\n" - f"- Reason: {successor['reason']}\n" - f"- Retired authority: `{organization}/{retired_repository}#{retired_number}`\n" - ) - - -def _item_labels(item: dict[str, Any]) -> list[str]: - classification = { - "blocker": "beta:blocker", - "compatible": "beta:compatible", - "post-2.0": "post-2.0", - }[item["classification"]] - return sorted( - { - "authority:github", - f"kind:{item['kind']}", - f"priority:{item['priority']}", - f"status:{item['status']}", - classification, - OWNER_LABELS[item["repository"]], - } - ) - - -def _render_unblock_context(item: dict[str, Any]) -> str: - unblock_condition = item.get("unblock_condition") - if not unblock_condition: - return "" - return f"{UNBLOCK_CONTEXT_START}\n## Unblock condition\n\n{unblock_condition.rstrip()}\n{UNBLOCK_CONTEXT_END}" - - -def _render_body( - item: dict[str, Any], - dependency_urls: Mapping[str, str], - dependency_titles: Mapping[str, str], -) -> str: - dependencies = item["depends_on"] - if dependencies: - dependency_lines = "\n".join( - f"- [{dependency_titles[dependency]}]({dependency_urls[dependency]})" for dependency in dependencies - ) - else: - dependency_lines = "None." - required_targets = item.get("required_source_targets") - target_section = ( - f"\n\n{cross_repository_lifecycle.TARGET_HEADING}\n\n" + "\n".join(required_targets) - if isinstance(required_targets, list) - else "" - ) - unblock_context = _render_unblock_context(item) - rendered_unblock_context = f"\n\n{unblock_context}" if unblock_context else "" - return ( - f"{item['body'].rstrip()}" - f"{target_section}\n\n" - f"## Dependencies\n\n{dependency_lines}" - f"{rendered_unblock_context}\n\n" - f"\n" - ) - - -def _marker_is_line_bounded(body: str, marker: str, index: int) -> bool: - starts_line = index == 0 or body[index - 1] == "\n" - after = index + len(marker) - ends_line = after == len(body) or body.startswith(("\n", "\r\n"), after) - return starts_line and ends_line - - -def _unblock_context_span(item: dict[str, Any], body: str) -> tuple[int, int] | None: - start_count = body.count(UNBLOCK_CONTEXT_START) - end_count = body.count(UNBLOCK_CONTEXT_END) - if start_count == 0 and end_count == 0: - return None - if start_count != 1 or end_count != 1: - raise AuthorityError(f"GitHub issue for {item['id']} has malformed unblock condition context") - - start = body.index(UNBLOCK_CONTEXT_START) - end = body.index(UNBLOCK_CONTEXT_END) - if ( - start >= end - or not _marker_is_line_bounded(body, UNBLOCK_CONTEXT_START, start) - or not _marker_is_line_bounded(body, UNBLOCK_CONTEXT_END, end) - ): - raise AuthorityError(f"GitHub issue for {item['id']} has malformed unblock condition context") - return start, end + len(UNBLOCK_CONTEXT_END) - - -def _reconcile_unblock_context(item: dict[str, Any], issue: dict[str, Any]) -> str | None: - body = issue.get("body") - if not isinstance(body, str): - raise AuthorityError(f"GitHub issue for {item['id']} has no text body") - context_span = _unblock_context_span(item, body) - desired = _render_unblock_context(item) - if context_span is None and not desired: - return None - if context_span is not None: - start, end = context_span - if desired: - updated = body[:start] + desired + body[end:] - else: - if body[max(0, start - 4) : start] == "\r\n\r\n": - start -= 4 - elif body[max(0, start - 2) : start] == "\n\n": - start -= 2 - updated = body[:start] + body[end:] - else: - marker = f"" - updated = body.replace(marker, f"{desired}\n\n{marker}", 1) - return updated if updated != body else None - - -def _plan_unblock_context_updates( - backlog: dict[str, Any], - resolved: Mapping[str, tuple[str, dict[str, Any]]], -) -> dict[str, str | None]: - return { - item["id"]: _reconcile_unblock_context(item, resolved[item["id"]][1]) - for item in backlog["items"] - if item["id"] in resolved - } - - -def _plan_ready_transition_updates( - backlog: dict[str, Any], - resolved: Mapping[str, tuple[str, dict[str, Any]]], -) -> dict[str, list[str] | None]: - updates: dict[str, list[str] | None] = {} - for item in backlog["items"]: - match = resolved.get(item["id"]) - if match is None: - continue - issue = match[1] - body = issue.get("body") - labels = _label_names(issue) - is_reviewed_ready_transition = ( - item["status"] == "ready" - and not item["depends_on"] - and not item.get("unblock_condition") - and isinstance(body, str) - and _unblock_context_span(item, body) is not None - ) - if ( - is_reviewed_ready_transition - and issue.get("state") == "open" - and labels & STATUS_LABELS == {"status:blocked"} - ): - replacement = labels - STATUS_LABELS | {"status:ready"} - updates[item["id"]] = sorted(replacement) - else: - updates[item["id"]] = None - return updates - - -def _preflight_unblock_context_layouts( - backlog: dict[str, Any], - inventory: Mapping[str, Sequence[dict[str, Any]]], -) -> None: - items = {item["id"]: item for item in backlog["items"]} - for issues in inventory.values(): - for issue in issues: - body = issue.get("body") - if not isinstance(body, str): - continue - for work_id in set(MARKER_PATTERN.findall(body)) & items.keys(): - _unblock_context_span(items[work_id], body) - - -def _inventory(policy: dict[str, Any], client: Any) -> dict[str, list[dict[str, Any]]]: - organization = policy["organization"] - return {repository: client.list_issues(organization, repository) for repository in policy["repositories"]} - - -def sync_metadata(policy: dict[str, Any], client: Any) -> tuple[dict[tuple[str, str], int], dict[str, Any]]: - organization = policy["organization"] - evidence: dict[str, Any] = {"labels": {}, "milestones": {}} - for repository in policy["repositories"]: - evidence["labels"][repository] = client.ensure_labels(organization, repository, policy["labels"]) - - milestone_numbers: dict[tuple[str, str], int] = {} - for milestone in policy["milestones"]: - for repository in milestone["repositories"]: - number, change = client.ensure_milestone(organization, repository, milestone) - milestone_numbers[(repository, milestone["title"])] = number - if change: - evidence["milestones"][f"{repository}/{milestone['title']}"] = change - return milestone_numbers, evidence - - -def _marker_index( - inventory: Mapping[str, Sequence[dict[str, Any]]], -) -> tuple[ - dict[str, list[tuple[str, dict[str, Any]]]], - list[tuple[str, dict[str, Any], list[str]]], -]: - markers: dict[str, list[tuple[str, dict[str, Any]]]] = {} - aliases: list[tuple[str, dict[str, Any], list[str]]] = [] - for repository, issues in inventory.items(): - for issue in issues: - body = issue.get("body") or "" - if not isinstance(body, str): - continue - ids = MARKER_PATTERN.findall(body) - consolidated_ids = CONSOLIDATED_FINDING_PATTERN.findall(body) - if len(ids) != len(set(ids)): - raise AuthorityError(f"issue {repository}/{issue.get('number')} repeats its beta work marker") - if len(consolidated_ids) != len(set(consolidated_ids)): - raise AuthorityError( - f"issue {repository}/{issue.get('number')} repeats a consolidated finding marker" - ) - if set(ids) & set(consolidated_ids): - raise AuthorityError( - f"issue {repository}/{issue.get('number')} repeats one work identity across marker kinds" - ) - distinct_ids = sorted(set(ids)) - if len(distinct_ids) > 1: - aliases.append((repository, issue, distinct_ids)) - for work_id in [*ids, *consolidated_ids]: - markers.setdefault(work_id, []).append((repository, issue)) - return markers, aliases - - -def _mark_conflicts(policy: dict[str, Any], client: Any, matches: Sequence[tuple[str, dict[str, Any]]]) -> None: - organization = policy["organization"] - for repository, issue in matches: - labels = _label_names(issue) | {"authority:github", "authority:conflict"} - client.replace_issue_labels(organization, repository, int(issue["number"]), sorted(labels)) - - -def _preflight_markers( - policy: dict[str, Any], - backlog: dict[str, Any], - client: Any, - inventory: Mapping[str, Sequence[dict[str, Any]]], - *, - allow_missing: bool, -) -> dict[str, tuple[str, dict[str, Any]]]: - selected_ids = {item["id"] for item in backlog["items"]} - markers, aliases = _marker_index(inventory) - if aliases: - failures = [ - f"{repository}#{issue.get('number')} contains multiple distinct beta work ids {work_ids}" - for repository, issue, work_ids in aliases - ] - raise AuthorityError("issue authority marker audit failed: " + "; ".join(failures)) - unknown = set(markers) - selected_ids - if unknown: - for work_id in sorted(unknown): - _mark_conflicts(policy, client, markers[work_id]) - raise AuthorityError(f"GitHub contains beta work ids absent from the reviewed backlog: {sorted(unknown)}") - - resolved: dict[str, tuple[str, dict[str, Any]]] = {} - failures: list[str] = [] - for work_id in sorted(selected_ids): - matches = markers.get(work_id, []) - if len(matches) > 1: - _mark_conflicts(policy, client, matches) - failures.append(f"{work_id} appears on {len(matches)} GitHub issues") - elif matches: - repository, issue = matches[0] - expected_repository = next(item["repository"] for item in backlog["items"] if item["id"] == work_id) - if repository != expected_repository: - _mark_conflicts(policy, client, matches) - failures.append(f"{work_id} is in {repository}, expected {expected_repository}") - else: - resolved[work_id] = matches[0] - elif not allow_missing: - failures.append(f"{work_id} has no GitHub issue") - if failures: - raise AuthorityError("issue authority marker audit failed: " + "; ".join(failures)) - return resolved - - -def _evaluate_frozen_lifecycle( - client: Any, - organization: str, - migration: Mapping[str, Any], -) -> dict[str, Any]: - """Revalidate exact frozen landing, workflow-run, and check identities.""" - - if migration["outcome"] == "missing-evidence": - return { - "missing_evidence": migration["missing_evidence"], - "outcome": "missing-evidence", - "targets": [], - } - targets: list[dict[str, Any]] = [] - for landing in migration["landings"]: - repository = landing["repository"] - branch = landing["branch"] - commit = landing["commit"] - qualification = landing["qualification"] - result = { - "branch": branch, - "commit": commit, - "qualification": json.loads(json.dumps(qualification)), - "repository": repository, - "state": "pending:landing-not-on-target", - } - if not client.commit_reaches_branch(organization, repository, commit, branch): - targets.append(result) - continue - if not client.successful_historical_workflow_run( - organization, - repository, - qualification["run"], - commit, - branch, - qualification["workflow_path"], - qualification["workflow_name"], - ): - result["state"] = "pending:qualification-run" - targets.append(result) - continue - successful_checks = client.successful_historical_workflow_jobs( - organization, - repository, - qualification["run"], - ) - if any( - successful_checks.get(check["name"]) != check["job"] - for check in qualification["checks"] - ): - result["state"] = "pending:qualification-check" - targets.append(result) - continue - result["state"] = "complete" - targets.append(result) - incomplete = [ - f"{target['repository']}@{target['branch']}={target['state']}" - for target in targets - if target["state"] != "complete" - ] - return { - "missing_evidence": ( - "Exact frozen aggregate revalidation failed: " + ", ".join(incomplete) - if incomplete - else None - ), - "outcome": "missing-evidence" if incomplete else "complete", - "targets": targets, - } - - -def _render_frozen_lifecycle_evidence( - migration: Mapping[str, Any], - result: Mapping[str, Any], -) -> str: - record = { - "approval": { - "actor_sha256": migration["approval_actor_sha256"], - "at": migration["approval_at"], - "mode": migration["approval_mode"], - }, - "authority_snapshot_sha256": migration["authority_snapshot_sha256"], - "completion_source": migration["completion_source"], - "declared_targets": list(migration["declared_targets"]), - "missing_evidence": result["missing_evidence"], - "outcome": result["outcome"], - "approved_issue_revision_sha256": migration["approved_issue_revision_sha256"], - "schema": "durable-workflow.frozen-cross-repository-lifecycle/v1", - "targets": list(result["targets"]), - } - return ( - f"{FROZEN_LIFECYCLE_EVIDENCE_MARKER}\n" - "Historical cross-repository lifecycle result generated by protected Issue Authority.\n\n" - "```json\n" - + json.dumps(record, indent=2, sort_keys=True) - + "\n```\n" - ) - - -def _audit_state_labels( - policy: dict[str, Any], - client: Any, - inventory: Mapping[str, list[dict[str, Any]]], - approved_completion_holds: set[tuple[str, int]], - cross_repository_targets: Mapping[tuple[str, int], Sequence[Mapping[str, Any]]] | None, - historical_cross_repository_completions: (Mapping[tuple[str, int], Sequence[Mapping[str, Any]]] | None), - frozen_cross_repository_lifecycles: Mapping[tuple[str, int], Mapping[str, Any]] | None, - prerelease_supersessions: Mapping[tuple[str, int], Mapping[str, Any]] | None, -) -> list[str]: - organization = policy["organization"] - failures: list[str] = [] - trusted_retirements: set[tuple[str, int]] = set() - retirement_quarantines: set[tuple[str, int]] = set() - for repository, issues in inventory.items(): - for issue in issues: - number = issue.get("number") - if type(number) is not int: - continue - identity = (repository, number) - try: - if _has_trusted_public_retirement( - client, - organization, - repository, - number, - ): - trusted_retirements.add(identity) - except AuthorityError as error: - failures.append(f"{repository}#{number} retirement record is malformed: {error}") - retirement_quarantines.add(identity) - - historical_completion_identities = set(historical_cross_repository_completions or {}) - frozen_results: dict[tuple[str, int], dict[str, Any]] = {} - for identity, migration in sorted((frozen_cross_repository_lifecycles or {}).items()): - if identity in trusted_retirements or identity in retirement_quarantines: - continue - declared = (cross_repository_targets or {}).get(identity, ()) - declared_contract = sorted( - f"durable-workflow/{target.get('repository')}@{target.get('branch')}" for target in declared - ) - if declared_contract != sorted(migration["declared_targets"]): - raise AuthorityError( - f"{identity[0]}#{identity[1]} frozen lifecycle evidence differs from its declared target set" - ) - frozen_results[identity] = _evaluate_frozen_lifecycle(client, organization, migration) - recorded_landing_results: dict[tuple[str, str, str, tuple[str, ...]], Mapping[str, Any]] = {} - for identity, landings in sorted((historical_cross_repository_completions or {}).items()): - if identity in trusted_retirements or identity in retirement_quarantines: - continue - declared = (cross_repository_targets or {}).get(identity, ()) - if not declared: - raise AuthorityError( - f"{identity[0]}#{identity[1]} has historical completion evidence without a declared target set" - ) - declared_contract = sorted( - ( - str(target.get("repository")), - str(target.get("branch")), - ) - for target in declared - ) - landing_contract = sorted( - ( - str(landing.get("repository")), - str(landing.get("branch")), - ) - for landing in landings - ) - if declared_contract != landing_contract: - raise AuthorityError( - f"{identity[0]}#{identity[1]} historical completion evidence differs from its declared target set" - ) - pending_landings = [ - landing - for landing in landings - if ( - str(landing.get("repository")), - str(landing.get("branch")), - str(landing.get("commit")), - tuple(sorted(str(check) for check in landing.get("required_checks", ()))), - ) - not in recorded_landing_results - ] - try: - if pending_landings: - assessment = cross_repository_lifecycle.evaluate_recorded_landings( - client, - organization, - pending_landings, - ) - for landing, result in zip(pending_landings, assessment["targets"], strict=True): - key = ( - str(landing["repository"]), - str(landing["branch"]), - str(landing["commit"]), - tuple(sorted(str(check) for check in landing["required_checks"])), - ) - recorded_landing_results[key] = result - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - results = [ - recorded_landing_results[ - ( - str(landing["repository"]), - str(landing["branch"]), - str(landing["commit"]), - tuple(sorted(str(check) for check in landing["required_checks"])), - ) - ] - for landing in landings - ] - if any(result["state"] != "complete" for result in results): - landing_failures = [ - f"{target['repository']}@{target['branch']}={target['state']}" - for target in results - if target["state"] != "complete" - ] - raise AuthorityError( - f"{identity[0]}#{identity[1]} historical completion evidence failed revalidation: " - + ", ".join(landing_failures) - ) - - for repository, issues in inventory.items(): - for issue in issues: - labels = _label_names(issue) - number = int(issue["number"]) - location = f"{repository}#{number}" - identity = (repository, number) - if identity in retirement_quarantines: - continue - trusted_retirement = identity in trusted_retirements - if "authority:github" not in labels and not trusted_retirement: - continue - state = issue.get("state") - if ( - state == "closed" - and not trusted_retirement - and not client.has_managed_lifecycle_comment( - organization, - repository, - number, - ) - ): - # Closed history is metadata-only unless this authority already - # owns a managed lifecycle record for an explicit transition. - continue - kinds = _kind_label_names(labels) - if len(kinds) != 1 or not kinds <= KIND_LABELS: - failures.append(f"{location} must have exactly one supported kind:* label, got {sorted(kinds)}") - # A classification choice is maintainer authority. Quarantine - # this identity without guessing while unrelated issues keep - # reconciling in the same aggregate run. - continue - if trusted_retirement: - replacement = labels - STATUS_LABELS - COMPLETION_LABELS - replacement.discard("authority:conflict") - replacement.update({"authority:github", SUPERSEDED_STATUS_LABEL}) - if replacement != labels: - client.replace_issue_labels( - organization, - repository, - number, - sorted(replacement), - ) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - if state != "closed" or issue.get("state_reason") != "not_planned": - client.update_issue_state( - organization, - repository, - number, - "closed", - state_reason="not_planned", - ) - issue["state"] = "closed" - issue["state_reason"] = "not_planned" - if len(labels & PRIORITY_LABELS) != 1: - failures.append(f"{location} must have exactly one priority label") - continue - statuses = labels & STATUS_LABELS - open_statuses_before_lifecycle = statuses & OPEN_STATUS_LABELS - replacement = set(labels) - aggregated_close = False - assessment: dict[str, Any] | None = None - approved_completion_hold = identity in approved_completion_holds - frozen_migration = (frozen_cross_repository_lifecycles or {}).get(identity) - frozen_result = frozen_results.get(identity) - supersession = (prerelease_supersessions or {}).get((repository, number)) - if ( - supersession is None - and frozen_migration is None - and approved_completion_hold - and COMPLETION_REQUIRED_LABEL not in labels - and COMPLETION_VERIFIED_LABEL not in labels - ): - replacement.add(COMPLETION_REQUIRED_LABEL) - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - statuses = labels & STATUS_LABELS - - if supersession is not None: - client.ensure_supersession_activation( - organization, - ".github", - supersession["activation"], - ) - client.upsert_lifecycle_comment( - organization, - repository, - number, - SUPERSESSION_EVIDENCE_MARKER, - _render_supersession_evidence( - organization, - repository, - number, - supersession, - ), - ) - if state != "closed" or issue.get("state_reason") != "not_planned": - client.update_issue_state( - organization, - repository, - number, - "closed", - state_reason="not_planned", - ) - issue["state"] = "closed" - issue["state_reason"] = "not_planned" - replacement = set(labels) - STATUS_LABELS - replacement -= COMPLETION_LABELS - replacement.add(SUPERSEDED_STATUS_LABEL) - if replacement != labels: - client.replace_issue_labels( - organization, - repository, - number, - sorted(replacement), - ) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - if len(labels & KIND_LABELS) != 1: - failures.append(f"{location} must have exactly one kind label") - if len(labels & PRIORITY_LABELS) != 1: - failures.append(f"{location} must have exactly one priority label") - continue - - if frozen_migration is not None: - if frozen_result is None: - raise AuthorityError(f"{location} has no evaluated frozen lifecycle result") - client.upsert_lifecycle_comment( - organization, - repository, - number, - FROZEN_LIFECYCLE_EVIDENCE_MARKER, - _render_frozen_lifecycle_evidence(frozen_migration, frozen_result), - ) - replacement = set(labels) - COMPLETION_LABELS - replacement.add( - COMPLETION_VERIFIED_LABEL - if frozen_result["outcome"] == "complete" - else COMPLETION_REQUIRED_LABEL - ) - if replacement != labels: - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = set(replacement) - statuses = labels & STATUS_LABELS - - completion_is_pending = COMPLETION_REQUIRED_LABEL in labels and COMPLETION_VERIFIED_LABEL not in labels - declared_targets = ( - cross_repository_targets.get((repository, number), ()) if cross_repository_targets is not None else () - ) - target_contract_is_missing = ( - cross_repository_targets is not None and "kind:cross-repository" in labels and not declared_targets - ) - target_completion_is_pending = False - target_contract_failure_reported = False - if frozen_result is not None: - target_completion_is_pending = frozen_result["outcome"] != "complete" - elif (repository, number) in historical_completion_identities: - target_completion_is_pending = False - elif declared_targets: - try: - assessment = cross_repository_lifecycle.evaluate_lifecycle( - client, - organization, - repository, - issue, - declared_targets, - trusted_actors=policy["intake"]["trusted_actors"], - ) - client.upsert_lifecycle_comment( - organization, - repository, - number, - cross_repository_lifecycle.EVIDENCE_MARKER, - cross_repository_lifecycle.render_evidence(assessment), - ) - if assessment["complete"] and not cross_repository_lifecycle.lifecycle_authority_is_current( - client, - organization, - repository, - issue, - assessment, - ): - assessment = cross_repository_lifecycle.pending_reference_change(assessment) - client.upsert_lifecycle_comment( - organization, - repository, - number, - cross_repository_lifecycle.EVIDENCE_MARKER, - cross_repository_lifecycle.render_evidence(assessment), - ) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - target_completion_is_pending = not assessment["complete"] - elif target_contract_is_missing: - target_completion_is_pending = True - - must_remain_open = completion_is_pending or target_completion_is_pending - if state == "closed" and must_remain_open: - client.update_issue_state( - organization, - repository, - number, - "open", - state_reason="reopened", - ) - issue["state"] = "open" - issue["state_reason"] = "reopened" - replacement -= STATUS_LABELS - previous_open_statuses = statuses & OPEN_STATUS_LABELS - replacement.update(previous_open_statuses if len(previous_open_statuses) == 1 else {"status:triage"}) - if replacement != labels: - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - statuses = labels & STATUS_LABELS - state = "open" - reason = ( - "closed without a valid declared target set" - if target_contract_is_missing - else "closed before every declared target landing and repository qualification completed" - if target_completion_is_pending - else "closed before its required public completion evidence was verified" - ) - if frozen_result is None: - failures.append(f"{location} {reason}") - target_contract_failure_reported = target_contract_is_missing - elif state == "open" and ( - (declared_targets and not must_remain_open) - or (COMPLETION_VERIFIED_LABEL in labels and not must_remain_open) - ): - client.update_issue_state( - organization, - repository, - number, - "closed", - state_reason="completed", - ) - issue["state"] = "closed" - issue["state_reason"] = "completed" - state = "closed" - aggregated_close = True - if target_contract_is_missing and not target_contract_failure_reported: - failures.append(f"{location} has cross-repository authority without a valid declared target set") - - if state == "closed" and statuses != {"status:done"}: - replacement -= STATUS_LABELS - replacement.add("status:done") - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - if not aggregated_close and frozen_result is None: - failures.append(f"{location} closed state overrode stale lifecycle labels {sorted(statuses)}") - elif state == "open" and "status:done" in statuses: - replacement.remove("status:done") - if not replacement & OPEN_STATUS_LABELS: - replacement.add("status:triage") - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - if frozen_result is None: - failures.append(f"{location} open state overrode stale status:done") - elif state == "open" and len(statuses & OPEN_STATUS_LABELS) != 1: - replacement.add("authority:conflict") - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - failures.append(f"{location} has ambiguous open lifecycle labels {sorted(statuses)}") - - if ( - assessment is not None - and assessment["complete"] - and not cross_repository_lifecycle.lifecycle_authority_is_current( - client, - organization, - repository, - issue, - assessment, - ) - ): - assessment = cross_repository_lifecycle.pending_reference_change(assessment) - client.upsert_lifecycle_comment( - organization, - repository, - number, - cross_repository_lifecycle.EVIDENCE_MARKER, - cross_repository_lifecycle.render_evidence(assessment), - ) - if state == "closed": - client.update_issue_state( - organization, - repository, - number, - "open", - state_reason="reopened", - ) - issue["state"] = "open" - issue["state_reason"] = "reopened" - state = "open" - replacement = set(labels) - STATUS_LABELS - replacement.update(open_statuses_before_lifecycle or {"status:triage"}) - if replacement != labels: - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - issue["labels"] = [{"name": label} for label in sorted(replacement)] - labels = replacement - failures.append(f"{location} closing-reference authority changed during lifecycle mutation") - - if state == "closed" and "state_reason" in issue and issue.get("state_reason") != "completed": - client.update_issue_state( - organization, - repository, - number, - "closed", - state_reason="completed", - ) - issue["state_reason"] = "completed" - - if len(_kind_label_names(labels)) != 1: - failures.append(f"{location} must have exactly one kind label") - if len(labels & PRIORITY_LABELS) != 1: - failures.append(f"{location} must have exactly one priority label") - return failures - - -def _audit_migrated_classification( - backlog: dict[str, Any], - resolved: Mapping[str, tuple[str, dict[str, Any]]], -) -> list[str]: - failures: list[str] = [] - for item in backlog["items"]: - match = resolved.get(item["id"]) - if match is None: - continue - repository, issue = match - labels = _label_names(issue) - required = {"authority:github", OWNER_LABELS[repository]} - if not required <= labels: - failures.append(f"{repository}#{issue['number']} is missing authoritative ownership labels") - if len(labels & CLASSIFICATION_LABELS) != 1: - failures.append(f"{repository}#{issue['number']} must have exactly one beta classification") - milestone = issue.get("milestone") - if not isinstance(milestone, dict) or milestone.get("title") != backlog["milestone"]: - failures.append(f"{repository}#{issue['number']} is not assigned to {backlog['milestone']!r}") - return failures - - -def _public_blocker_condition(issue: Mapping[str, Any] | None) -> str: - if issue is None or not isinstance(issue.get("body"), str): - return "See the public issue for the condition that must change before work can resume." - body = str(issue["body"]) - if body.count(UNBLOCK_CONTEXT_START) != 1 or body.count(UNBLOCK_CONTEXT_END) != 1: - return "See the public issue for the condition that must change before work can resume." - start = body.index(UNBLOCK_CONTEXT_START) + len(UNBLOCK_CONTEXT_START) - end = body.index(UNBLOCK_CONTEXT_END) - if start >= end: - return "See the public issue for the condition that must change before work can resume." - condition = body[start:end].strip() - condition = re.sub(r"^##[ \t]+Unblock condition[ \t]*\r?\n+", "", condition).strip() - if not condition: - return "See the public issue for the condition that must change before work can resume." - try: - _public_safe([condition]) - except AuthorityError: - return "See the public issue for the condition that must change before work can resume." - return condition - - -def _public_lifecycle_state( - metadata: Mapping[str, Any], - labels: Collection[str], -) -> str: - if SUPERSEDED_STATUS_LABEL in labels: - return "superseded" - if metadata.get("state") == "closed" or "status:done" in labels: - return "completed" - if metadata.get("approved") is not True: - return "awaiting-maintainer-vetting" - if "status:blocked" in labels: - return "blocked" - if "status:in-progress" in labels: - return "in-progress" - return "approved-queued" - - -def _render_public_lifecycle_comment( - state: str, - condition: str | None = None, - *, - condition_key: str | None = None, -) -> str: - headings = { - "approved-queued": "Approved and queued", - "awaiting-maintainer-vetting": "Awaiting maintainer vetting", - "blocked": "Blocked", - "completed": "Completed", - "in-progress": "In progress", - "superseded": "Superseded", - } - explanations = { - "approved-queued": "A maintainer approved the current issue revision and it is queued for product work.", - "awaiting-maintainer-vetting": "A maintainer has not yet approved the current issue revision.", - "blocked": "Work cannot advance until the public condition below changes.", - "completed": "The approved work and its required public completion evidence are complete.", - "in-progress": "Implementation is actively in progress.", - "superseded": "A reviewed successor or disposition replaced this work.", - } - if state not in headings: - raise AuthorityError(f"unsupported public lifecycle state {state!r}") - condition_heading = "Public unblock condition" if state == "blocked" else "Public condition" - condition_section = f"\n\n**{condition_heading}**\n\n{condition}" if condition else "" - rendered_condition_key = condition_key or "none" - return ( - f"{PUBLIC_LIFECYCLE_MARKER}\n" - f"\n" - "### Public lifecycle\n\n" - f"**State:** {headings[state]}\n\n" - f"{explanations[state]}" - f"{condition_section}\n" - ) - - -def reconcile_public_lifecycle( - policy: Mapping[str, Any], - client: Any, - public_metadata: Sequence[dict[str, Any]], - inventory: Mapping[str, Sequence[dict[str, Any]]], - *, - lifecycle_projection: Mapping[tuple[str, int], Mapping[str, Any]] | None = None, - now: datetime | None = None, - projection_failures: Sequence[str] = (), -) -> list[str]: - """Converge labels/comments per issue while quarantining malformed identities.""" - - organization = str(policy["organization"]) - lifecycle_policy = policy["lifecycle"] - state_labels = lifecycle_policy["state_labels"] - transition_at = (now or datetime.now(UTC)).astimezone(UTC).isoformat().replace("+00:00", "Z") - projection = lifecycle_projection or {} - approved_issues = { - (repository, int(issue["number"])): issue - for repository, issues in inventory.items() - for issue in issues - if type(issue.get("number")) is int - } - failures: list[str] = list(projection_failures) - seen_projection_identities: set[tuple[str, int]] = set() - for metadata in public_metadata: - if metadata.get("type") != "issue": - continue - repository = metadata.get("repository") - number = metadata.get("number") - if not isinstance(repository, str) or type(number) is not int: - failures.append("public issue metadata has an invalid identity") - continue - location = f"{repository}#{number}" - if metadata.get("quarantined") is True: - failures.append(f"{location} has quarantined approved intake") - continue - issue = approved_issues.get((repository, number)) - if issue is not None: - metadata["approved"] = True - metadata["labels"] = sorted(_label_names(issue)) - metadata["state"] = issue.get("state") - metadata["closed_at"] = issue.get("closed_at") - labels = set(metadata.get("labels", ())) - identity = (repository, number) - projected = projection.get(identity) - if projected is not None: - seen_projection_identities.add(identity) - if metadata.get("approved") is not True: - failures.append(f"{location} projection does not match approved public intake") - metadata["reconciliation_failure"] = "projection-not-approved" - continue - - try: - trusted_retirement = _has_trusted_public_retirement( - client, - organization, - repository, - number, - ) - except AuthorityError as error: - failures.append(f"{location} retirement record is malformed: {error}") - metadata["reconciliation_failure"] = "retirement-record" - continue - if trusted_retirement: - kinds = _kind_label_names(labels) - if len(kinds) != 1 or not kinds <= KIND_LABELS: - failures.append(f"{location} has malformed kind labels {sorted(kinds)}") - metadata["reconciliation_failure"] = "malformed-kind-labels" - continue - if projected is not None and projected.get("state") != "superseded": - failures.append(f"{location} projection conflicts with trusted retirement") - metadata["reconciliation_failure"] = "projection-conflicts-with-retirement" - replacement = labels - STATUS_LABELS - COMPLETION_LABELS - replacement.discard("authority:conflict") - replacement.update({"authority:github", SUPERSEDED_STATUS_LABEL}) - if replacement != labels: - client.replace_issue_labels( - organization, - repository, - number, - sorted(replacement), - ) - labels = replacement - metadata["labels"] = sorted(labels) - if issue is not None: - issue["labels"] = [{"name": label} for label in sorted(labels)] - if metadata.get("state") != "closed" or (issue is not None and issue.get("state_reason") != "not_planned"): - client.update_issue_state( - organization, - repository, - number, - "closed", - state_reason="not_planned", - ) - metadata["state"] = "closed" - metadata["closed_at"] = transition_at - if issue is not None: - issue["state"] = "closed" - issue["state_reason"] = "not_planned" - metadata["public_state"] = "superseded" - continue - - if metadata.get("state") == "closed": - metadata["public_state"] = _public_lifecycle_state(metadata, labels) - if metadata.get("specialized_lifecycle") is True or projected is None: - continue - projected_state = projected.get("state") - if projected_state not in {"completed", "superseded"}: - continue - if not client.has_managed_lifecycle_comment( - organization, - repository, - number, - PUBLIC_LIFECYCLE_MARKER, - ): - continue - public_state = "completed" if projected_state == "completed" else "superseded" - metadata["public_state"] = public_state - try: - client.upsert_lifecycle_comment( - organization, - repository, - number, - PUBLIC_LIFECYCLE_MARKER, - _render_public_lifecycle_comment(public_state), - ) - except AuthorityError as error: - failures.append(f"{location} lifecycle comment reconciliation failed: {error}") - metadata["reconciliation_failure"] = "lifecycle-comment" - continue - - kinds = _kind_label_names(labels) - if len(kinds) != 1 or not kinds <= KIND_LABELS: - failures.append(f"{location} has malformed kind labels {sorted(kinds)}") - metadata["reconciliation_failure"] = "malformed-kind-labels" - continue - statuses = labels & STATUS_LABELS - if len(statuses) > 1: - failures.append(f"{location} has ambiguous lifecycle labels {sorted(statuses)}") - metadata["reconciliation_failure"] = "ambiguous-lifecycle-labels" - continue - - projected_state = projected.get("state") if projected is not None else None - completion_target_pending = ( - COMPLETION_REQUIRED_LABEL in labels and COMPLETION_VERIFIED_LABEL not in labels - ) - if projected_state == "completed" and completion_target_pending: - failures.append(f"{location} verified release completion is waiting on required target evidence") - metadata["reconciliation_failure"] = "required-target-evidence" - projected_state = "integrated" - desired_state = None - if projected_state == "completed" or ( - projected is None - and metadata.get("approved") is True - and COMPLETION_VERIFIED_LABEL in labels - ): - desired_state = "completed" - elif projected_state == "superseded": - desired_state = "superseded" - - effective_transition_at = str(projected.get("transition_at")) if projected is not None else transition_at - if desired_state in {"completed", "superseded"}: - state_reason = "completed" if desired_state == "completed" else "not_planned" - client.update_issue_state( - organization, - repository, - number, - "closed", - state_reason=state_reason, - ) - metadata["state"] = "closed" - metadata["closed_at"] = transition_at - metadata["last_transition_at"] = effective_transition_at - - if desired_state == "completed": - desired_status = state_labels["completed"] - elif desired_state == "superseded": - desired_status = SUPERSEDED_STATUS_LABEL - elif metadata.get("approved") is not True: - desired_status = state_labels["awaiting-maintainer-vetting"] - elif projected_state in {"blocked", "failed"} or ( - projected is None and "status:blocked" in statuses - ): - desired_status = state_labels["blocked"] - elif projected_state in {"built", "claimed", "integrated", "integrating"} or ( - projected is None and "status:in-progress" in statuses - ): - desired_status = state_labels["in-progress"] - else: - desired_status = state_labels["approved-queued"] - - replacement = labels - STATUS_LABELS | {desired_status} - if projected is not None: - # A unique authenticated projection, a non-duplicated managed - # lifecycle record, and unambiguous kind/status labels prove that - # an older authority conflict has been resolved. - replacement.discard("authority:conflict") - if desired_state == "completed": - replacement -= COMPLETION_LABELS - replacement.add(COMPLETION_VERIFIED_LABEL) - elif desired_state == "superseded": - replacement -= COMPLETION_LABELS - status_changed = statuses != {desired_status} - if metadata.get("approved") is True: - replacement.add("authority:github") - if replacement != labels: - client.replace_issue_labels(organization, repository, number, sorted(replacement)) - labels = replacement - metadata["labels"] = sorted(labels) - if status_changed: - metadata["last_transition_at"] = effective_transition_at - metadata.setdefault("label_transition_at", {})[desired_status] = effective_transition_at - if issue is not None: - issue["labels"] = [{"name": label} for label in sorted(labels)] - - public_state = _public_lifecycle_state(metadata, labels) - metadata["public_state"] = public_state - if metadata.get("specialized_lifecycle") is True: - continue - if projected is not None and projected.get("public_condition") is not None: - public_condition = str(projected["public_condition"]) - condition = PUBLIC_CONDITIONS[public_condition] - condition_key = f"projection:{public_condition}" - else: - condition = _public_blocker_condition(issue) if public_state == "blocked" else None - condition_key = ( - f"public-text:{hashlib.sha256(condition.encode('utf-8')).hexdigest()[:16]}" - if condition is not None - else None - ) - try: - existing_comment = client.managed_lifecycle_comment_body( - organization, - repository, - number, - PUBLIC_LIFECYCLE_MARKER, - ) - if ( - projected is None - and existing_comment is not None - and f"lifecycle-state:{public_state};condition:projection:" in existing_comment - ): - continue - client.upsert_lifecycle_comment( - organization, - repository, - number, - PUBLIC_LIFECYCLE_MARKER, - _render_public_lifecycle_comment( - public_state, - condition, - condition_key=condition_key, - ), - ) - except AuthorityError as error: - failures.append(f"{location} lifecycle comment reconciliation failed: {error}") - metadata["reconciliation_failure"] = "lifecycle-comment" - for repository, number in sorted(set(projection) - seen_projection_identities): - failures.append(f"{repository}#{number} projection does not match admitted public issue metadata") - return failures - - -def _verified_release_completions( - projection: Mapping[tuple[str, int], Mapping[str, Any]], -) -> dict[tuple[str, int], Mapping[str, Any]]: - """Select only source-bound release completions for the pre-intake writer.""" - - return { - identity: record - for identity, record in projection.items() - if ( - record.get("state") == "completed" - and record.get("completion_evidence") == "verified" - and isinstance(record.get("implementation_source"), str) - and isinstance(record.get("verified_release"), Mapping) - ) - } - - -def reconcile_verified_releases_before_intake( - policy: Mapping[str, Any], - client: Any, - public_metadata: Sequence[dict[str, Any]], - lifecycle_projection: Mapping[tuple[str, int], Mapping[str, Any]], - *, - now: datetime | None = None, - projection_failures: Sequence[str] = (), -) -> dict[str, Any]: - """Close verified release completions before intake can bind or reserve them.""" - - release_completions = _verified_release_completions(lifecycle_projection) - selected_metadata = [ - metadata - for metadata in public_metadata - if ( - metadata.get("type") == "issue" - and (metadata.get("repository"), metadata.get("number")) in release_completions - ) - ] - inventory: dict[str, list[dict[str, Any]]] = { - str(repository): [] for repository in policy["repositories"] - } - for metadata in selected_metadata: - if metadata.get("approved") is not True: - continue - repository = str(metadata["repository"]) - inventory[repository].append( - { - "closed_at": metadata.get("closed_at"), - "labels": [{"name": label} for label in metadata.get("labels", [])], - "number": int(metadata["number"]), - "state": metadata.get("state"), - "state_reason": None, - } - ) - failures = reconcile_public_lifecycle( - policy, - client, - selected_metadata, - inventory, - lifecycle_projection=release_completions, - now=now, - projection_failures=projection_failures, - ) - terminal_identities = sorted( - f"{metadata['repository']}#{metadata['number']}" - for metadata in selected_metadata - if ( - metadata.get("state") == "closed" - and set(metadata.get("labels", ())) & STATUS_LABELS == {"status:done"} - and COMPLETION_VERIFIED_LABEL in set(metadata.get("labels", ())) - ) - ) - return { - "failures": list(failures), - "mode": "pre-intake-release-completion", - "outcome": "fail" if failures else "pass", - "release_completion_count": len(release_completions), - "schema": "durable-workflow.github-issue-authority-evidence/v1", - "terminal_identities": terminal_identities, - } - - -def build_public_age_audit( - policy: Mapping[str, Any], - public_metadata: Sequence[Mapping[str, Any]], - reconciliation_failures: Sequence[str], - *, - now: datetime | None = None, -) -> dict[str, Any]: - """Build a metadata-only, public-safe age artifact with issues and PRs separate.""" - - audited_at = (now or datetime.now(UTC)).astimezone(UTC) - lifecycle_policy = policy["lifecycle"] - approved_state_seconds = int(lifecycle_policy["approved_state_seconds"]) - audit_interval_seconds = int(lifecycle_policy["audit_interval_seconds"]) - closure_seconds = int(lifecycle_policy["completed_issue_closure_seconds"]) - creation_stale_seconds = int(lifecycle_policy["creation_stale_age_seconds"]) - maximum_open_actionable = int(lifecycle_policy["max_open_actionable_per_repository"]) - priority_escalation_seconds = int(lifecycle_policy["priority_escalation_seconds"]) - product_owner_alert_seconds = int(lifecycle_policy["product_owner_alert_seconds"]) - triage_seconds = int(lifecycle_policy["maintainer_vetting_seconds"]) - stale_transition_seconds = int(lifecycle_policy["stale_approved_transition_seconds"]) - unattended_seconds = int(lifecycle_policy["unattended_placeholder_seconds"]) - repositories: dict[str, dict[str, Any]] = {} - age_bucket_template = {"under-24h": 0, "24h-to-72h": 0, "72h-to-7d": 0, "over-7d": 0} - - def empty_bucket() -> dict[str, Any]: - return { - "age_buckets": dict(age_bucket_template), - "actionable_open_count": 0, - "closed_count": 0, - "counts_by_state": {}, - "creation_suppressed": False, - "oldest_age_seconds": 0, - "oldest_open_created_age_seconds": 0, - "open_count": 0, - "over_budget": False, - "stale_identities": [], - "total_count": 0, - } - - totals = { - "issues": empty_bucket(), - "pull_requests": empty_bucket(), - } - for repository in policy["repositories"]: - repositories[str(repository)] = { - "issues": empty_bucket(), - "pull_requests": empty_bucket(), - } - - metadata_failures: list[str] = [] - actionable_records: dict[str, list[dict[str, Any]]] = { - str(repository): [] for repository in policy["repositories"] - } - open_issue_created_ages: dict[str, list[int]] = { - str(repository): [] for repository in policy["repositories"] - } - external_intake: list[dict[str, Any]] = [] - notification_crossings: list[dict[str, Any]] = [] - for record in public_metadata: - repository = record.get("repository") - number = record.get("number") - record_type = record.get("type") - if repository not in repositories or type(number) is not int or record_type not in {"issue", "pull_request"}: - metadata_failures.append("public age audit encountered an invalid metadata identity") - continue - bucket_name = "issues" if record_type == "issue" else "pull_requests" - bucket = repositories[str(repository)][bucket_name] - total_bucket = totals[bucket_name] - if record_type == "issue": - labels = set(record.get("labels", ())) - state = str(record.get("public_state") or _public_lifecycle_state(record, labels)) - transition_value = record.get("last_transition_at") or record.get("created_at") - else: - state = "merged" if record.get("merged_at") else str(record.get("state", "unknown")) - transition_value = ( - record.get("merged_at") - or record.get("closed_at") - or record.get("updated_at") - or record.get("created_at") - ) - try: - transition = _parse_timestamp(transition_value, f"{record_type} age timestamp").astimezone(UTC) - except AuthorityError as error: - metadata_failures.append(f"{repository}#{number} has invalid age metadata: {error}") - continue - age_seconds = max(0, int((audited_at - transition).total_seconds())) - created_age_seconds = age_seconds - if record_type == "issue": - try: - created_at = _parse_timestamp(record.get("created_at"), "issue creation timestamp").astimezone(UTC) - created_age_seconds = max(0, int((audited_at - created_at).total_seconds())) - except AuthorityError: - created_age_seconds = age_seconds - age_bucket = ( - "under-24h" - if age_seconds < 24 * 3600 - else "24h-to-72h" - if age_seconds < 72 * 3600 - else "72h-to-7d" - if age_seconds < 7 * 24 * 3600 - else "over-7d" - ) - for current in (bucket, total_bucket): - counts = current["counts_by_state"] - counts[state] = counts.get(state, 0) + 1 - current["oldest_age_seconds"] = max(current["oldest_age_seconds"], age_seconds) - current["age_buckets"][age_bucket] += 1 - current["total_count"] += 1 - if record.get("state") == "open": - current["open_count"] += 1 - current["oldest_open_created_age_seconds"] = max( - current["oldest_open_created_age_seconds"], - created_age_seconds, - ) - else: - current["closed_count"] += 1 - - stale: list[dict[str, Any]] = [] - identity = f"{repository}#{number}" - if record_type == "issue": - labels = set(record.get("labels", ())) - if record.get("state") == "open": - open_issue_created_ages[str(repository)].append(created_age_seconds) - if record.get("state") == "open" and record.get("approved") is True: - priority = next((label for label in PRIORITY_LABELS if label in labels), "priority:untriaged") - actionable_record = { - "age_seconds": created_age_seconds, - "identity": identity, - "kind": next((label for label in KIND_LABELS if label in labels), None), - "priority": priority, - "repository": str(repository), - "state": state, - } - actionable_records[str(repository)].append(actionable_record) - bucket["actionable_open_count"] += 1 - total_bucket["actionable_open_count"] += 1 - elif record.get("state") == "open" and record.get("approved") is not True: - external_intake.append( - { - "age_seconds": created_age_seconds, - "identity": identity, - "kind": next((label for label in KIND_LABELS if label in labels), None), - "repository": str(repository), - } - ) - if ( - record.get("state") == "open" - and record.get("approved") is True - and age_seconds >= stale_transition_seconds - ): - stale.append( - { - "age_seconds": age_seconds, - "identity": identity, - "target": ( - "stale-blocker-72h" - if state == "blocked" - else "approved-transition-72h" - ), - } - ) - if ( - record.get("state") == "open" - and record.get("approved") is True - and created_age_seconds >= product_owner_alert_seconds - ): - stale.append( - { - "age_seconds": created_age_seconds, - "identity": identity, - "target": "product-owner-alert-7d", - } - ) - if created_age_seconds < product_owner_alert_seconds + audit_interval_seconds: - notification_crossings.append( - {"identity": identity, "target": "product-owner-alert-7d"} - ) - if ( - record.get("state") == "open" - and record.get("approved") is True - and created_age_seconds >= unattended_seconds - ): - unattended_target = "blocker-review-14d" if state == "blocked" else "unattended-placeholder-14d" - stale.append( - { - "age_seconds": created_age_seconds, - "identity": identity, - "target": unattended_target, - } - ) - if created_age_seconds < unattended_seconds + audit_interval_seconds: - notification_crossings.append({"identity": identity, "target": unattended_target}) - if ( - record.get("state") == "open" - and record.get("approved") is True - and ( - len(labels & STATUS_LABELS) != 1 - or record.get("reconciliation_failure") is not None - ) - and age_seconds >= approved_state_seconds - ): - stale.append({"age_seconds": age_seconds, "identity": identity, "target": "approved-state-24h"}) - if ( - record.get("state") == "open" - and record.get("approved") is not True - and "status:triage" not in labels - and age_seconds >= triage_seconds - ): - stale.append({"age_seconds": age_seconds, "identity": identity, "target": "triage-visibility-24h"}) - if record.get("state") == "open" and COMPLETION_VERIFIED_LABEL in labels: - verified_at = record.get("label_transition_at", {}).get(COMPLETION_VERIFIED_LABEL) - try: - verified_timestamp = _parse_timestamp(verified_at, "completion verification timestamp") - verified_age = int( - (audited_at - verified_timestamp).total_seconds() - ) - except AuthorityError: - verified_age = age_seconds - if verified_age >= closure_seconds: - stale.append( - {"age_seconds": verified_age, "identity": identity, "target": "verified-closure-15m"} - ) - bucket["stale_identities"].extend(stale) - total_bucket["stale_identities"].extend(stale) - - priority_order = ["priority:P0", "priority:P1", "priority:P2", "priority:P3", "priority:untriaged"] - claim_order: list[dict[str, Any]] = [] - over_budget_repositories: list[str] = [] - for repository, records in actionable_records.items(): - issue_bucket = repositories[repository]["issues"] - oldest_created_age = max(open_issue_created_ages[repository], default=0) - creation_suppressed = ( - len(records) >= maximum_open_actionable or oldest_created_age >= creation_stale_seconds - ) - over_budget = len(records) > maximum_open_actionable or oldest_created_age >= creation_stale_seconds - issue_bucket["creation_suppressed"] = creation_suppressed - issue_bucket["over_budget"] = over_budget - if over_budget: - over_budget_repositories.append(repository) - if ( - len(records) == maximum_open_actionable - and records - and min(int(record["age_seconds"]) for record in records) < audit_interval_seconds - ): - notification_crossings.append( - {"identity": repository, "target": "open-actionable-budget"} - ) - for record in records: - if record["state"] != "approved-queued": - continue - priority_index = priority_order.index(str(record["priority"])) - escalated = int(record["age_seconds"]) >= priority_escalation_seconds - effective_index = max(0, priority_index - 1) if escalated else priority_index - claim_order.append( - { - "age_escalated": escalated, - "age_seconds": int(record["age_seconds"]), - "effective_priority": priority_order[effective_index], - "identity": record["identity"], - "priority": record["priority"], - } - ) - claim_order.sort( - key=lambda record: ( - priority_order.index(str(record["effective_priority"])), - -int(record["age_seconds"]), - str(record["identity"]), - ) - ) - totals["issues"]["creation_suppressed"] = any( - repositories[repository]["issues"]["creation_suppressed"] for repository in repositories - ) - totals["issues"]["over_budget"] = bool(over_budget_repositories) - - deduplication_candidates: list[dict[str, Any]] = [] - for intake in external_intake: - roots = [ - record - for record in actionable_records[intake["repository"]] - if intake["kind"] is not None and record["kind"] == intake["kind"] - ] - deduplication_candidates.append( - { - "identity": intake["identity"], - "root": max(roots, key=lambda record: int(record["age_seconds"]))["identity"] if roots else None, - "triage_exempt_from_creation_budget": True, - } - ) - - notification_crossings = sorted( - {json.dumps(record, sort_keys=True) for record in notification_crossings} - ) - notification_records = [json.loads(record) for record in notification_crossings] - notification_digest = hashlib.sha256( - json.dumps(notification_records, separators=(",", ":"), sort_keys=True).encode("utf-8") - ).hexdigest() - pipeline_health = { - "claim_order": claim_order, - "deduplication_candidates": deduplication_candidates, - "dm_notification": { - "dedupe_key": f"public-issue-lifecycle:{notification_digest}", - "required": bool(notification_records), - "threshold_crossings": notification_records, - }, - "open_count_by_repository": { - repository: repositories[repository]["issues"]["open_count"] - for repository in repositories - }, - "oldest_open_created_age_seconds": totals["issues"]["oldest_open_created_age_seconds"], - "over_budget_repositories": sorted(over_budget_repositories), - "schema": "durable-workflow.public-issue-lifecycle-health/v1", - "status_distribution": dict(totals["issues"]["counts_by_state"]), - } - - failures = [*reconciliation_failures, *metadata_failures] - stale_count = sum(len(totals[bucket]["stale_identities"]) for bucket in ("issues", "pull_requests")) - return { - "schema": "durable-workflow.public-issue-age-audit/v1", - "audited_at": audited_at.isoformat().replace("+00:00", "Z"), - "outcome": "fail" if failures else "attention-required" if stale_count else "pass", - "operational_targets_seconds": { - "audit_interval": audit_interval_seconds, - "approved_state": approved_state_seconds, - "completed_issue_closure": closure_seconds, - "creation_stale_age": creation_stale_seconds, - "maintainer_vetting": triage_seconds, - "priority_escalation": priority_escalation_seconds, - "product_owner_alert": product_owner_alert_seconds, - "stale_approved_transition": stale_transition_seconds, - "unattended_placeholder": unattended_seconds, - }, - "pipeline_health": pipeline_health, - "repositories": repositories, - "reconciliation_failures": list(failures), - "stale_identity_count": stale_count, - "summary": totals, - } - - -def _issue_sweep_evidence( - policy: Mapping[str, Any], - before: Mapping[str, Any], - after: Mapping[str, Any], - reconciliation_failures: Sequence[str], -) -> dict[str, Any]: - return { - "schema": "durable-workflow.public-issue-lifecycle-sweep/v1", - "before": dict(before["summary"]["issues"]), - "after": dict(after["summary"]["issues"]), - "repositories": { - repository: { - "before": dict(before["repositories"][repository]["issues"]), - "after": dict(after["repositories"][repository]["issues"]), - } - for repository in policy["repositories"] - }, - "reconciliation_failures": list(reconciliation_failures), - } - - -def _issue_created_at(issue: Mapping[str, Any]) -> datetime | None: - value = issue.get("created_at") or issue.get("createdAt") - if value is None: - return None - try: - return _parse_timestamp(value, "public issue creation timestamp").astimezone(UTC) - except AuthorityError: - return None - - -def _is_open_actionable_issue(issue: Mapping[str, Any]) -> bool: - labels = _label_names(dict(issue)) - return ( - issue.get("state") == "open" - and "pull_request" not in issue - and "authority:github" in labels - and not labels & {"status:done", SUPERSEDED_STATUS_LABEL} - ) - - -def _creation_budget( - policy: Mapping[str, Any], - issues: Sequence[dict[str, Any]], - item: Mapping[str, Any], - *, - now: datetime, - public_metadata: Sequence[Mapping[str, Any]] | None = None, -) -> dict[str, Any]: - lifecycle = policy["lifecycle"] - maximum = int(lifecycle["max_open_actionable_per_repository"]) - stale_seconds = int(lifecycle["creation_stale_age_seconds"]) - open_issues = [ - issue - for issue in issues - if issue.get("state") == "open" and "pull_request" not in issue - ] - actionable = [issue for issue in open_issues if _is_open_actionable_issue(issue)] - - def oldest_key(issue: Mapping[str, Any]) -> tuple[datetime, int]: - return ( - _issue_created_at(issue) or datetime.max.replace(tzinfo=UTC), - int(issue.get("number")) if type(issue.get("number")) is int else 2**63 - 1, - ) - - creation_times = [ - created_at - for issue in open_issues - if (created_at := _issue_created_at(issue)) is not None - ] - for record in public_metadata or (): - if ( - record.get("repository") != item["repository"] - or record.get("type") != "issue" - or record.get("state") != "open" - ): - continue - try: - creation_times.append( - _parse_timestamp(record.get("created_at"), "public issue creation timestamp").astimezone(UTC) - ) - except AuthorityError: - continue - oldest_created_at = min(creation_times) if creation_times else None - oldest_age_seconds = max(0, int((now - oldest_created_at).total_seconds())) if oldest_created_at else 0 - reasons: list[str] = [] - if len(actionable) >= maximum: - reasons.append("open-actionable-budget") - if oldest_age_seconds >= stale_seconds: - reasons.append("open-issue-older-than-7d") - - required_labels = set(_item_labels(dict(item))) - required_kind = next(label for label in required_labels if label.startswith("kind:")) - required_classification = next(label for label in required_labels if label in CLASSIFICATION_LABELS) - applicable = [ - issue - for issue in actionable - if {required_kind, required_classification} <= _label_names(issue) - and isinstance(issue.get("body"), str) - and type(issue.get("number")) is int - ] - root = min(applicable, key=oldest_key) if applicable else None - return { - "blocked": bool(reasons), - "oldest_age_seconds": oldest_age_seconds, - "open_actionable_count": len(actionable), - "reasons": reasons, - "root": root, - } - - -def _consolidate_backlog_finding(item: Mapping[str, Any], root: Mapping[str, Any]) -> str | None: - body = root.get("body") - if not isinstance(body, str): - raise AuthorityError(f"consolidation root for {item['id']} has no public body") - marker = f"" - if marker in body: - return None - finding = ( - f"## Consolidated finding: {item['title']}\n\n" - f"{str(item['body']).strip()}\n\n" - f"{marker}\n" - ) - return f"{body.rstrip()}\n\n{finding}" - - -def apply_backlog( - policy: dict[str, Any], - backlog: dict[str, Any], - client: Any, - *, - inventory: dict[str, list[dict[str, Any]]] | None = None, - approved_completion_holds: set[tuple[str, int]] | None = None, - cross_repository_targets: Mapping[tuple[str, int], Sequence[Mapping[str, Any]]] | None = None, - historical_cross_repository_completions: (Mapping[tuple[str, int], Sequence[Mapping[str, Any]]] | None) = None, - frozen_cross_repository_lifecycles: Mapping[tuple[str, int], Mapping[str, Any]] | None = None, - lifecycle_projection: Mapping[tuple[str, int], Mapping[str, Any]] | None = None, - lifecycle_projection_failures: Sequence[str] = (), - prerelease_supersessions: Mapping[tuple[str, int], Mapping[str, Any]] | None = None, - public_metadata: list[dict[str, Any]] | None = None, - audit_time: datetime | None = None, -) -> dict[str, Any]: - organization = policy["organization"] - inventory = inventory if inventory is not None else _inventory(policy, client) - effective_audit_time = audit_time or datetime.now(UTC) - before_age_audit = ( - build_public_age_audit(policy, public_metadata, [], now=effective_audit_time) - if public_metadata is not None - else None - ) - resolved = _preflight_markers(policy, backlog, client, inventory, allow_missing=True) - _preflight_unblock_context_layouts(backlog, inventory) - planned_body_updates = _plan_unblock_context_updates(backlog, resolved) - planned_ready_transitions = _plan_ready_transition_updates(backlog, resolved) - milestone_numbers, metadata_evidence = sync_metadata(policy, client) - dependency_urls = { - work_id: _issue_url(issue, organization, repository) for work_id, (repository, issue) in resolved.items() - } - dependency_titles = {item["id"]: item["title"] for item in backlog["items"]} - issue_evidence: dict[str, Any] = {} - - for item in backlog["items"]: - if item["id"] in resolved: - repository, issue = resolved[item["id"]] - updated_body = planned_body_updates[item["id"]] - updated_labels = planned_ready_transitions[item["id"]] - if updated_labels is not None: - client.replace_issue_labels( - organization, - repository, - int(issue["number"]), - updated_labels, - ) - issue["labels"] = [{"name": label} for label in updated_labels] - if updated_body is not None: - client.update_issue_body( - organization, - repository, - int(issue["number"]), - updated_body, - ) - issue["body"] = updated_body - issue_evidence[item["id"]] = { - "action": ( - "transitioned-to-ready" - if updated_labels is not None - else "updated-blocker-context" - if updated_body is not None - else "preserved" - ), - "state": issue.get("state"), - "url": _issue_url(issue, organization, repository), - } - continue - budget = _creation_budget( - policy, - inventory[item["repository"]], - item, - now=effective_audit_time, - public_metadata=public_metadata, - ) - if budget["blocked"]: - root = budget["root"] - if isinstance(root, Mapping): - updated_body = _consolidate_backlog_finding(item, root) - if updated_body is not None: - client.update_issue_body( - organization, - item["repository"], - int(root["number"]), - updated_body, - ) - root["body"] = updated_body - resolved[item["id"]] = (item["repository"], root) - dependency_urls[item["id"]] = _issue_url(root, organization, item["repository"]) - issue_evidence[item["id"]] = { - "action": "consolidated" if updated_body is not None else "preserved-consolidation", - "budget_reasons": list(budget["reasons"]), - "state": root.get("state"), - "url": dependency_urls[item["id"]], - } - else: - issue_evidence[item["id"]] = { - "action": "retained-private-audit", - "budget_reasons": list(budget["reasons"]), - "open_actionable_count": int(budget["open_actionable_count"]), - } - continue - unresolved_dependencies = [dependency for dependency in item["depends_on"] if dependency not in dependency_urls] - if unresolved_dependencies: - issue_evidence[item["id"]] = { - "action": "retained-private-audit", - "budget_reasons": ["dependency-was-not-publicly-routed"], - } - continue - dependency_urls_for_item = {dependency: dependency_urls[dependency] for dependency in item["depends_on"]} - issue = client.create_issue( - organization, - item["repository"], - title=item["title"], - body=_render_body(item, dependency_urls_for_item, dependency_titles), - labels=_item_labels(item), - milestone=milestone_numbers[(item["repository"], backlog["milestone"])], - ) - inventory[item["repository"]].append(issue) - dependency_urls[item["id"]] = _issue_url(issue, organization, item["repository"]) - resolved[item["id"]] = (item["repository"], issue) - issue_evidence[item["id"]] = { - "action": "created", - "state": issue.get("state"), - "url": dependency_urls[item["id"]], - } - - failures = _audit_state_labels( - policy, - client, - inventory, - approved_completion_holds or set(), - cross_repository_targets, - historical_cross_repository_completions, - frozen_cross_repository_lifecycles, - prerelease_supersessions, - ) - failures.extend(_audit_migrated_classification(backlog, resolved)) - if public_metadata is not None: - failures.extend( - reconcile_public_lifecycle( - policy, - client, - public_metadata, - inventory, - lifecycle_projection=lifecycle_projection, - now=effective_audit_time, - projection_failures=lifecycle_projection_failures, - ) - ) - evidence = { - "schema": "durable-workflow.github-issue-authority-evidence/v1", - "mode": "apply", - "outcome": "fail" if failures else "pass", - "metadata": metadata_evidence, - "issues": issue_evidence, - "prerelease_supersessions": _supersession_evidence( - policy["organization"], - prerelease_supersessions, - ), - } - if public_metadata is not None: - after_age_audit = build_public_age_audit( - policy, - public_metadata, - failures, - now=effective_audit_time, - ) - evidence["age_audit"] = after_age_audit - assert before_age_audit is not None - evidence["issue_sweep"] = _issue_sweep_evidence( - policy, - before_age_audit, - after_age_audit, - failures, - ) - if failures: - raise LifecycleAuditError( - "GitHub issue state drift was corrected or flagged: " + "; ".join(failures), - evidence, - ) - return evidence - - -def audit_backlog( - policy: dict[str, Any], - backlog: dict[str, Any], - client: Any, - *, - inventory: dict[str, list[dict[str, Any]]] | None = None, - approved_completion_holds: set[tuple[str, int]] | None = None, - cross_repository_targets: Mapping[tuple[str, int], Sequence[Mapping[str, Any]]] | None = None, - historical_cross_repository_completions: (Mapping[tuple[str, int], Sequence[Mapping[str, Any]]] | None) = None, - frozen_cross_repository_lifecycles: Mapping[tuple[str, int], Mapping[str, Any]] | None = None, - lifecycle_projection: Mapping[tuple[str, int], Mapping[str, Any]] | None = None, - lifecycle_projection_failures: Sequence[str] = (), - prerelease_supersessions: Mapping[tuple[str, int], Mapping[str, Any]] | None = None, - public_metadata: list[dict[str, Any]] | None = None, - audit_time: datetime | None = None, -) -> dict[str, Any]: - inventory = inventory if inventory is not None else _inventory(policy, client) - effective_audit_time = audit_time or datetime.now(UTC) - before_age_audit = ( - build_public_age_audit(policy, public_metadata, [], now=effective_audit_time) - if public_metadata is not None - else None - ) - resolved = _preflight_markers(policy, backlog, client, inventory, allow_missing=True) - budget_deferrals: dict[str, dict[str, Any]] = {} - missing_failures: list[str] = [] - for item in backlog["items"]: - if item["id"] in resolved: - continue - budget = _creation_budget( - policy, - inventory[item["repository"]], - item, - now=effective_audit_time, - public_metadata=public_metadata, - ) - if not budget["blocked"]: - missing_failures.append(f"{item['id']} has no GitHub issue") - continue - root = budget["root"] - budget_deferrals[item["id"]] = { - "action": "awaiting-consolidation" if isinstance(root, Mapping) else "retained-private-audit", - "budget_reasons": list(budget["reasons"]), - "open_actionable_count": int(budget["open_actionable_count"]), - **( - {"url": _issue_url(root, policy["organization"], item["repository"])} - if isinstance(root, Mapping) - else {} - ), - } - if missing_failures: - raise AuthorityError("issue authority marker audit failed: " + "; ".join(missing_failures)) - _preflight_unblock_context_layouts(backlog, inventory) - _plan_unblock_context_updates(backlog, resolved) - _milestones, metadata_evidence = sync_metadata(policy, client) - failures = _audit_state_labels( - policy, - client, - inventory, - approved_completion_holds or set(), - cross_repository_targets, - historical_cross_repository_completions, - frozen_cross_repository_lifecycles, - prerelease_supersessions, - ) - failures.extend(_audit_migrated_classification(backlog, resolved)) - if public_metadata is not None: - failures.extend( - reconcile_public_lifecycle( - policy, - client, - public_metadata, - inventory, - lifecycle_projection=lifecycle_projection, - now=effective_audit_time, - projection_failures=lifecycle_projection_failures, - ) - ) - organization = policy["organization"] - evidence = { - "schema": "durable-workflow.github-issue-authority-evidence/v1", - "mode": "audit", - "outcome": "fail" if failures else "pass", - "metadata": metadata_evidence, - "prerelease_supersessions": _supersession_evidence( - organization, - prerelease_supersessions, - ), - "issues": { - work_id: { - "state": issue.get("state"), - "url": _issue_url(issue, organization, repository), - } - for work_id, (repository, issue) in sorted(resolved.items()) - } - | budget_deferrals, - } - if public_metadata is not None: - after_age_audit = build_public_age_audit( - policy, - public_metadata, - failures, - now=effective_audit_time, - ) - evidence["age_audit"] = after_age_audit - assert before_age_audit is not None - evidence["issue_sweep"] = _issue_sweep_evidence( - policy, - before_age_audit, - after_age_audit, - failures, - ) - if failures: - raise LifecycleAuditError( - "GitHub issue state drift was corrected or flagged: " + "; ".join(failures), - evidence, - ) - return evidence - - -def _supersession_evidence( - organization: str, - supersessions: Mapping[tuple[str, int], Mapping[str, Any]] | None, -) -> list[dict[str, Any]]: - def successor_evidence(successor: Mapping[str, Any]) -> dict[str, Any]: - repository = str(successor["repository"]) - if "number" in successor: - number = int(successor["number"]) - return { - "number": number, - "repository": repository, - "revision": successor["revision"], - "url": f"https://github.com/{organization}/{repository}/issues/{number}", - } - commit = str(successor["commit"]) - path = str(successor["path"]) - return { - "commit": commit, - "path": path, - "release_plan": dict(successor["release_plan"]), - "repository": repository, - "sha256": successor["sha256"], - "train": successor["train"], - "url": f"https://github.com/{organization}/{repository}/blob/{commit}/{path}", - } - - return [ - { - "retired": { - "number": number, - "repository": repository, - "url": f"https://github.com/{organization}/{repository}/issues/{number}", - }, - "state": "superseded", - "activation": dict(successor["activation"]), - "successor": successor_evidence(successor), - } - for (repository, number), successor in sorted((supersessions or {}).items()) - ] - - -def _write_evidence(path: Path | None, evidence: dict[str, Any]) -> None: - if path is not None: - path.write_text(json.dumps(evidence, indent=2, sort_keys=True) + "\n", encoding="utf-8") - - -def _write_discovery_outputs(path: Path | None, manifest: dict[str, Any]) -> None: - if path is None: - return - trigger = manifest.get("trigger") - trigger_approved = not isinstance(trigger, Mapping) or trigger.get("approved") is True - with path.open("a", encoding="utf-8") as output: - output.write("intake_ready=true\n") - output.write(f"trigger_approved={'true' if trigger_approved else 'false'}\n") - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - for name in ( - "validate", - "discover", - "metadata-audit", - "complete-before-intake", - "activate", - "apply", - "audit", - ): - command = subparsers.add_parser(name) - command.add_argument("policy", type=Path) - command.add_argument("backlog", type=Path) - command.add_argument( - "--qualification-policy", - type=Path, - default=Path("qualification/policy.json"), - ) - command.add_argument( - "--legacy-cross-repository-targets", - type=Path, - default=Path("issue-authority/legacy-cross-repository-targets.json"), - ) - command.add_argument("--policy-schema", type=Path) - command.add_argument("--backlog-schema", type=Path) - if name == "discover": - command.add_argument("--output", type=Path, required=True) - command.add_argument("--github-output", type=Path) - command.add_argument("--lifecycle-projection", type=Path) - command.add_argument("--projection-actor") - command.add_argument("--trigger-repository") - command.add_argument("--trigger-number", type=int) - command.add_argument("--trigger-action") - command.add_argument("--trigger-actor") - command.add_argument("--trigger-label") - elif name == "metadata-audit": - command.add_argument("--evidence", type=Path, required=True) - elif name == "complete-before-intake": - command.add_argument("--evidence", type=Path, required=True) - command.add_argument("--lifecycle-projection", type=Path, required=True) - command.add_argument("--projection-actor", required=True) - elif name == "activate": - command.add_argument("--evidence", type=Path) - elif name in {"apply", "audit"}: - command.add_argument("--evidence", type=Path) - command.add_argument("--intake-manifest", type=Path, required=True) - command.add_argument("--lifecycle-projection", type=Path) - command.add_argument("--projection-actor") - return parser.parse_args(argv) - - -def main(argv: list[str] | None = None) -> int: - arguments = parse_args(argv or sys.argv[1:]) - evidence_path = getattr(arguments, "evidence", None) - try: - policy, backlog = load_contract( - arguments.policy, - arguments.backlog, - arguments.policy_schema, - arguments.backlog_schema, - ) - target_qualification = _load_json(arguments.qualification_policy, "target qualification policy") - try: - cross_repository_lifecycle.qualification_targets(target_qualification) - except cross_repository_lifecycle.LifecycleError as error: - raise AuthorityError(str(error)) from error - legacy_cross_repository_targets = load_legacy_cross_repository_targets( - arguments.legacy_cross_repository_targets, - target_qualification, - ) - validate_backlog_cross_repository_targets( - backlog, - target_qualification, - organization=policy["organization"], - ) - if arguments.command == "validate": - return 0 - discovery_token = os.environ.get("GITHUB_TOKEN") or "" - discovery = GitHubDiscovery( - discovery_token, - os.environ.get("GITHUB_GRAPHQL_URL", "https://api.github.com/graphql"), - api_url=os.environ.get("GITHUB_API_URL", "https://api.github.com"), - ) - if arguments.command == "metadata-audit": - public_metadata = discover_public_metadata(policy, discovery) - evidence = build_public_age_audit(policy, public_metadata, []) - _write_evidence(evidence_path, evidence) - notification = evidence["pipeline_health"]["dm_notification"] - if notification["required"]: - print( - f"::warning title=Public lifecycle age audit::" - f"Lifecycle thresholds crossed for {len(notification['threshold_crossings'])} " - "public identities; use the retained dedupe key for one product-owner notification.", - file=sys.stderr, - ) - return 0 - if arguments.command == "complete-before-intake": - lifecycle_projection, lifecycle_projection_failures = load_public_lifecycle_projection( - arguments.lifecycle_projection, - policy, - arguments.projection_actor, - ) - public_metadata = discover_public_issue_metadata(policy, discovery) - token = os.environ.get("BETA_PRODUCT_WORK_TOKEN") or "" - client = GitHubApi( - token, - os.environ.get("GITHUB_API_URL", "https://api.github.com"), - activation_token=discovery_token, - read_token=discovery_token, - graphql_url=os.environ.get("GITHUB_GRAPHQL_URL", "https://api.github.com/graphql"), - ) - evidence = reconcile_verified_releases_before_intake( - policy, - client, - public_metadata, - lifecycle_projection, - projection_failures=lifecycle_projection_failures, - ) - _write_evidence(evidence_path, evidence) - for failure in evidence["failures"]: - print(f"issue authority isolated {failure}", file=sys.stderr) - return 0 - if arguments.command == "discover": - has_repository = arguments.trigger_repository is not None - has_number = arguments.trigger_number is not None - if has_repository != has_number: - raise AuthorityError("trigger repository and issue number must be provided together") - lifecycle_projection: dict[tuple[str, int], dict[str, Any]] = {} - if arguments.lifecycle_projection is not None: - lifecycle_projection, lifecycle_projection_failures = load_public_lifecycle_projection( - arguments.lifecycle_projection, - policy, - arguments.projection_actor, - ) - for failure in lifecycle_projection_failures: - print(f"issue authority isolated {failure}", file=sys.stderr) - elif arguments.projection_actor is not None: - raise AuthorityError("public lifecycle projection actor was supplied without a projection") - manifest, _inventory = reconstruct_intake( - policy, - discovery, - pre_intake_release_completions=_verified_release_completions(lifecycle_projection), - target_qualification=target_qualification, - legacy_cross_repository_targets=legacy_cross_repository_targets, - trigger_repository=arguments.trigger_repository, - trigger_number=arguments.trigger_number, - trigger_action=arguments.trigger_action, - trigger_actor=arguments.trigger_actor, - trigger_label=arguments.trigger_label, - ) - for rejection in manifest["rejected_issues"]: - print( - f"issue authority isolated {rejection['repository']}#{rejection['number']}: " - f"{rejection['reason']}", - file=sys.stderr, - ) - _write_evidence(arguments.output, manifest) - _write_discovery_outputs(arguments.github_output, manifest) - return 0 - - if arguments.command == "activate": - client = GitHubApi( - discovery_token, - os.environ.get("GITHUB_API_URL", "https://api.github.com"), - activation_token=discovery_token, - read_token=discovery_token, - graphql_url=os.environ.get("GITHUB_GRAPHQL_URL", "https://api.github.com/graphql"), - ) - evidence = activate_prerelease_supersessions( - policy, - discovery, - client, - target_qualification=target_qualification, - legacy_cross_repository_targets=legacy_cross_repository_targets, - ) - _write_evidence(evidence_path, evidence) - return 0 - - manifest = _load_json(arguments.intake_manifest, "issue-intake manifest") - inventory = verify_intake_manifest( - policy, - manifest, - discovery, - target_qualification=target_qualification, - legacy_cross_repository_targets=legacy_cross_repository_targets, - ) - approved_completion_holds = _manifest_completion_holds(manifest) - cross_repository_targets = _manifest_cross_repository_targets(manifest) - historical_cross_repository_completions = _manifest_historical_cross_repository_completions(manifest) - frozen_cross_repository_lifecycles = _manifest_frozen_cross_repository_lifecycles(manifest) - prerelease_supersessions = _manifest_prerelease_supersessions(manifest) - public_metadata = _manifest_public_metadata(manifest) - lifecycle_projection: dict[tuple[str, int], dict[str, Any]] = {} - lifecycle_projection_failures: list[str] = [] - if arguments.lifecycle_projection is not None: - lifecycle_projection, lifecycle_projection_failures = load_public_lifecycle_projection( - arguments.lifecycle_projection, - policy, - arguments.projection_actor, - ) - elif arguments.projection_actor is not None: - raise AuthorityError("public lifecycle projection actor was supplied without a projection") - token = os.environ.get("BETA_PRODUCT_WORK_TOKEN") or "" - client = GitHubApi( - token, - os.environ.get("GITHUB_API_URL", "https://api.github.com"), - activation_token=discovery_token, - read_token=discovery_token, - graphql_url=os.environ.get("GITHUB_GRAPHQL_URL", "https://api.github.com/graphql"), - ) - if arguments.command == "apply": - evidence = apply_backlog( - policy, - backlog, - client, - inventory=inventory, - approved_completion_holds=approved_completion_holds, - cross_repository_targets=cross_repository_targets, - historical_cross_repository_completions=historical_cross_repository_completions, - frozen_cross_repository_lifecycles=frozen_cross_repository_lifecycles, - lifecycle_projection=lifecycle_projection, - lifecycle_projection_failures=lifecycle_projection_failures, - prerelease_supersessions=prerelease_supersessions, - public_metadata=public_metadata, - ) - else: - evidence = audit_backlog( - policy, - backlog, - client, - inventory=inventory, - approved_completion_holds=approved_completion_holds, - cross_repository_targets=cross_repository_targets, - historical_cross_repository_completions=historical_cross_repository_completions, - frozen_cross_repository_lifecycles=frozen_cross_repository_lifecycles, - lifecycle_projection=lifecycle_projection, - lifecycle_projection_failures=lifecycle_projection_failures, - prerelease_supersessions=prerelease_supersessions, - public_metadata=public_metadata, - ) - evidence["intake"] = _manifest_core(manifest) - _write_evidence(evidence_path, evidence) - return 0 - except AuthorityError as error: - failure_evidence = getattr(error, "evidence", None) - if arguments.command == "metadata-audit" and "policy" in locals(): - failure_evidence = build_public_age_audit(policy, [], [str(error)]) - if not isinstance(failure_evidence, dict): - failure_evidence = { - "schema": "durable-workflow.github-issue-authority-evidence/v1", - "mode": arguments.command, - "outcome": "fail", - } - failure_evidence["error"] = str(error) - _write_evidence( - evidence_path, - failure_evidence, - ) - print(f"issue authority failed: {error}", file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/packagist_metadata.py b/scripts/packagist_metadata.py deleted file mode 100644 index a643f75..0000000 --- a/scripts/packagist_metadata.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Expand Packagist's Composer 2 metadata representation.""" - -from __future__ import annotations - -import re -from typing import Any - -COMPOSER_MINIFIED_FORMAT = "composer/2.0" -COMPOSER_RELEASE_PATTERN = re.compile( - r"^v?(?P\d+(?:\.\d+){0,3})" - r"(?:[._-]?(?Pstable|dev|alpha|a|beta|b|rc|patch|pl|p)(?P(?:[._-]?\d+)*))?" - r"(?:\+[0-9A-Za-z.-]+)?$", - re.IGNORECASE, -) -STABILITY_ORDER = { - "dev": 0, - "alpha": 1, - "a": 1, - "beta": 2, - "b": 2, - "rc": 3, - "stable": 4, - "patch": 5, - "pl": 5, - "p": 5, -} - - -class PackagistMetadataError(ValueError): - """Packagist metadata does not satisfy the advertised wire format.""" - - -def package_versions(payload: Any, package: str) -> list[dict[str, Any]]: - """Return package versions after applying the advertised Composer diff format.""" - if not isinstance(payload, dict): - raise PackagistMetadataError("response must be an object") - packages = payload.get("packages") - if packages is None: - return [] - if not isinstance(packages, dict): - raise PackagistMetadataError("packages must be an object") - versions = packages.get(package) - if versions is None: - return [] - if not isinstance(versions, list): - raise PackagistMetadataError(f"packages.{package} must be a list") - - if "minified" not in payload: - return [_version_object(version, package) for version in versions] - minified_format = payload["minified"] - if minified_format != COMPOSER_MINIFIED_FORMAT: - raise PackagistMetadataError(f"unsupported minified format: {minified_format!r}") - - expanded: list[dict[str, Any]] = [] - previous: dict[str, Any] | None = None - previous_identity: tuple[tuple[int, ...], int, tuple[int, ...]] | None = None - previous_version: str | None = None - for index, version in enumerate(versions): - diff = _version_object(version, package) - compact_version = diff.get("version") - if not isinstance(compact_version, str) or not compact_version or compact_version == "__unset": - raise PackagistMetadataError(f"packages.{package} compact entry {index} must declare a version") - identity = _composer_release_identity(compact_version, package, index) - if previous_identity is not None and identity >= previous_identity: - raise PackagistMetadataError( - f"packages.{package} compact versions must be strictly descending; " - f"{compact_version!r} follows {previous_version!r}" - ) - if previous is None: - current = diff.copy() - else: - current = previous.copy() - for key, value in diff.items(): - if value == "__unset": - current.pop(key, None) - else: - current[key] = value - expanded.append(current) - previous = current - previous_identity = identity - previous_version = compact_version - return expanded - - -def exact_package_version(payload: Any, package: str, version: str) -> dict[str, Any] | None: - """Return one exact package version, rejecting ambiguous registry metadata.""" - requested = version[1:] if version.startswith("v") else version - matches: list[dict[str, Any]] = [] - for release in package_versions(payload, package): - release_version = release.get("version") - if not isinstance(release_version, str) or not release_version: - raise PackagistMetadataError(f"packages.{package} entries must declare a version") - comparable = release_version[1:] if release_version.startswith("v") else release_version - if comparable == requested: - matches.append(release) - if len(matches) > 1: - raise PackagistMetadataError(f"packages.{package} contains multiple records for exact version {version}") - return matches[0] if matches else None - - -def _version_object(version: Any, package: str) -> dict[str, Any]: - if not isinstance(version, dict): - raise PackagistMetadataError(f"packages.{package} entries must be objects") - return version - - -def _composer_release_identity( - version: str, package: str, index: int -) -> tuple[tuple[int, ...], int, tuple[int, ...]]: - match = COMPOSER_RELEASE_PATTERN.fullmatch(version) - if match is None: - raise PackagistMetadataError( - f"packages.{package} compact entry {index} has an unsupported release version {version!r}" - ) - release = tuple(int(part) for part in match.group("release").split(".")) - release += (0,) * (4 - len(release)) - stability = match.group("stability") - stability_order = STABILITY_ORDER[stability.lower()] if stability else STABILITY_ORDER["stable"] - number = tuple(int(part) for part in re.findall(r"\d+", match.group("number") or "")) - return release, stability_order, number diff --git a/scripts/product_train.py b/scripts/product_train.py deleted file mode 100644 index 2ec7807..0000000 --- a/scripts/product_train.py +++ /dev/null @@ -1,435 +0,0 @@ -"""Load and enforce the supported seven-component product train.""" - -from __future__ import annotations - -import hashlib -import json -import re -import urllib.error -import urllib.request -from functools import lru_cache -from pathlib import Path -from typing import Any - -from scripts.beta_candidate import COMPONENTS, CandidateError, canonical_pypi_version - -CONTRACT_PATH = Path(__file__).resolve().parents[1] / "product-train" / "current.json" -CURRENT_PLAN_PATH = Path(__file__).resolve().parents[1] / "release-plans" / "current.json" -SDK_SERVER_QUALIFICATION_PATH = Path(__file__).resolve().parents[1] / "product-train" / "sdk-server-qualification.json" -SCHEMA = "durable-workflow.product-train/v2" -SDK_SERVER_QUALIFICATION_SCHEMA = "durable-workflow.sdk-server-qualification/v1" -SDK_SERVER_QUALIFICATION_URL = ( - "https://raw.githubusercontent.com/durable-workflow/.github/main/product-train/sdk-server-qualification.json" -) -SDK_ARTIFACTS = ("sdk-php", "sdk-python", "sdk-rust") -SDK_SERVER_EXPERIMENTS = ("heartbeats", "replay", "signals-queries") -CONFORMANCE_SUITE_SCHEMA = "durable-workflow.beta-conformance.suite-result/v2" -CONFORMANCE_SUITE_MAX_BYTES = 256 * 1024 - - -def load_sdk_server_qualification( - path: Path = SDK_SERVER_QUALIFICATION_PATH, -) -> tuple[dict[str, Any], bytes]: - try: - raw = path.read_bytes() - qualification = json.loads(raw) - except (OSError, json.JSONDecodeError) as error: - raise CandidateError(f"cannot load SDK-to-Server qualification authority {path}: {error}") from error - if len(raw) > 64 * 1024: - raise CandidateError("SDK-to-Server qualification authority exceeds the 64 KiB limit") - return qualification, raw - - -@lru_cache(maxsize=8) -def download_conformance_suite(source_url: str) -> bytes: - request = urllib.request.Request( - source_url, - headers={ - "Accept": "application/octet-stream", - "User-Agent": "durable-workflow-product-train/1", - }, - ) - try: - with urllib.request.urlopen(request, timeout=30) as response: - raw = response.read(CONFORMANCE_SUITE_MAX_BYTES + 1) - except (OSError, urllib.error.URLError) as error: - raise CandidateError("cannot download immutable SDK-to-Server conformance suite") from error - if len(raw) > CONFORMANCE_SUITE_MAX_BYTES: - raise CandidateError("SDK-to-Server conformance suite exceeds the 256 KiB limit") - return raw - - -def load_conformance_suite(raw: bytes, expected_sha256: str) -> dict[str, Any]: - if not isinstance(raw, bytes): - raise CandidateError("SDK-to-Server conformance suite must be downloaded as bytes") - if len(raw) > CONFORMANCE_SUITE_MAX_BYTES: - raise CandidateError("SDK-to-Server conformance suite exceeds the 256 KiB limit") - if hashlib.sha256(raw).hexdigest() != expected_sha256: - raise CandidateError("SDK-to-Server conformance suite does not match its pinned SHA-256") - try: - suite = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise CandidateError("SDK-to-Server conformance suite is not valid JSON") from error - if not isinstance(suite, dict): - raise CandidateError("SDK-to-Server conformance suite must be a JSON object") - return suite - - -def validate_conformance_suite( - suite: dict[str, Any], - evidence: dict[str, Any], - release_plan: dict[str, Any], -) -> None: - expected_keys = { - "schema", - "candidate", - "artifact_tuple", - "source_identities", - "distribution_identities", - "executed_distribution_identities", - "runtime_dependencies", - "runner", - "server_runner", - "waterline_service_runner", - "source_policy", - "github_run", - "generated_at", - "outcome", - "experiments", - } - if ( - set(suite) != expected_keys - or suite.get("schema") != CONFORMANCE_SUITE_SCHEMA - or suite.get("outcome") != "pass" - or suite.get("github_run") != evidence["github_run"] - or suite.get("source_policy") - != { - "product_artifacts": "published_only", - "orchestration_source": "bound_control_plane_and_exact_candidate_images", - "local_product_source_checkout_used": False, - } - ): - raise CandidateError("SDK-to-Server conformance suite has an invalid or non-passing shape") - - components = release_plan["components"] - candidate = suite["candidate"] - expected_candidate = evidence["tag"].split("/")[-2] - if ( - not isinstance(candidate, dict) - or candidate.get("name") != expected_candidate - or suite.get("artifact_tuple") != components - or suite.get("source_identities") != {name: identity["commit"] for name, identity in components.items()} - ): - raise CandidateError("SDK-to-Server conformance suite does not bind the exact release-plan source tuple") - - declared_distributions = suite["distribution_identities"] - executed_distributions = suite["executed_distribution_identities"] - if not isinstance(declared_distributions, dict) or not isinstance(executed_distributions, dict): - raise CandidateError("SDK-to-Server conformance suite lacks distribution identities") - for artifact in (*SDK_ARTIFACTS, "server"): - if not valid_distribution_identity(declared_distributions.get(artifact)): - raise CandidateError(f"SDK-to-Server conformance suite lacks declared {artifact} distribution identity") - if not valid_distribution_identity(executed_distributions.get(artifact)): - raise CandidateError(f"SDK-to-Server conformance suite lacks executed {artifact} distribution identity") - - experiments = suite["experiments"] - if not isinstance(experiments, dict): - raise CandidateError("SDK-to-Server conformance suite lacks experiment results") - for name in SDK_SERVER_EXPERIMENTS: - experiment = experiments.get(name) - if ( - not isinstance(experiment, dict) - or set(experiment) - != { - "outcome", - "classification", - "owning_contract", - "required_clients", - "required_distributions", - "result_sha256", - "failure_fingerprint", - } - or experiment.get("outcome") != "pass" - or experiment.get("classification") != "passed" - or experiment.get("failure_fingerprint") is not None - or not isinstance(experiment.get("required_clients"), list) - or not all(artifact in experiment["required_clients"] for artifact in SDK_ARTIFACTS) - or not isinstance(experiment.get("required_distributions"), list) - or not all(artifact in experiment["required_distributions"] for artifact in ("server", *SDK_ARTIFACTS)) - or re.fullmatch(r"[0-9a-f]{64}", str(experiment.get("result_sha256", ""))) is None - ): - raise CandidateError( - f"SDK-to-Server conformance suite experiment {name} must pass for PHP, Python, Rust, and Server" - ) - - -def validate_sdk_server_qualification( - qualification: Any, - release_plan: dict[str, Any], - release_plan_reference: dict[str, str], - *, - suite_result_raw: bytes | None = None, -) -> dict[str, Any]: - expected_keys = { - "$schema", - "schema", - "release_plan", - "outcome", - "evidence", - "bindings", - } - if ( - not isinstance(qualification, dict) - or set(qualification) != expected_keys - or qualification.get("$schema") != "./sdk-server-qualification-schema.json" - or qualification.get("schema") != SDK_SERVER_QUALIFICATION_SCHEMA - or qualification.get("release_plan") != release_plan_reference - or qualification.get("outcome") != "pass" - ): - raise CandidateError("SDK-to-Server qualification authority has an invalid or non-passing shape") - - evidence = qualification["evidence"] - github_run = evidence.get("github_run") if isinstance(evidence, dict) else None - expected_evidence_url = ( - f"https://github.com/durable-workflow/.github/releases/download/{evidence.get('tag')}/suite-result.json" - if isinstance(evidence, dict) - else "" - ) - if ( - not isinstance(evidence, dict) - or set(evidence) - != { - "schema", - "tag", - "source_url", - "sha256", - "outcome", - "github_run", - } - or evidence.get("schema") != CONFORMANCE_SUITE_SCHEMA - or re.fullmatch( - r"beta-conformance/(?:beta|rc)-[a-z0-9._-]+/[1-9][0-9]*\.[1-9][0-9]*", - str(evidence.get("tag", "")), - ) - is None - or evidence.get("source_url") != expected_evidence_url - or re.fullmatch(r"[0-9a-f]{64}", str(evidence.get("sha256", ""))) is None - or evidence.get("outcome") != "pass" - or not isinstance(github_run, dict) - or set(github_run) != {"repository", "run_id", "run_attempt", "evidence_tag"} - or github_run.get("repository") != "durable-workflow/.github" - or type(github_run.get("run_id")) is not int - or github_run["run_id"] < 1 - or type(github_run.get("run_attempt")) is not int - or github_run["run_attempt"] < 1 - or github_run.get("evidence_tag") != evidence.get("tag") - or not evidence["tag"].endswith(f"/{github_run['run_id']}.{github_run['run_attempt']}") - ): - raise CandidateError("SDK-to-Server qualification has invalid conformance evidence") - - if suite_result_raw is None: - suite_result_raw = download_conformance_suite(evidence["source_url"]) - suite = load_conformance_suite(suite_result_raw, evidence["sha256"]) - validate_conformance_suite(suite, evidence, release_plan) - - bindings = qualification["bindings"] - if not isinstance(bindings, dict) or set(bindings) != set(SDK_ARTIFACTS): - raise CandidateError("SDK-to-Server qualification must define PHP, Python, and Rust bindings") - server = release_plan["components"]["server"] - executed_distributions = suite["executed_distribution_identities"] - for artifact in SDK_ARTIFACTS: - binding = bindings[artifact] - if ( - not isinstance(binding, dict) - or set(binding) - != { - "sdk", - "server", - "supported_server_versions", - "outcome", - "experiments", - } - or not isinstance(binding.get("sdk"), dict) - or set(binding["sdk"]) != {"source", "distribution"} - or binding["sdk"].get("source") != release_plan["components"][artifact] - or binding["sdk"].get("distribution") != executed_distributions[artifact] - or not isinstance(binding.get("server"), dict) - or set(binding["server"]) != {"source", "distribution"} - or binding["server"].get("source") != server - or binding["server"].get("distribution") != executed_distributions["server"] - or binding.get("supported_server_versions") != server["version"] - or binding.get("outcome") != "pass" - or binding.get("experiments") != list(SDK_SERVER_EXPERIMENTS) - ): - raise CandidateError(f"SDK-to-Server qualification does not contain a passing exact {artifact} binding") - return qualification - - -def valid_distribution_identity(value: Any) -> bool: - if ( - not isinstance(value, dict) - or set(value) != {"kind", "locator", "artifacts"} - or value.get("kind") not in {"composer", "pypi", "crates.io", "oci"} - or not isinstance(value.get("locator"), str) - or not value["locator"] - or not isinstance(value.get("artifacts"), list) - or not value["artifacts"] - ): - return False - names: set[str] = set() - for artifact in value["artifacts"]: - if ( - not isinstance(artifact, dict) - or set(artifact) != {"name", "sha256"} - or not isinstance(artifact.get("name"), str) - or not artifact["name"] - or artifact["name"] in names - or re.fullmatch(r"[0-9a-f]{64}", str(artifact.get("sha256", ""))) is None - ): - return False - names.add(artifact["name"]) - return True - - -def load_product_train( - path: Path = CONTRACT_PATH, - *, - current_plan_path: Path = CURRENT_PLAN_PATH, - qualification_path: Path = SDK_SERVER_QUALIFICATION_PATH, -) -> dict[str, Any]: - try: - contract = json.loads(path.read_text(encoding="utf-8")) - except (OSError, json.JSONDecodeError) as error: - raise CandidateError(f"cannot load product-train authority {path}: {error}") from error - - expected_keys = { - "$schema", - "schema", - "current", - "components", - "trains", - "progression", - "historical_prereleases", - } - if not isinstance(contract, dict) or set(contract) != expected_keys or contract.get("schema") != SCHEMA: - raise CandidateError("product-train authority has an invalid top-level shape") - if contract["components"] != list(COMPONENTS): - raise CandidateError("product-train authority components must follow release order") - trains = contract.get("trains") - current = contract.get("current") - if not isinstance(trains, dict) or current not in trains: - raise CandidateError("product-train authority does not define its current train") - current_train = trains[current] - if ( - not isinstance(current_train, dict) - or current_train.get("status") != "supported" - or set(current_train.get("versions", {})) != set(COMPONENTS) - ): - raise CandidateError("current product train does not define the supported seven-component tuple") - channel = current_train.get("channel") - if channel not in {"beta", "rc"} or re.fullmatch(rf"2\.0\.0-{channel}\.[1-9][0-9]*", str(current)) is None: - raise CandidateError("current product train must use a channel-matched 2.0.0 prerelease identifier") - if any(not isinstance(train, dict) for train in trains.values()): - raise CandidateError("product-train authority contains an invalid train record") - supported = [name for name, train in trains.items() if train.get("status") == "supported"] - if supported != [current]: - raise CandidateError("product-train authority must define exactly one supported train") - versions = current_train["versions"] - version_pattern = re.compile(rf"^2\.0\.0-{channel}\.[1-9][0-9]*$") - if any(not isinstance(version, str) or version_pattern.fullmatch(version) is None for version in versions.values()): - raise CandidateError("current component versions must be valid 2.0.0 prereleases in the selected channel") - progression = contract.get("progression") - if ( - not isinstance(progression, dict) - or progression.get("stable") != "semantic_versioning" - or progression.get("compatibility_shims") != "forbidden_between_2_0_prereleases" - or progression.get("prerelease") - not in { - "synchronized_beta_increment", - "synchronized_prerelease_increment", - "independent_prerelease_components", - } - or (len(set(versions.values())) > 1 and progression["prerelease"] != "independent_prerelease_components") - ): - raise CandidateError("product-train progression must authorize the selected component version model") - expected_registry_versions = dict(versions) - expected_registry_versions["sdk-python"] = canonical_pypi_version(versions["sdk-python"]) - if current_train.get("registry_versions") != expected_registry_versions: - raise CandidateError("current registry versions must identify the exact component tuple") - install = current_train.get("install") - waterline_install = install.get("waterline") if isinstance(install, dict) else None - stability = "beta" if channel == "beta" else "RC" - expected_install = { - "workflow": f"composer require durable-workflow/workflow:{versions['workflow']}@{stability}", - "sdk-php": f"composer require durable-workflow/sdk:{versions['sdk-php']}@{stability}", - "waterline": { - "embedded": ( - f"composer require durable-workflow/waterline:{versions['waterline']}@{stability} " - f"durable-workflow/workflow:{versions['workflow']}@{stability} " - f"durable-workflow/sdk:{versions['sdk-php']}@{stability}" - ), - "service": f"docker pull durableworkflow/waterline:{versions['waterline']}", - }, - "server": f"docker pull durableworkflow/server:{versions['server']}", - "cli": f"curl -fsSL https://durable-workflow.com/install.sh | VERSION={versions['cli']} sh", - "sdk-python": f"pip install durable-workflow=={canonical_pypi_version(versions['sdk-python'])}", - "sdk-rust": f"cargo add durable-workflow@={versions['sdk-rust']}", - } - if not isinstance(waterline_install, dict) or install != expected_install: - raise CandidateError("current product train install commands must identify the exact component tuple") - plan_reference = current_train.get("release_plan") - try: - plan_raw = current_plan_path.read_bytes() - plan = json.loads(plan_raw) - except (OSError, json.JSONDecodeError) as error: - raise CandidateError(f"cannot load current release-plan authority {current_plan_path}: {error}") from error - if not isinstance(plan, dict): - raise CandidateError("current release-plan authority must be a JSON object") - canonical_plan = (json.dumps(plan, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - expected_plan_reference = { - "tag": f"release-plan/{plan.get('plan')}", - "sha256": hashlib.sha256(canonical_plan).hexdigest(), - } - plan_components = plan.get("components") if isinstance(plan, dict) else None - if ( - plan_reference != expected_plan_reference - or plan.get("channel") != channel - or not isinstance(plan_components, dict) - or { - name: identity.get("version") if isinstance(identity, dict) else None - for name, identity in plan_components.items() - } - != current_train["versions"] - ): - raise CandidateError("current product train does not bind its exact public release plan") - - qualification_reference = current_train.get("sdk_server_qualification") - qualification, qualification_raw = load_sdk_server_qualification(qualification_path) - expected_qualification_reference = { - "schema": SDK_SERVER_QUALIFICATION_SCHEMA, - "source_url": SDK_SERVER_QUALIFICATION_URL, - "sha256": hashlib.sha256(qualification_raw).hexdigest(), - } - if qualification_reference != expected_qualification_reference: - raise CandidateError("current product train does not bind its exact SDK-to-Server qualification") - validate_sdk_server_qualification(qualification, plan, expected_plan_reference) - return contract - - -def require_current_product_train(components: Any) -> str: - contract = load_product_train() - current = str(contract["current"]) - expected_versions = contract["trains"][current]["versions"] - if not isinstance(components, dict): - raise CandidateError("current product train requires a seven-component tuple") - - mismatches = [ - f"{name}={components.get(name, {}).get('version', '')}" - for name, expected in expected_versions.items() - if not isinstance(components.get(name), dict) or components[name].get("version") != expected - ] - if mismatches: - raise CandidateError( - f"new prerelease records must use supported product train {current}; mismatched " + ", ".join(mismatches) - ) - return current diff --git a/scripts/protected_release_plan_writer.py b/scripts/protected_release_plan_writer.py deleted file mode 100644 index ed8246a..0000000 --- a/scripts/protected_release_plan_writer.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env python3 -"""Bind protected release-plan jobs to their serialized writers.""" - -from __future__ import annotations - -import argparse -import hashlib -import hmac -import re -import sys -from pathlib import Path -from typing import Any - -CONTROL_REPOSITORY = "durable-workflow/.github" -AUTHORITY_REF = "main" -PROTECTED_WORKFLOWS = frozenset( - { - "release-plan.yml", - "release-plan-supersession.yml", - } -) -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -HANDOFF_DOMAIN = b"durable-workflow.protected-release-plan-writer/v1\0" - - -class ProtectedWriterError(ValueError): - """A protected release-plan writer identity is absent or mismatched.""" - - -def _identity(value: Any, label: str) -> str: - if not isinstance(value, str) or not value: - raise ProtectedWriterError(f"protected release-plan {label} is absent") - return value - - -def _positive_integer(value: Any, label: str) -> int: - if isinstance(value, bool): - raise ProtectedWriterError(f"protected release-plan {label} must be a positive integer") - try: - parsed = int(value) - except (TypeError, ValueError) as error: - raise ProtectedWriterError(f"protected release-plan {label} must be a positive integer") from error - if parsed < 1: - raise ProtectedWriterError(f"protected release-plan {label} must be a positive integer") - return parsed - - -def expected_workflow_ref(workflow: Any) -> str: - workflow = _identity(workflow, "workflow") - if workflow not in PROTECTED_WORKFLOWS: - raise ProtectedWriterError(f"protected release-plan workflow is not allowlisted: {workflow}") - return f"{CONTROL_REPOSITORY}/.github/workflows/{workflow}@refs/heads/{AUTHORITY_REF}" - - -def validate_runtime_identity( - workflow: Any, - repository: Any, - ref: Any, - workflow_ref: Any, -) -> tuple[str, str, str, str]: - workflow = _identity(workflow, "workflow") - repository = _identity(repository, "repository") - ref = _identity(ref, "ref") - workflow_ref = _identity(workflow_ref, "workflow ref") - - if repository != CONTROL_REPOSITORY: - raise ProtectedWriterError( - f"protected release-plan repository mismatch: expected {CONTROL_REPOSITORY}, got {repository}" - ) - expected_ref = f"refs/heads/{AUTHORITY_REF}" - if ref != expected_ref: - raise ProtectedWriterError(f"protected release-plan ref mismatch: expected {expected_ref}, got {ref}") - expected = expected_workflow_ref(workflow) - if workflow_ref != expected: - raise ProtectedWriterError(f"protected release-plan workflow mismatch: expected {expected}, got {workflow_ref}") - return workflow, repository, ref, workflow_ref - - -def approved_writer_handoff( - workflow: Any, - repository: Any, - ref: Any, - workflow_ref: Any, - source_sha: Any, - run_id: Any, - producer_attempt: Any, -) -> str: - """Bind one successful protected job to its exact workflow run.""" - - workflow, repository, ref, workflow_ref = validate_runtime_identity( - workflow, - repository, - ref, - workflow_ref, - ) - source_sha = _identity(source_sha, "source SHA") - if COMMIT_PATTERN.fullmatch(source_sha) is None: - raise ProtectedWriterError("protected release-plan source SHA must be a full lowercase commit") - run_id = _positive_integer(run_id, "run ID") - producer_attempt = _positive_integer(producer_attempt, "approval attempt") - identity = "\0".join( - ( - workflow, - repository, - ref, - workflow_ref, - source_sha, - str(run_id), - str(producer_attempt), - ) - ).encode() - return hashlib.sha256(HANDOFF_DOMAIN + identity).hexdigest() - - -def validate_approved_writer_handoff( - handoff: Any, - workflow: Any, - repository: Any, - ref: Any, - workflow_ref: Any, - source_sha: Any, - run_id: Any, - current_attempt: Any, - producer_attempt: Any, -) -> None: - handoff = _identity(handoff, "approved writer handoff") - current_attempt = _positive_integer(current_attempt, "current attempt") - producer_attempt_value = _positive_integer(producer_attempt, "approval attempt") - if producer_attempt_value > current_attempt: - raise ProtectedWriterError("protected release-plan approval attempt is newer than the writer attempt") - expected = approved_writer_handoff( - workflow, - repository, - ref, - workflow_ref, - source_sha, - run_id, - producer_attempt_value, - ) - if not hmac.compare_digest(handoff, expected): - raise ProtectedWriterError("protected release-plan approved writer handoff does not match this workflow run") - - -def write_github_output(path: Path, values: dict[str, str | int]) -> None: - with path.open("a", encoding="utf-8") as output: - for key, value in values.items(): - output.write(f"{key}={value}\n") - - -def add_identity_arguments(parser: argparse.ArgumentParser) -> None: - parser.add_argument("--workflow", required=True, choices=sorted(PROTECTED_WORKFLOWS)) - parser.add_argument("--repository", required=True) - parser.add_argument("--ref", required=True) - parser.add_argument("--workflow-ref", required=True) - parser.add_argument("--source-sha", required=True) - parser.add_argument("--run-id", required=True) - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - create = subparsers.add_parser( - "create", - help="bind a successful protected job to its exact workflow run", - ) - add_identity_arguments(create) - create.add_argument("--run-attempt", required=True) - create.add_argument("--github-output", required=True, type=Path) - validate = subparsers.add_parser( - "validate", - help="fail unless a writer follows the exact successful protected job", - ) - add_identity_arguments(validate) - validate.add_argument("--current-attempt", required=True) - validate.add_argument("--producer-attempt", required=True) - validate.add_argument("--handoff", required=True) - return parser - - -def main(argv: list[str] | None = None) -> int: - arguments = build_parser().parse_args(argv) - try: - if arguments.command == "create": - handoff = approved_writer_handoff( - arguments.workflow, - arguments.repository, - arguments.ref, - arguments.workflow_ref, - arguments.source_sha, - arguments.run_id, - arguments.run_attempt, - ) - write_github_output( - arguments.github_output, - { - "handoff": handoff, - "producer-attempt": _positive_integer( - arguments.run_attempt, - "approval attempt", - ), - }, - ) - elif arguments.command == "validate": - validate_approved_writer_handoff( - arguments.handoff, - arguments.workflow, - arguments.repository, - arguments.ref, - arguments.workflow_ref, - arguments.source_sha, - arguments.run_id, - arguments.current_attempt, - arguments.producer_attempt, - ) - except ProtectedWriterError as error: - print(error, file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/recovery_authority_reconciliation.py b/scripts/recovery_authority_reconciliation.py deleted file mode 100644 index 172fa7f..0000000 --- a/scripts/recovery_authority_reconciliation.py +++ /dev/null @@ -1,483 +0,0 @@ -#!/usr/bin/env python3 -"""Propose one qualified reconciliation for protected recovery workflow drift.""" - -from __future__ import annotations - -import argparse -import copy -import json -import os -import re -import sys -import urllib.parse -from collections.abc import Mapping -from pathlib import Path -from typing import Any - -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.beta_candidate import COMPONENTS, CandidateError, PublicClient, canonical_json, write_github_output -from scripts.recovery_workflow_authority import ( - AUTHORITY_PATH, - CHECK_RUN_APP, - MAX_SOURCE_IDENTITIES_BYTES, - SOURCE_IDENTITIES_PATH, - SOURCE_IDENTITY_HISTORY_LIMIT, - RecoveryWorkflowAuthorityError, - branch_url, - compare_url, - exact_source_sha256, - qualification_policy_binding, - resolve_qualification_policy, - source_history_binding, - validate_authority, - validate_source_identities, - verify_authority_source_identities, - workflow_metadata_url, - workflow_run_url, - workflow_source_url, -) - -OBSERVATION_SCHEMA = "durable-workflow.component-release-recovery-authority-observation/v1" -DEFAULT_BRANCHES = { - "workflow": "v2", - "waterline": "v2", - "server": "main", - "cli": "main", - "sdk-php": "main", - "sdk-python": "main", - "sdk-rust": "main", -} - - -def _load_json(path: Path, label: str) -> dict[str, Any]: - try: - value = json.loads(path.read_bytes()) - except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError(f"cannot read {label}: {error}") from error - if not isinstance(value, dict): - raise RecoveryWorkflowAuthorityError(f"{label} must contain a JSON object") - return value - - -def component_identities() -> dict[str, tuple[str, str]]: - return {name: (component.repository, DEFAULT_BRANCHES[name]) for name, component in COMPONENTS.items()} - - -def check_runs_url(repository: str, commit: str, check_name: str) -> str: - encoded_check_name = urllib.parse.quote(check_name, safe="") - return ( - f"https://api.github.com/repos/{repository}/commits/{commit}/check-runs" - f"?filter=latest&check_name={encoded_check_name}&per_page=100" - ) - - -def _qualified_identity( - client: Any, - name: str, - repository: str, - branch: str, - commit: str, - requirement: Mapping[str, str], -) -> dict[str, Any]: - response = client.json(check_runs_url(repository, commit, requirement["required_check"])) - check_runs = response.get("check_runs") if isinstance(response, dict) else None - if not isinstance(check_runs, list): - raise RecoveryWorkflowAuthorityError(f"{name} recovery qualification checks have an invalid response") - matching = [ - check for check in check_runs if isinstance(check, dict) and check.get("name") == requirement["required_check"] - ] - if not matching: - raise RecoveryWorkflowAuthorityError(f"{name} recovery workflow source has no protected qualification check") - check = max(matching, key=lambda value: value.get("id") if isinstance(value.get("id"), int) else 0) - check_run_id = check.get("id") - app = check.get("app") - if ( - not isinstance(check_run_id, int) - or isinstance(check_run_id, bool) - or check_run_id < 1 - or check.get("head_sha") != commit - or check.get("status") != "completed" - or check.get("conclusion") != "success" - or not isinstance(app, dict) - or app.get("slug") != CHECK_RUN_APP - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery workflow source is not protected by a successful GitHub Actions check" - ) - check_url = check.get("html_url") - match = ( - re.fullmatch( - rf"https://github\.com/{re.escape(repository)}/actions/runs/([1-9][0-9]*)/job/{check_run_id}", - check_url, - ) - if isinstance(check_url, str) - else None - ) - if match is None: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery qualification check has an invalid workflow-run identity" - ) - run_id = int(match.group(1)) - run = client.json(workflow_run_url(repository, run_id)) - run_attempt = run.get("run_attempt") if isinstance(run, dict) else None - expected_run = { - "id": run_id, - "path": requirement["workflow"], - "event": "push", - "head_branch": branch, - "head_sha": commit, - "status": "completed", - "conclusion": "success", - "html_url": f"https://github.com/{repository}/actions/runs/{run_id}", - } - if ( - not isinstance(run, dict) - or not isinstance(run_attempt, int) - or isinstance(run_attempt, bool) - or run_attempt < 1 - or any(run.get(field) != expected for field, expected in expected_run.items()) - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery qualification is not an exact successful protected-branch run" - ) - return { - "check_run_id": check_run_id, - "check_url": check_url, - "conclusion": "success", - "event": "push", - "head_branch": branch, - "head_sha": commit, - "required_check": requirement["required_check"], - "run_attempt": run_attempt, - "run_id": run_id, - "status": "completed", - "url": expected_run["html_url"], - "workflow": requirement["workflow"], - } - - -def _protected_branch_observation( - client: Any, - name: str, - workflow: Mapping[str, str], - requirement: Mapping[str, str], -) -> dict[str, Any]: - repository = workflow["repository"] - branch = workflow["ref"].removeprefix("refs/heads/") - branch_data = client.json(branch_url(repository, branch)) - commit = branch_data.get("commit", {}).get("sha") if isinstance(branch_data, dict) else None - if not isinstance(commit, str) or not re.fullmatch(r"[0-9a-f]{40}", commit): - raise RecoveryWorkflowAuthorityError(f"{name} recovery protected branch did not resolve to an exact commit") - metadata = client.json(workflow_metadata_url(repository, workflow["path"])) - if ( - not isinstance(metadata, dict) - or metadata.get("path") != workflow["path"] - or metadata.get("state") != workflow["state"] - ): - raise RecoveryWorkflowAuthorityError(f"{name} recovery workflow does not expose the protected path and state") - raw = client.bytes( - workflow_source_url(repository, workflow["path"], commit), - accept="application/vnd.github.raw+json", - ) - return { - "source_commit": commit, - "sha256": exact_source_sha256(raw), - "qualification": _qualified_identity( - client, - name, - repository, - branch, - commit, - requirement, - ), - } - - -def reconcile_authority( - authority: dict[str, Any], - source_document: dict[str, Any], - policy: dict[str, Any], - policy_binding: Mapping[str, str], - client: Any, - components: Mapping[str, tuple[str, str]], - *, - source_raw: bytes | None = None, -) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: - if source_raw is not None and len(source_raw) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError("recovery protected source identities exceed the 1 MiB limit") - workflows = validate_authority(authority, components) - resolved_policy, requirements = resolve_qualification_policy( - client, - policy_binding, - components, - ) - if resolved_policy != policy: - raise RecoveryWorkflowAuthorityError( - "current recovery qualification policy differs from its protected binding" - ) - source_identities = validate_source_identities( - source_document, - workflows, - components, - ) - if source_raw is None and len(canonical_json(source_document)) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError("recovery protected source identities exceed the 1 MiB limit") - verify_authority_source_identities( - client, - workflows, - source_identities, - require_current=False, - ) - - proposed_authority = copy.deepcopy(authority) - proposed_sources = copy.deepcopy(source_document) - changes: list[dict[str, Any]] = [] - for name, workflow in workflows.items(): - observation = _protected_branch_observation( - client, - name, - workflow, - requirements[name], - ) - current = source_identities[name]["identities"][-1] - if observation["sha256"] == current["sha256"]: - continue - - repository = workflow["repository"] - comparison = client.json(compare_url(repository, current["source_commit"], observation["source_commit"])) - if ( - not isinstance(comparison, dict) - or comparison.get("status") != "ahead" - or comparison.get("base_commit", {}).get("sha") != current["source_commit"] - or comparison.get("merge_base_commit", {}).get("sha") != current["source_commit"] - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery workflow successor does not descend from the accepted identity" - ) - successor = { - **observation, - "qualification_policy": dict(policy_binding), - "supersedes": { - "source_commit": current["source_commit"], - "sha256": current["sha256"], - }, - } - proposed_authority["workflows"][name]["sha256"] = successor["sha256"] - proposed_record = proposed_sources["workflows"][name] - checkpoint: dict[str, Any] | None = None - if len(source_identities[name]["identities"]) == SOURCE_IDENTITY_HISTORY_LIMIT: - if source_raw is None: - raise RecoveryWorkflowAuthorityError( - "the exact protected source identity document is required for history rollover" - ) - try: - source_value = json.loads(source_raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError( - "recovery protected source identities are not valid UTF-8 JSON" - ) from error - if source_value != source_document: - raise RecoveryWorkflowAuthorityError( - "recovery source history rollover bytes do not match the validated document" - ) - checkpoint = { - "accepted_identities": ( - source_identities[name].get("checkpoint", {}).get("accepted_identities", 0) - + SOURCE_IDENTITY_HISTORY_LIMIT - ), - "predecessor": dict(successor["supersedes"]), - "source": source_history_binding(source_raw, policy_binding["commit"]), - } - proposed_record["checkpoint"] = checkpoint - proposed_record["identities"] = [successor] - else: - proposed_record["identities"].append(successor) - change = { - "component": name, - "previous": successor["supersedes"], - "successor": { - "source_commit": successor["source_commit"], - "sha256": successor["sha256"], - "qualification": successor["qualification"], - "qualification_policy": successor["qualification_policy"], - }, - } - if checkpoint is not None: - change["checkpoint"] = checkpoint - changes.append(change) - - if len(canonical_json(proposed_sources)) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError( - "proposed recovery protected source identities exceed the 1 MiB limit" - ) - proposed_workflows = validate_authority(proposed_authority, components) - validate_source_identities( - proposed_sources, - proposed_workflows, - components, - ) - observation = { - "schema": OBSERVATION_SCHEMA, - "outcome": "change-required" if changes else "current", - "changes": changes, - } - return proposed_authority, proposed_sources, observation - - -def _write_reconciliation( - authority_path: Path, - source_path: Path, - policy_path: Path, - proposed_authority_path: Path, - proposed_source_path: Path, - observation_path: Path, - policy_commit: str, - client: Any, - github_output: Path | None, -) -> dict[str, Any]: - try: - policy_raw = policy_path.read_bytes() - except OSError as error: - raise RecoveryWorkflowAuthorityError(f"cannot read qualification policy: {error}") from error - try: - policy = json.loads(policy_raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError("qualification policy is not valid UTF-8 JSON") from error - if not isinstance(policy, dict): - raise RecoveryWorkflowAuthorityError("qualification policy must contain a JSON object") - try: - source_raw = source_path.read_bytes() - except OSError as error: - raise RecoveryWorkflowAuthorityError(f"cannot read recovery protected source identities: {error}") from error - if len(source_raw) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError("recovery protected source identities exceed the 1 MiB limit") - proposed_authority, proposed_sources, observation = reconcile_authority( - _load_json(authority_path, "recovery workflow authority"), - _load_json(source_path, "recovery protected source identities"), - policy, - qualification_policy_binding(policy_raw, policy_commit), - client, - component_identities(), - source_raw=source_raw, - ) - proposed_authority_path.write_bytes(canonical_json(proposed_authority)) - proposed_source_path.write_bytes(canonical_json(proposed_sources)) - observation_path.write_bytes(canonical_json(observation)) - write_github_output( - github_output, - { - "changed": "true" if observation["changes"] else "false", - "components": ",".join(change["component"] for change in observation["changes"]), - }, - ) - return observation - - -def _verify_proposal( - authority_path: Path, - source_path: Path, - policy_path: Path, - proposed_authority_path: Path, - proposed_source_path: Path, - observation_path: Path, - policy_commit: str, - client: Any, -) -> dict[str, Any]: - try: - policy_raw = policy_path.read_bytes() - except OSError as error: - raise RecoveryWorkflowAuthorityError(f"cannot read qualification policy: {error}") from error - try: - policy = json.loads(policy_raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError("qualification policy is not valid UTF-8 JSON") from error - if not isinstance(policy, dict): - raise RecoveryWorkflowAuthorityError("qualification policy must contain a JSON object") - try: - source_raw = source_path.read_bytes() - except OSError as error: - raise RecoveryWorkflowAuthorityError(f"cannot read recovery protected source identities: {error}") from error - if len(source_raw) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError("recovery protected source identities exceed the 1 MiB limit") - expected_authority, expected_sources, expected_observation = reconcile_authority( - _load_json(authority_path, "recovery workflow authority"), - _load_json(source_path, "recovery protected source identities"), - policy, - qualification_policy_binding(policy_raw, policy_commit), - client, - component_identities(), - source_raw=source_raw, - ) - supplied_authority = _load_json(proposed_authority_path, "proposed recovery workflow authority") - supplied_sources = _load_json(proposed_source_path, "proposed recovery protected source identities") - supplied_observation = _load_json(observation_path, "recovery authority observation") - if ( - canonical_json(supplied_authority) != canonical_json(expected_authority) - or canonical_json(supplied_sources) != canonical_json(expected_sources) - or canonical_json(supplied_observation) != canonical_json(expected_observation) - ): - raise RecoveryWorkflowAuthorityError( - "recovery authority proposal differs from the current qualified observation" - ) - if not expected_observation["changes"]: - raise RecoveryWorkflowAuthorityError("recovery authority proposal contains no reconciliation") - return expected_observation - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("command", choices=("reconcile", "verify-proposal")) - parser.add_argument("--authority", type=Path, default=Path(AUTHORITY_PATH)) - parser.add_argument("--source-identities", type=Path, default=Path(SOURCE_IDENTITIES_PATH)) - parser.add_argument("--policy", type=Path, default=Path("qualification/policy.json")) - parser.add_argument("--policy-commit", default=os.environ.get("GITHUB_SHA")) - parser.add_argument("--proposed-authority", type=Path, required=True) - parser.add_argument("--proposed-source-identities", type=Path, required=True) - parser.add_argument("--observation", type=Path, required=True) - parser.add_argument("--github-output", type=Path) - parser.add_argument("--github-token", default=os.environ.get("GITHUB_TOKEN")) - return parser.parse_args(argv) - - -def main(argv: list[str] | None = None) -> int: - args = parse_args(argv or sys.argv[1:]) - try: - if args.policy_commit is None: - raise RecoveryWorkflowAuthorityError( - "the exact protected qualification policy commit is required" - ) - client = PublicClient(args.github_token) - if args.command == "reconcile": - result = _write_reconciliation( - args.authority, - args.source_identities, - args.policy, - args.proposed_authority, - args.proposed_source_identities, - args.observation, - args.policy_commit, - client, - args.github_output, - ) - else: - result = _verify_proposal( - args.authority, - args.source_identities, - args.policy, - args.proposed_authority, - args.proposed_source_identities, - args.observation, - args.policy_commit, - client, - ) - print(json.dumps(result, sort_keys=True)) - return 0 - except (CandidateError, RecoveryWorkflowAuthorityError) as error: - print(f"recovery authority reconciliation error: {error}", file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/recovery_workflow_authority.py b/scripts/recovery_workflow_authority.py deleted file mode 100644 index 3376245..0000000 --- a/scripts/recovery_workflow_authority.py +++ /dev/null @@ -1,1074 +0,0 @@ -"""Resolve and validate the qualified component recovery-workflow authority.""" - -from __future__ import annotations - -import hashlib -import hmac -import json -import re -import urllib.parse -from collections.abc import Mapping -from typing import Any - -SCHEMA = "durable-workflow.component-release-recovery-authority/v2" -CONTROL_REPOSITORY = "durable-workflow/.github" -AUTHORITY_REF = "main" -AUTHORITY_PATH = "release-recovery/authority.json" -QUALIFICATION_WORKFLOW = ".github/workflows/beta-candidate.yml" -QUALIFICATION_EVENT = "push" -QUALIFICATION_REF_PATH = f"{QUALIFICATION_WORKFLOW}@{AUTHORITY_REF}" -WORKFLOW_PATH = ".github/workflows/release-plan-recovery.yml" -SOURCE_IDENTITIES_SCHEMA = "durable-workflow.component-release-recovery-source-identities/v2" -SOURCE_IDENTITIES_PATH = "release-recovery/protected-source-identities.json" -SOURCE_IDENTITY_HISTORY_LIMIT = 100 -MAX_SOURCE_IDENTITIES_BYTES = 1024 * 1024 -QUALIFICATION_POLICY_PATH = "qualification/policy.json" -MAX_QUALIFICATION_POLICY_BYTES = 256 * 1024 -CHECK_RUN_APP = "github-actions" -SOURCE_IDENTITY = { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "path": AUTHORITY_PATH, - "qualification": { - "workflow": QUALIFICATION_WORKFLOW, - "event": QUALIFICATION_EVENT, - }, -} - - -class RecoveryWorkflowAuthorityError(ValueError): - """The protected recovery-workflow authority is malformed or mismatched.""" - - -def normalized_source_sha256(source: str) -> str: - return hashlib.sha256(source.replace("\r\n", "\n").encode("utf-8")).hexdigest() - - -def exact_source_sha256(raw: bytes) -> str: - try: - source = raw.decode("utf-8") - except UnicodeDecodeError as error: - raise RecoveryWorkflowAuthorityError("recovery workflow is not valid UTF-8") from error - if "\r" in source: - raise RecoveryWorkflowAuthorityError("recovery workflow source must use canonical LF bytes") - return hashlib.sha256(raw).hexdigest() - - -def _commit(value: Any, label: str) -> str: - if ( - not isinstance(value, str) - or len(value) != 40 - or any(character not in "0123456789abcdef" for character in value) - ): - raise RecoveryWorkflowAuthorityError(f"{label} has an invalid commit") - return value - - -def _positive_integer(value: Any, label: str) -> int: - if not isinstance(value, int) or isinstance(value, bool) or value < 1: - raise RecoveryWorkflowAuthorityError(f"{label} has an invalid identity") - return value - - -def branch_url(repository: str, branch: str) -> str: - encoded = urllib.parse.quote(branch, safe="") - return f"https://api.github.com/repos/{repository}/branches/{encoded}" - - -def compare_url(repository: str, base: str, head: str) -> str: - comparison = urllib.parse.quote(f"{base}...{head}", safe=".") - return f"https://api.github.com/repos/{repository}/compare/{comparison}" - - -def workflow_metadata_url(repository: str, path: str) -> str: - return ( - f"https://api.github.com/repos/{repository}/actions/workflows/" - f"{urllib.parse.quote(path.rsplit('/', 1)[-1], safe='')}" - ) - - -def workflow_source_url(repository: str, path: str, commit: str) -> str: - encoded_path = urllib.parse.quote(path, safe="/") - encoded_commit = urllib.parse.quote(commit, safe="") - return f"https://api.github.com/repos/{repository}/contents/{encoded_path}?ref={encoded_commit}" - - -def workflow_run_url(repository: str, run_id: int) -> str: - return f"https://api.github.com/repos/{repository}/actions/runs/{run_id}" - - -def check_run_url(repository: str, check_run_id: int) -> str: - return f"https://api.github.com/repos/{repository}/check-runs/{check_run_id}" - - -def authority_ref_url() -> str: - return f"https://api.github.com/repos/{CONTROL_REPOSITORY}/commits/{AUTHORITY_REF}" - - -def authority_url(commit: str) -> str: - return ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/contents/{AUTHORITY_PATH}" - f"?ref={commit}" - ) - - -def source_identities_url(commit: str) -> str: - return ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/contents/{SOURCE_IDENTITIES_PATH}" - f"?ref={commit}" - ) - - -def qualification_policy_url(commit: str) -> str: - return ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/contents/{QUALIFICATION_POLICY_PATH}" - f"?ref={commit}" - ) - - -def qualification_runs_url(commit: str) -> str: - workflow = QUALIFICATION_WORKFLOW.rsplit("/", 1)[-1] - return ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/workflows/{workflow}/runs" - f"?branch={AUTHORITY_REF}&event={QUALIFICATION_EVENT}&head_sha={commit}&per_page=100" - ) - - -def validate_authority_commit(value: Any) -> str: - commit = value.get("sha") if isinstance(value, dict) else None - if ( - not isinstance(commit, str) - or len(commit) != 40 - or any(character not in "0123456789abcdef" for character in commit) - ): - raise RecoveryWorkflowAuthorityError("recovery workflow authority ref has an invalid commit") - return commit - - -def _qualification_evidence(run: dict[str, Any], commit: str) -> dict[str, Any]: - run_id = run.get("id") - run_attempt = run.get("run_attempt") - if ( - not isinstance(run_id, int) - or isinstance(run_id, bool) - or run_id < 1 - or not isinstance(run_attempt, int) - or isinstance(run_attempt, bool) - or run_attempt < 1 - ): - raise RecoveryWorkflowAuthorityError( - "recovery workflow authority qualification has an invalid run identity" - ) - return { - "workflow": QUALIFICATION_WORKFLOW, - "path": run["path"], - "event": QUALIFICATION_EVENT, - "head_branch": AUTHORITY_REF, - "head_sha": commit, - "run_id": run_id, - "run_attempt": run_attempt, - "status": "completed", - "conclusion": "success", - "url": f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - } - - -def validate_authority_qualification(value: Any, commit: str) -> dict[str, Any]: - runs = value.get("workflow_runs") if isinstance(value, dict) else None - if not isinstance(runs, list): - raise RecoveryWorkflowAuthorityError( - "recovery workflow authority qualification response has an invalid shape" - ) - - candidates = [ - run - for run in runs - if isinstance(run, dict) - and run.get("path") in (QUALIFICATION_WORKFLOW, QUALIFICATION_REF_PATH) - and run.get("event") == QUALIFICATION_EVENT - and run.get("head_branch") == AUTHORITY_REF - ] - if not candidates: - raise RecoveryWorkflowAuthorityError( - "recovery workflow authority qualification is absent for the resolved commit" - ) - if any(run.get("head_sha") != commit for run in candidates): - raise RecoveryWorkflowAuthorityError( - "recovery workflow authority qualification is bound to another commit" - ) - - successful = [ - run - for run in candidates - if run.get("status") == "completed" and run.get("conclusion") == "success" - ] - if successful: - return _qualification_evidence(successful[0], commit) - if any(run.get("status") != "completed" for run in candidates): - raise RecoveryWorkflowAuthorityError( - "recovery workflow authority qualification is pending for the resolved commit" - ) - if any(run.get("conclusion") == "cancelled" for run in candidates): - raise RecoveryWorkflowAuthorityError( - "recovery workflow authority qualification was cancelled for the resolved commit" - ) - raise RecoveryWorkflowAuthorityError( - "recovery workflow authority qualification failed for the resolved commit" - ) - - -def qualified_source_identity( - raw: bytes, - commit: str, - qualification: dict[str, Any], -) -> dict[str, Any]: - return { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "commit": commit, - "path": AUTHORITY_PATH, - "sha256": hashlib.sha256(raw).hexdigest(), - "qualification": qualification, - } - - -def validate_authority( - value: Any, - components: Mapping[str, tuple[str, str]], -) -> dict[str, dict[str, str]]: - if not isinstance(value, dict) or set(value) != {"schema", "source", "workflows"}: - raise RecoveryWorkflowAuthorityError("recovery workflow authority has an invalid document shape") - if value.get("schema") != SCHEMA or value.get("source") != SOURCE_IDENTITY: - raise RecoveryWorkflowAuthorityError("recovery workflow authority has an unexpected protected source") - - workflows = value.get("workflows") - if not isinstance(workflows, dict) or set(workflows) != set(components): - raise RecoveryWorkflowAuthorityError("recovery workflow authority does not name the complete component set") - - validated: dict[str, dict[str, str]] = {} - for name, (repository, default_branch) in components.items(): - entry = workflows.get(name) - expected_identity = { - "repository": repository, - "ref": f"refs/heads/{default_branch}", - "path": WORKFLOW_PATH, - "state": "active", - } - if not isinstance(entry, dict) or set(entry) != {*expected_identity, "sha256"}: - raise RecoveryWorkflowAuthorityError(f"{name} recovery workflow authority has an invalid shape") - if any(entry.get(field) != expected for field, expected in expected_identity.items()): - raise RecoveryWorkflowAuthorityError(f"{name} recovery workflow authority has a mismatched identity") - digest = entry.get("sha256") - if ( - not isinstance(digest, str) - or len(digest) != 64 - or any(character not in "0123456789abcdef" for character in digest) - ): - raise RecoveryWorkflowAuthorityError(f"{name} recovery workflow authority has an invalid SHA-256") - validated[name] = dict(entry) - return validated - - -def decode_authority( - raw: bytes, - components: Mapping[str, tuple[str, str]], -) -> dict[str, dict[str, str]]: - try: - value = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError("recovery workflow authority is not valid UTF-8 JSON") from error - return validate_authority(value, components) - - -def qualification_requirements( - policy: Any, - components: Mapping[str, tuple[str, str]], -) -> dict[str, dict[str, str]]: - if not isinstance(policy, dict) or policy.get("organization") != "durable-workflow": - raise RecoveryWorkflowAuthorityError("recovery qualification policy has an invalid organization") - targets = policy.get("targets") - if not isinstance(targets, dict): - raise RecoveryWorkflowAuthorityError("recovery qualification policy has an invalid target set") - - requirements: dict[str, dict[str, str]] = {} - for name, (repository, branch) in components.items(): - target = targets.get(name) - expected_repository = repository.removeprefix("durable-workflow/") - workflows = target.get("workflows") if isinstance(target, dict) else None - if ( - not isinstance(target, dict) - or target.get("repository") != expected_repository - or target.get("branch") != branch - or not isinstance(workflows, list) - or len(workflows) != 1 - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery qualification policy has a mismatched protected target" - ) - workflow = workflows[0] - workflow_path = workflow.get("path") if isinstance(workflow, dict) else None - required_check = workflow.get("required_check") if isinstance(workflow, dict) else None - if ( - not isinstance(workflow_path, str) - or not re.fullmatch(r"[A-Za-z0-9._-]+\.ya?ml", workflow_path) - or not isinstance(required_check, str) - or not required_check - or len(f".github/workflows/{workflow_path}") > 256 - or len(required_check) > 256 - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery qualification policy has an invalid workflow identity" - ) - requirements[name] = { - "workflow": f".github/workflows/{workflow_path}", - "required_check": required_check, - } - return requirements - - -def qualification_policy_binding(raw: bytes, commit: str) -> dict[str, str]: - if len(raw) > MAX_QUALIFICATION_POLICY_BYTES: - raise RecoveryWorkflowAuthorityError("recovery qualification policy exceeds the 256 KiB limit") - try: - value = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError("recovery qualification policy is not valid UTF-8 JSON") from error - if not isinstance(value, dict): - raise RecoveryWorkflowAuthorityError("recovery qualification policy must contain a JSON object") - return { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "commit": _commit(commit, "recovery qualification policy"), - "path": QUALIFICATION_POLICY_PATH, - "sha256": hashlib.sha256(raw).hexdigest(), - } - - -def source_history_binding(raw: bytes, commit: str) -> dict[str, str]: - if len(raw) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError("recovery protected source identities exceed the 1 MiB limit") - try: - value = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError( - "recovery protected source identities are not valid UTF-8 JSON" - ) from error - if not isinstance(value, dict): - raise RecoveryWorkflowAuthorityError( - "recovery protected source identities must contain a JSON object" - ) - return { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "commit": _commit(commit, "recovery source history checkpoint"), - "path": SOURCE_IDENTITIES_PATH, - "sha256": hashlib.sha256(raw).hexdigest(), - } - - -def _validate_source_history_binding(name: str, value: Any) -> dict[str, str]: - expected = { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "path": SOURCE_IDENTITIES_PATH, - } - if not isinstance(value, dict) or set(value) != {*expected, "commit", "sha256"}: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint binding has an invalid shape" - ) - if any(value.get(field) != expected_value for field, expected_value in expected.items()): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint binding has a mismatched protected identity" - ) - commit = _commit(value.get("commit"), f"{name} recovery source history checkpoint") - digest = value.get("sha256") - if ( - not isinstance(digest, str) - or len(digest) != 64 - or any(character not in "0123456789abcdef" for character in digest) - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint binding has an invalid SHA-256" - ) - return {**expected, "commit": commit, "sha256": digest} - - -def _validate_qualification_policy_binding(name: str, value: Any) -> dict[str, str]: - expected = { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "path": QUALIFICATION_POLICY_PATH, - } - if not isinstance(value, dict) or set(value) != {*expected, "commit", "sha256"}: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy binding has an invalid shape" - ) - if any(value.get(field) != expected_value for field, expected_value in expected.items()): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy binding has a mismatched protected identity" - ) - commit = _commit(value.get("commit"), f"{name} recovery source qualification policy") - digest = value.get("sha256") - if ( - not isinstance(digest, str) - or len(digest) != 64 - or any(character not in "0123456789abcdef" for character in digest) - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy binding has an invalid SHA-256" - ) - return {**expected, "commit": commit, "sha256": digest} - - -def resolve_qualification_policy( - client: Any, - binding: Mapping[str, Any], - components: Mapping[str, tuple[str, str]], - *, - protected_head: str | None = None, -) -> tuple[dict[str, Any], dict[str, dict[str, str]]]: - name = next(iter(components), "protected") - validated = _validate_qualification_policy_binding(name, binding) - head = ( - _commit(protected_head, "recovery qualification policy protected branch") - if protected_head is not None - else validate_authority_commit(client.json(authority_ref_url())) - ) - commit = validated["commit"] - if commit != head: - comparison = client.json(compare_url(CONTROL_REPOSITORY, commit, head)) - if ( - not isinstance(comparison, dict) - or comparison.get("status") != "ahead" - or comparison.get("base_commit", {}).get("sha") != commit - or comparison.get("merge_base_commit", {}).get("sha") != commit - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy is not on the protected branch" - ) - raw = client.bytes( - qualification_policy_url(commit), - accept="application/vnd.github.raw+json", - ) - if len(raw) > MAX_QUALIFICATION_POLICY_BYTES: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy exceeds the 256 KiB limit" - ) - if not hmac.compare_digest(hashlib.sha256(raw).hexdigest(), validated["sha256"]): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy does not match its protected binding" - ) - try: - policy = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy is not valid UTF-8 JSON" - ) from error - if not isinstance(policy, dict): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy must contain a JSON object" - ) - return policy, qualification_requirements(policy, components) - - -def _validate_qualification( - name: str, - value: Any, - repository: str, - branch: str, - commit: str, - requirement: Mapping[str, str], -) -> dict[str, Any]: - fields = { - "check_run_id", - "check_url", - "conclusion", - "event", - "head_branch", - "head_sha", - "required_check", - "run_attempt", - "run_id", - "status", - "url", - "workflow", - } - if not isinstance(value, dict) or set(value) != fields: - raise RecoveryWorkflowAuthorityError(f"{name} recovery source qualification has an invalid shape") - workflow = requirement.get("workflow") - required_check = requirement.get("required_check") - if ( - not isinstance(workflow, str) - or not re.fullmatch(r"\.github/workflows/[A-Za-z0-9._-]+\.ya?ml", workflow) - or not isinstance(required_check, str) - or not required_check - or len(workflow) > 256 - or len(required_check) > 256 - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification has an invalid protected policy contract" - ) - run_id = _positive_integer(value.get("run_id"), f"{name} recovery qualification run") - run_attempt = _positive_integer( - value.get("run_attempt"), - f"{name} recovery qualification attempt", - ) - check_run_id = _positive_integer( - value.get("check_run_id"), - f"{name} recovery qualification check", - ) - expected = { - "check_run_id": check_run_id, - "check_url": (f"https://github.com/{repository}/actions/runs/{run_id}/job/{check_run_id}"), - "conclusion": "success", - "event": "push", - "head_branch": branch, - "head_sha": commit, - "required_check": required_check, - "run_attempt": run_attempt, - "run_id": run_id, - "status": "completed", - "url": f"https://github.com/{repository}/actions/runs/{run_id}", - "workflow": workflow, - } - if value != expected: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification has a mismatched protected identity" - ) - return dict(value) - - -def _validate_predecessor(name: str, value: Any) -> dict[str, str]: - if not isinstance(value, dict) or set(value) != {"source_commit", "sha256"}: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint predecessor has an invalid shape" - ) - commit = _commit( - value.get("source_commit"), - f"{name} recovery source history checkpoint predecessor", - ) - digest = value.get("sha256") - if ( - not isinstance(digest, str) - or len(digest) != 64 - or any(character not in "0123456789abcdef" for character in digest) - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint predecessor has an invalid SHA-256" - ) - return {"source_commit": commit, "sha256": digest} - - -def _validate_source_history_checkpoint(name: str, value: Any) -> dict[str, Any]: - if not isinstance(value, dict) or set(value) != { - "accepted_identities", - "predecessor", - "source", - }: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint has an invalid shape" - ) - accepted_identities = value.get("accepted_identities") - if ( - not isinstance(accepted_identities, int) - or isinstance(accepted_identities, bool) - or accepted_identities < SOURCE_IDENTITY_HISTORY_LIMIT - or accepted_identities % SOURCE_IDENTITY_HISTORY_LIMIT - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint has an invalid accepted identity count" - ) - return { - "accepted_identities": accepted_identities, - "predecessor": _validate_predecessor(name, value["predecessor"]), - "source": _validate_source_history_binding(name, value["source"]), - } - - -def validate_source_identities( - value: Any, - workflows: Mapping[str, Mapping[str, str]], - components: Mapping[str, tuple[str, str]], -) -> dict[str, dict[str, Any]]: - expected_source = { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "authority_path": AUTHORITY_PATH, - "path": SOURCE_IDENTITIES_PATH, - } - if not isinstance(value, dict) or set(value) != {"schema", "source", "workflows"}: - raise RecoveryWorkflowAuthorityError("recovery protected source identities have an invalid document shape") - if value.get("schema") != SOURCE_IDENTITIES_SCHEMA or value.get("source") != expected_source: - raise RecoveryWorkflowAuthorityError("recovery protected source identities have an unexpected authority") - records = value.get("workflows") - if not isinstance(records, dict) or set(records) != set(components): - raise RecoveryWorkflowAuthorityError( - "recovery protected source identities do not name the complete component set" - ) - validated: dict[str, dict[str, Any]] = {} - for name, (repository, branch) in components.items(): - record = records[name] - expected_identity = { - "repository": repository, - "ref": f"refs/heads/{branch}", - "path": WORKFLOW_PATH, - "state": "active", - } - if not isinstance(record, dict) or set(record) not in ( - {*expected_identity, "identities"}, - {*expected_identity, "checkpoint", "identities"}, - ): - raise RecoveryWorkflowAuthorityError(f"{name} recovery protected source history has an invalid shape") - if any(record.get(field) != expected for field, expected in expected_identity.items()): - raise RecoveryWorkflowAuthorityError(f"{name} recovery protected source history has a mismatched identity") - identities = record.get("identities") - if not isinstance(identities, list) or not identities: - raise RecoveryWorkflowAuthorityError(f"{name} recovery protected source history is empty") - if len(identities) > SOURCE_IDENTITY_HISTORY_LIMIT: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source history exceeds the {SOURCE_IDENTITY_HISTORY_LIMIT}-identity limit" - ) - - checkpoint = ( - _validate_source_history_checkpoint(name, record["checkpoint"]) - if "checkpoint" in record - else None - ) - previous: dict[str, Any] | None = checkpoint["predecessor"] if checkpoint is not None else None - accepted: list[dict[str, Any]] = [] - seen: set[tuple[str, str]] = ( - {(previous["source_commit"], previous["sha256"])} - if previous is not None - else set() - ) - for index, identity in enumerate(identities): - expected_fields = { - "source_commit", - "sha256", - "qualification", - "qualification_policy", - } - if index or checkpoint is not None: - expected_fields.add("supersedes") - if not isinstance(identity, dict) or set(identity) != expected_fields: - raise RecoveryWorkflowAuthorityError(f"{name} recovery protected source identity has an invalid shape") - commit = _commit( - identity.get("source_commit"), - f"{name} recovery protected source identity", - ) - digest = identity.get("sha256") - if ( - not isinstance(digest, str) - or len(digest) != 64 - or any(character not in "0123456789abcdef" for character in digest) - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source identity has an invalid SHA-256" - ) - if (commit, digest) in seen: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source history repeats an accepted identity" - ) - if previous is not None and identity.get("supersedes") != { - "source_commit": previous["source_commit"], - "sha256": previous["sha256"], - }: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source successor has a mismatched predecessor" - ) - accepted_identity = { - "source_commit": commit, - "sha256": digest, - "qualification": _validate_qualification( - name, - identity["qualification"], - repository, - branch, - commit, - { - "workflow": identity["qualification"].get("workflow"), - "required_check": identity["qualification"].get("required_check"), - } - if isinstance(identity.get("qualification"), dict) - else {}, - ), - "qualification_policy": _validate_qualification_policy_binding( - name, - identity["qualification_policy"], - ), - } - if previous is not None: - accepted_identity["supersedes"] = dict(identity["supersedes"]) - accepted.append(accepted_identity) - seen.add((commit, digest)) - previous = accepted_identity - - if accepted[-1]["sha256"] != workflows[name]["sha256"]: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source history does not bind the current authority" - ) - validated[name] = { - **expected_identity, - **({"checkpoint": checkpoint} if checkpoint is not None else {}), - "identities": accepted, - } - return validated - - -def decode_source_identities( - raw: bytes, - workflows: Mapping[str, Mapping[str, str]], - components: Mapping[str, tuple[str, str]], -) -> dict[str, dict[str, Any]]: - if len(raw) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError("recovery protected source identities exceed the 1 MiB limit") - try: - value = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise RecoveryWorkflowAuthorityError("recovery protected source identities are not valid UTF-8 JSON") from error - return validate_source_identities(value, workflows, components) - - -def load_qualified_authority( - client: Any, - components: Mapping[str, tuple[str, str]], -) -> tuple[dict[str, dict[str, str]], dict[str, Any]]: - commit = validate_authority_commit(client.json(authority_ref_url())) - qualification = validate_authority_qualification( - client.json(qualification_runs_url(commit)), - commit, - ) - raw = client.bytes(authority_url(commit), accept="application/vnd.github.raw+json") - workflows = decode_authority(raw, components) - return workflows, qualified_source_identity(raw, commit, qualification) - - -def verify_workflow_source(name: str, source: str, expected_sha256: str) -> str: - actual_sha256 = normalized_source_sha256(source) - if not hmac.compare_digest(actual_sha256, expected_sha256): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery workflow does not match the protected source identity" - ) - return actual_sha256 - - -def validate_live_qualification( - client: Any, - name: str, - repository: str, - branch: str, - identity: Mapping[str, Any], - requirement: Mapping[str, str], -) -> dict[str, Any]: - commit = identity["source_commit"] - recorded = identity["qualification"] - run = client.json(workflow_run_url(repository, recorded["run_id"])) - expected_run = { - "id": recorded["run_id"], - "run_attempt": recorded["run_attempt"], - "path": requirement["workflow"], - "event": "push", - "head_branch": branch, - "head_sha": commit, - "status": "completed", - "conclusion": "success", - "html_url": recorded["url"], - } - if not isinstance(run, dict) or any(run.get(field) != expected for field, expected in expected_run.items()): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery qualification run is not exact successful protected-branch evidence" - ) - - check = client.json(check_run_url(repository, recorded["check_run_id"])) - app = check.get("app") if isinstance(check, dict) else None - expected_check = { - "id": recorded["check_run_id"], - "name": requirement["required_check"], - "head_sha": commit, - "status": "completed", - "conclusion": "success", - "html_url": recorded["check_url"], - } - if ( - not isinstance(check, dict) - or any(check.get(field) != expected for field, expected in expected_check.items()) - or not isinstance(app, dict) - or app.get("slug") != CHECK_RUN_APP - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery qualification check is not exact successful GitHub Actions evidence" - ) - return dict(recorded) - - -def _verify_source_history_checkpoint( - client: Any, - name: str, - checkpoint: Mapping[str, Any], - components: Mapping[str, tuple[str, str]], - protected_head: str, - cache: dict[tuple[str, str], dict[str, dict[str, Any]]], -) -> dict[str, Any]: - binding = _validate_source_history_binding(name, checkpoint["source"]) - cache_key = (binding["commit"], binding["sha256"]) - if cache_key not in cache: - commit = binding["commit"] - if commit != protected_head: - comparison = client.json(compare_url(CONTROL_REPOSITORY, commit, protected_head)) - if ( - not isinstance(comparison, dict) - or comparison.get("status") != "ahead" - or comparison.get("base_commit", {}).get("sha") != commit - or comparison.get("merge_base_commit", {}).get("sha") != commit - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint is not on the protected branch" - ) - source_raw = client.bytes( - source_identities_url(commit), - accept="application/vnd.github.raw+json", - ) - if len(source_raw) > MAX_SOURCE_IDENTITIES_BYTES: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint exceeds the 1 MiB limit" - ) - if not hmac.compare_digest(hashlib.sha256(source_raw).hexdigest(), binding["sha256"]): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint does not match its protected binding" - ) - authority_raw = client.bytes( - authority_url(commit), - accept="application/vnd.github.raw+json", - ) - if len(authority_raw) > 64 * 1024: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint authority exceeds the 64 KiB limit" - ) - historical_workflows = decode_authority(authority_raw, components) - cache[cache_key] = decode_source_identities( - source_raw, - historical_workflows, - components, - ) - - historical_record = cache[cache_key][name] - historical_identities = historical_record["identities"] - if len(historical_identities) != SOURCE_IDENTITY_HISTORY_LIMIT: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint was not created at the retention boundary" - ) - historical_count = ( - historical_record["checkpoint"]["accepted_identities"] - if "checkpoint" in historical_record - else 0 - ) - if checkpoint["accepted_identities"] != historical_count + SOURCE_IDENTITY_HISTORY_LIMIT: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint has a discontinuous accepted identity count" - ) - predecessor = { - "source_commit": historical_identities[-1]["source_commit"], - "sha256": historical_identities[-1]["sha256"], - } - if checkpoint["predecessor"] != predecessor: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source history checkpoint does not bind the exact current predecessor" - ) - return { - "accepted_identities": checkpoint["accepted_identities"], - "predecessor": predecessor, - "source": binding, - } - - -def verify_authority_source_identities( - client: Any, - workflows: Mapping[str, Mapping[str, str]], - source_identities: Mapping[str, Mapping[str, Any]], - *, - require_current: bool = True, -) -> dict[str, dict[str, Any]]: - evidence: dict[str, dict[str, Any]] = {} - policy_head = validate_authority_commit(client.json(authority_ref_url())) - policy_components = { - name: ( - expected["repository"], - expected["ref"].removeprefix("refs/heads/"), - ) - for name, expected in workflows.items() - } - policy_cache: dict[tuple[str, str], dict[str, dict[str, str]]] = {} - checkpoint_cache: dict[tuple[str, str], dict[str, dict[str, Any]]] = {} - for name, expected in workflows.items(): - record = source_identities[name] - repository = expected["repository"] - branch = expected["ref"].removeprefix("refs/heads/") - branch_data = client.json(branch_url(repository, branch)) - head_commit = branch_data.get("commit", {}).get("sha") if isinstance(branch_data, dict) else None - head_commit = _commit(head_commit, f"{name} protected branch") - - metadata = client.json(workflow_metadata_url(repository, expected["path"])) - if ( - not isinstance(metadata, dict) - or metadata.get("path") != expected["path"] - or metadata.get("state") != expected["state"] - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery workflow does not expose the protected path and state" - ) - - checkpoint = record.get("checkpoint") - verified_checkpoint = ( - _verify_source_history_checkpoint( - client, - name, - checkpoint, - policy_components, - policy_head, - checkpoint_cache, - ) - if checkpoint is not None - else None - ) - verified_history: list[dict[str, Any]] = [] - head_source: bytes | None = None - previous_source_commit = ( - verified_checkpoint["predecessor"]["source_commit"] - if verified_checkpoint is not None - else None - ) - for identity in record["identities"]: - commit = identity["source_commit"] - if previous_source_commit is not None: - comparison = client.json(compare_url(repository, previous_source_commit, commit)) - if ( - not isinstance(comparison, dict) - or comparison.get("status") != "ahead" - or comparison.get("base_commit", {}).get("sha") != previous_source_commit - or comparison.get("merge_base_commit", {}).get("sha") != previous_source_commit - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source successor does not descend from its exact predecessor" - ) - raw = client.bytes( - workflow_source_url(repository, expected["path"], commit), - accept="application/vnd.github.raw+json", - ) - if commit == head_commit: - head_source = raw - if not hmac.compare_digest(exact_source_sha256(raw), identity["sha256"]): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source bytes do not match the accepted identity" - ) - binding = identity["qualification_policy"] - policy_identity = (binding["commit"], binding["sha256"]) - if policy_identity not in policy_cache: - _policy, policy_cache[policy_identity] = resolve_qualification_policy( - client, - binding, - policy_components, - protected_head=policy_head, - ) - requirement = policy_cache[policy_identity].get(name) - if requirement is None: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery source qualification policy does not resolve its protected target" - ) - _validate_qualification( - name, - identity["qualification"], - repository, - branch, - commit, - requirement, - ) - qualification = validate_live_qualification( - client, - name, - repository, - branch, - identity, - requirement, - ) - verified_history.append( - { - "source_commit": commit, - "sha256": identity["sha256"], - "qualification": qualification, - "qualification_policy": dict(binding), - } - ) - previous_source_commit = commit - terminal_commit = record["identities"][-1]["source_commit"] - if terminal_commit != head_commit: - comparison = client.json(compare_url(repository, terminal_commit, head_commit)) - if ( - not isinstance(comparison, dict) - or comparison.get("status") != "ahead" - or comparison.get("base_commit", {}).get("sha") != terminal_commit - or comparison.get("merge_base_commit", {}).get("sha") != terminal_commit - ): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery protected source history is not on the protected branch" - ) - if head_source is None: - head_source = client.bytes( - workflow_source_url(repository, expected["path"], head_commit), - accept="application/vnd.github.raw+json", - ) - head_sha256 = exact_source_sha256(head_source) - if require_current and not hmac.compare_digest(head_sha256, expected["sha256"]): - raise RecoveryWorkflowAuthorityError( - f"{name} recovery workflow does not match the protected source identity" - ) - evidence[name] = { - "repository": repository, - "ref": expected["ref"], - "path": expected["path"], - "state": expected["state"], - "head_commit": head_commit, - "sha256": head_sha256, - "workflow_id": metadata.get("id"), - "url": metadata.get("html_url"), - **({"checkpoint": verified_checkpoint} if verified_checkpoint is not None else {}), - "identities": verified_history, - } - return evidence - - -def verify_authority_workflow_sources( - client: Any, - workflows: Mapping[str, Mapping[str, str]], -) -> dict[str, dict[str, Any]]: - evidence: dict[str, dict[str, Any]] = {} - for name, expected in workflows.items(): - repository = expected["repository"] - path = expected["path"] - branch = expected["ref"].removeprefix("refs/heads/") - workflow = client.json(workflow_metadata_url(repository, path)) - if workflow.get("path") != path or workflow.get("state") != expected["state"]: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery workflow does not expose the protected path and state" - ) - - source_url = workflow_source_url(repository, path, branch) - try: - source = client.bytes( - source_url, - accept="application/vnd.github.raw+json", - ).decode("utf-8") - except UnicodeDecodeError as error: - raise RecoveryWorkflowAuthorityError( - f"{name} recovery workflow is not valid UTF-8" - ) from error - digest = verify_workflow_source(name, source, expected["sha256"]) - evidence[name] = { - "repository": repository, - "ref": expected["ref"], - "path": path, - "state": workflow["state"], - "sha256": digest, - "workflow_id": workflow.get("id"), - "url": workflow.get("html_url"), - } - return evidence diff --git a/scripts/release_asset_mirror.py b/scripts/release_asset_mirror.py deleted file mode 100644 index 68c834b..0000000 --- a/scripts/release_asset_mirror.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python3 -"""Create or compare a GitHub Release asset against immutable Git authority.""" - -from __future__ import annotations - -import argparse -import filecmp -import json -import os -import re -import shutil -import subprocess -import sys -import tempfile -import time -from collections.abc import Callable, Sequence -from pathlib import Path - -REPOSITORY_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$") -DEFAULT_RETRY_DELAYS = (1.0, 2.0, 4.0, 8.0) - -Runner = Callable[[list[str]], subprocess.CompletedProcess[str]] -Sleeper = Callable[[float], None] - - -class MirrorError(RuntimeError): - """A Release mirror cannot be reconciled with its immutable authority.""" - - -def run_command(command: list[str]) -> subprocess.CompletedProcess[str]: - return subprocess.run(command, check=False, capture_output=True, text=True) - - -def command_problem(result: subprocess.CompletedProcess[str]) -> str: - message = (result.stderr or result.stdout or "").strip() - if not message: - message = f"command exited with status {result.returncode}" - return " ".join(message.split())[:500] - - -def validate_inputs(repository: str, tag: str, source_path: Path, asset_name: str) -> None: - if not REPOSITORY_PATTERN.fullmatch(repository): - raise MirrorError("release repository has an invalid identity") - if not tag or len(tag) > 255 or tag.startswith("-") or any(character in tag for character in "\0\r\n"): - raise MirrorError("release tag has an invalid identity") - if ( - not asset_name - or len(asset_name) > 255 - or Path(asset_name).name != asset_name - or asset_name in {".", ".."} - or any(character in asset_name for character in "\0\r\n#*?[]") - ): - raise MirrorError("release asset has an unsafe name") - if source_path.is_symlink() or not source_path.is_file(): - raise MirrorError("immutable Git authority must be a regular file") - - -def parse_asset_names(payload: str) -> set[str]: - try: - release = json.loads(payload) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise MirrorError("release asset listing is not valid JSON") from error - if not isinstance(release, dict) or not isinstance(release.get("assets"), list): - raise MirrorError("release asset listing has an invalid shape") - names: list[str] = [] - for asset in release["assets"]: - if not isinstance(asset, dict) or not isinstance(asset.get("name"), str) or not asset["name"]: - raise MirrorError("release asset listing contains an invalid asset") - names.append(asset["name"]) - if len(names) != len(set(names)): - raise MirrorError("release asset listing contains duplicate names") - return set(names) - - -def inspect_assets( - repository: str, - tag: str, - *, - expected_asset: str | None, - runner: Runner, - retry_delays: Sequence[float], - sleep: Sleeper, -) -> set[str]: - attempts = len(retry_delays) + 1 - last_problem = "release metadata did not contain the expected asset" - for attempt in range(attempts): - result = runner( - [ - "gh", - "release", - "view", - tag, - "--repo", - repository, - "--json", - "assets", - ] - ) - if result.returncode == 0: - try: - names = parse_asset_names(result.stdout) - except MirrorError as error: - last_problem = str(error) - else: - if expected_asset is None or expected_asset in names: - return names - last_problem = f"release asset {expected_asset} is not visible yet" - else: - last_problem = command_problem(result) - if attempt < len(retry_delays): - sleep(retry_delays[attempt]) - - if expected_asset is not None: - raise MirrorError( - f"release asset {expected_asset} did not become visible after {attempts} attempts: {last_problem}" - ) - raise MirrorError(f"cannot inspect release assets after {attempts} attempts: {last_problem}") - - -def download_and_compare( - repository: str, - tag: str, - source_path: Path, - asset_name: str, - *, - runner: Runner, - retry_delays: Sequence[float], - sleep: Sleeper, -) -> None: - attempts = len(retry_delays) + 1 - last_problem = "release asset download did not produce a regular file" - for attempt in range(attempts): - with tempfile.TemporaryDirectory(prefix="release-asset-download-") as temporary: - destination = Path(temporary) - result = runner( - [ - "gh", - "release", - "download", - tag, - "--repo", - repository, - "--pattern", - asset_name, - "--dir", - str(destination), - ] - ) - downloaded = destination / asset_name - if result.returncode == 0: - if downloaded.is_symlink() or not downloaded.is_file(): - last_problem = "release asset download did not produce a regular file" - elif not filecmp.cmp(source_path, downloaded, shallow=False): - raise MirrorError(f"release asset {asset_name} differs from immutable Git authority") - else: - return - else: - last_problem = command_problem(result) - if attempt < len(retry_delays): - sleep(retry_delays[attempt]) - - raise MirrorError(f"cannot download release asset {asset_name} after {attempts} attempts: {last_problem}") - - -def upload_asset( - repository: str, - tag: str, - source_path: Path, - asset_name: str, - *, - runner: Runner, -) -> subprocess.CompletedProcess[str]: - with tempfile.TemporaryDirectory(prefix="release-asset-upload-") as temporary: - upload_path = Path(temporary) / asset_name - shutil.copyfile(source_path, upload_path) - return runner( - [ - "gh", - "release", - "upload", - tag, - str(upload_path), - "--repo", - repository, - ] - ) - - -def repair_asset( - repository: str, - tag: str, - source_path: Path, - asset_name: str, - *, - runner: Runner = run_command, - retry_delays: Sequence[float] = DEFAULT_RETRY_DELAYS, - sleep: Sleeper = time.sleep, -) -> str: - validate_inputs(repository, tag, source_path, asset_name) - assets = inspect_assets( - repository, - tag, - expected_asset=None, - runner=runner, - retry_delays=retry_delays, - sleep=sleep, - ) - if asset_name in assets: - download_and_compare( - repository, - tag, - source_path, - asset_name, - runner=runner, - retry_delays=retry_delays, - sleep=sleep, - ) - return "matched" - - upload = upload_asset(repository, tag, source_path, asset_name, runner=runner) - if upload.returncode == 0: - return "uploaded" - - upload_problem = command_problem(upload) - try: - inspect_assets( - repository, - tag, - expected_asset=asset_name, - runner=runner, - retry_delays=retry_delays, - sleep=sleep, - ) - except MirrorError as visibility_error: - raise MirrorError(f"cannot upload release asset {asset_name}: {upload_problem}; {visibility_error}") from None - download_and_compare( - repository, - tag, - source_path, - asset_name, - runner=runner, - retry_delays=retry_delays, - sleep=sleep, - ) - return "matched" - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description=__doc__) - commands = parser.add_subparsers(dest="command", required=True) - repair = commands.add_parser("repair", help="create or compare one immutable Release asset") - repair.add_argument("tag") - repair.add_argument("source_path", type=Path) - repair.add_argument("asset_name") - repair.add_argument("--repository", required=True) - return parser - - -def main(argv: Sequence[str] | None = None) -> int: - arguments = build_parser().parse_args(argv) - try: - if not (os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")): - raise MirrorError("GH_TOKEN or GITHUB_TOKEN is required to repair a Release asset") - result = repair_asset( - arguments.repository, - arguments.tag, - arguments.source_path, - arguments.asset_name, - ) - except MirrorError as error: - print(f"release mirror error: {error}", file=sys.stderr) - return 1 - print(f"release asset {arguments.asset_name}: {result}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/release_plan.py b/scripts/release_plan.py deleted file mode 100644 index 04001d6..0000000 --- a/scripts/release_plan.py +++ /dev/null @@ -1,4196 +0,0 @@ -#!/usr/bin/env python3 -"""Validate, record, discover, and observe immutable public release plans.""" - -from __future__ import annotations - -import argparse -import datetime as dt -import hashlib -import json -import os -import re -import shutil -import subprocess -import sys -import tempfile -import tomllib -import urllib.error -import urllib.parse -from pathlib import Path -from typing import Any - -# GitHub Actions invokes this file directly from the repository root. In that -# mode Python adds scripts/, rather than the repository root, to sys.path. -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts import component_release_recovery as recovery_discovery -from scripts.beta_candidate import ( - COMPONENTS, - INFRASTRUCTURE_EXIT_CODE, - VERIFIERS, - CandidateError, - PublicClient, - PublicInfrastructureError, - canonical_cli_embedded_identity, - canonical_json, - fetch_existing_record, - load_manifest, - load_verification, - manifest_digest, - read_record_file, - resolve_github_tag, - revalidate_verification, - run_git, - validate_recorded_verification, - verify_candidate, - verify_github_release, - write_github_output, -) -from scripts.beta_candidate import ( - LEGACY_SCHEMA as LEGACY_CANDIDATE_SCHEMA, -) -from scripts.beta_candidate import ( - SCHEMA as CANDIDATE_SCHEMA, -) -from scripts.product_train import require_current_product_train -from scripts.recovery_workflow_authority import ( - SOURCE_IDENTITIES_PATH, - RecoveryWorkflowAuthorityError, - decode_authority, - decode_source_identities, - load_qualified_authority, - verify_authority_source_identities, - verify_workflow_source, -) - -SCHEMA = "durable-workflow.release-plan/v2" -LEGACY_SCHEMA = "durable-workflow.release-plan/v1" -LEGACY_PLAN_DIGESTS = recovery_discovery.LEGACY_PLAN_DIGESTS -PREPARATION_SCHEMA = "durable-workflow.release-preparation/v1" -SOURCE_PREPARATION_SCHEMA = "durable-workflow.release-source-preparation/v1" -PLAN_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,55}$") -VERSION_PATTERN = recovery_discovery.VERSION_PATTERN -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -ALPHA_VERSION_PATTERN = re.compile(r"^2\.0\.0-alpha\.[1-9][0-9]*$") -BETA_VERSION_PATTERN = re.compile(r"^2\.0\.0-beta\.[1-9][0-9]*$") -RC_VERSION_PATTERN = re.compile(r"^2\.0\.0-rc\.[1-9][0-9]*$") -PLAN_TAG_PREFIX = "release-plan/" -COMPLETION_TAG_PREFIX = "release-candidate/" -FAILURE_TAG_PREFIX = "release-plan-failure/" -CONTINUITY_TAG_PREFIX = "beta-continuity/" -CONTINUITY_EVIDENCE_SCHEMA = "durable-workflow.beta-continuity.evidence/v1" -CONTINUITY_SUPERSESSION_REASON = "missing-post-acceptance-publication-trigger" -CONTINUITY_RESOLUTION_TAG_PREFIX = "release-plan-continuity-resolution/" -CONTINUITY_RESOLUTION_SCHEMA = recovery_discovery.CONTINUITY_RESOLUTION_SCHEMA -FOUNDATION_TAG = "beta-candidate/beta-continuity-foundation" -FOUNDATION_COMMIT = "4995052410bd4301c5796ffba54e0b6d2f490ed1" -CONTROL_REPOSITORY = "durable-workflow/.github" -SUPERSESSION_ENVIRONMENT = "release-plan-supersession" -SUPERSESSION_WORKFLOW = ".github/workflows/release-plan-supersession.yml" -SUPERSESSION_REASON = "published-version-source-conflict" -SOURCE_MANIFEST_REASON = "source-manifest-version-conflict" -OCCUPIED_SOURCE_MANIFEST_REASON = "occupied-source-manifest-version-conflict" -SUPERSESSION_API_VERSION = "2026-03-10" -SUPERSESSION_ENVIRONMENT_URL = ( - f"https://github.com/{CONTROL_REPOSITORY}/deployments/activity_log?environments_filter={SUPERSESSION_ENVIRONMENT}" -) -SUPERSESSION_ENVIRONMENT_API_URL = ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{SUPERSESSION_ENVIRONMENT}" -) -OBSERVATION_MAX_BYTES = 256 * 1024 -OBSERVATION_MAX_TEXT = 4096 -OBSERVATION_MAX_ITEMS = 64 -OBSERVATION_MAX_DEPTH = 12 -OBSERVATION_FAILURE_REASON = ( - "Public artifact verification failed; inspect the read-only observer run for component diagnostics" -) -OBSERVATION_RECOVERY_ACTION = ( - "Run the affected component's Release plan recovery action, then rerun Release plan observer" -) - -EXPECTED_DEFAULT_BRANCHES = { - "workflow": "v2", - "waterline": "v2", - "server": "main", - "cli": "main", - "sdk-php": "main", - "sdk-python": "main", - "sdk-rust": "main", -} - -SOURCE_MANIFESTS = { - "sdk-python": { - "path": "pyproject.toml", - "package": "durable-workflow", - "table": "project", - }, - "sdk-rust": { - "path": "Cargo.toml", - "package": "durable-workflow", - "table": "package", - }, -} - -SOURCE_CHANGELOGS = { - "workflow": "CHANGELOG.md", - "waterline": "CHANGELOG.md", - "sdk-php": "CHANGELOG.md", - "sdk-python": "CHANGELOG.md", -} - -SOURCE_PREPARATION_PATH = Path(__file__).resolve().parent.parent / "release-plans" / "current-source-preparation.json" -RELEASE_CANDIDATE_SOURCE_PREPARATION_PATH = ( - Path(__file__).resolve().parent.parent / "release-plans" / "first-release-candidate-source-preparation.json" -) -CURRENT_PLAN_PATH = Path(__file__).resolve().parent.parent / "release-plans" / "current.json" -CURRENT_CANDIDATE_PATH = Path(__file__).resolve().parent.parent / "candidates" / "main.json" -CURRENT_AUTHORIZATION_DATE = "2026-07-25T00:00:00Z" - -MARKDOWN_MEDIA_TYPE = "text/markdown" - - -def load_recovery_workflow_authority( - client: PublicClient, -) -> tuple[dict[str, dict[str, str]], dict[str, Any]]: - identities = { - name: (component.repository, EXPECTED_DEFAULT_BRANCHES[name]) - for name, component in COMPONENTS.items() - } - try: - return load_qualified_authority(client, identities) - except RecoveryWorkflowAuthorityError as error: - raise CandidateError(f"invalid component release recovery authority: {error}") from error - - -def verify_local_recovery_workflow_authority( - path: Path, - client: PublicClient, - *, - source_identities_path: Path = Path(SOURCE_IDENTITIES_PATH), -) -> dict[str, Any]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read recovery workflow authority {path}: {error}") from error - if len(raw) > 64 * 1024: - raise CandidateError("recovery workflow authority exceeds the 64 KiB limit") - identities = { - name: (component.repository, EXPECTED_DEFAULT_BRANCHES[name]) - for name, component in COMPONENTS.items() - } - try: - workflows = decode_authority(raw, identities) - source_identities_raw = source_identities_path.read_bytes() - source_identities = decode_source_identities( - source_identities_raw, - workflows, - identities, - ) - protected_sources = verify_authority_source_identities( - client, - workflows, - source_identities, - ) - except (OSError, json.JSONDecodeError, RecoveryWorkflowAuthorityError) as error: - raise CandidateError(f"invalid component release recovery authority: {error}") from error - return { - "schema": "durable-workflow.component-release-recovery-authority-verification/v1", - "authority_sha256": hashlib.sha256(raw).hexdigest(), - "protected_source_identities_sha256": hashlib.sha256(source_identities_raw).hexdigest(), - "outcome": "verified", - "protected_sources": protected_sources, - "workflows": { - name: { - field: evidence[field] - for field in ("repository", "ref", "path", "state", "sha256", "workflow_id", "url") - } - for name, evidence in protected_sources.items() - }, - } - - -def load_plan(path: Path, *, require_current: bool = False) -> dict[str, Any]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read release plan {path}: {error}") from error - if len(raw) > 64 * 1024: - raise CandidateError("release plan exceeds the 64 KiB limit") - try: - plan = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"release plan is not valid JSON: {error}") from error - validate_plan(plan) - if require_current: - require_current_product_train(plan["components"]) - return plan - - -def validate_plan(plan: Any) -> None: - _validate_plan(plan, {SCHEMA}) - - -def validate_recorded_plan(plan: Any) -> None: - """Validate a current plan or an exact plan recorded under the v1 contract.""" - _validate_plan(plan, {LEGACY_SCHEMA, SCHEMA}) - if plan["schema"] == LEGACY_SCHEMA and manifest_digest(plan) not in LEGACY_PLAN_DIGESTS: - raise CandidateError("legacy release plan is not an exact recorded historical contract") - - -def _validate_plan(plan: Any, schemas: set[str]) -> None: - if not isinstance(plan, dict): - raise CandidateError("release plan must be a JSON object") - expected = {"schema", "plan", "channel", "foundation", "components", "beta_authorization"} - if set(plan) != expected: - raise CandidateError(f"release plan keys must be exactly {sorted(expected)}") - if plan["schema"] not in schemas: - raise CandidateError(f"release plan schema must be one of {sorted(schemas)}") - if not isinstance(plan["plan"], str) or not PLAN_PATTERN.fullmatch(plan["plan"]): - raise CandidateError("plan must be 1-56 lowercase letters, digits, dots, underscores, or hyphens") - if plan["channel"] not in {"alpha", "beta", "rc"}: - raise CandidateError("release channel must be alpha, beta, or rc") - foundation = plan["foundation"] - legacy_foundation = {"tag": FOUNDATION_TAG, "commit": FOUNDATION_COMMIT} - aggregate_rc_foundation = ( - plan["channel"] == "rc" - and isinstance(foundation, dict) - and set(foundation) == {"tag", "commit"} - and foundation.get("tag") == f"beta-candidate/rc-{plan['plan']}" - and COMMIT_PATTERN.fullmatch(str(foundation.get("commit", ""))) is not None - ) - if foundation != legacy_foundation and not aggregate_rc_foundation: - raise CandidateError("release plan must name its proven immutable candidate foundation") - - components = plan["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise CandidateError(f"components must be exactly {sorted(COMPONENTS)}") - for name, identity in components.items(): - if not isinstance(identity, dict) or set(identity) != {"version", "commit"}: - raise CandidateError(f"components.{name} must contain only version and commit") - if not isinstance(identity["version"], str) or recovery_discovery.parse_semver(identity["version"]) is None: - raise CandidateError(f"components.{name}.version must be an exact SemVer release") - if not isinstance(identity["commit"], str) or not COMMIT_PATTERN.fullmatch(identity["commit"]): - raise CandidateError(f"components.{name}.commit must be a full lowercase Git commit identity") - - prerelease_pattern = { - "alpha": ALPHA_VERSION_PATTERN, - "beta": BETA_VERSION_PATTERN, - "rc": RC_VERSION_PATTERN, - }[plan["channel"]] - channel_components = COMPONENTS if plan["channel"] == "rc" else ("workflow", "waterline") - for component in channel_components: - version = components[component]["version"] - if not prerelease_pattern.fullmatch(version): - raise CandidateError(f"{component} version {version} is not an exact 2.0.0-{plan['channel']}.N identity") - - authorization = plan["beta_authorization"] - if plan["channel"] == "alpha": - if authorization is not None: - raise CandidateError("alpha release plans must not claim beta authorization") - elif aggregate_rc_foundation: - if authorization is not None: - raise CandidateError("aggregate release-candidate plans must not claim beta authorization") - elif ( - not isinstance(authorization, dict) - or set(authorization) != {"tag", "commit"} - or not re.fullmatch(r"beta-authorization/[a-z0-9][a-z0-9._-]{0,55}", str(authorization.get("tag", ""))) - or not COMMIT_PATTERN.fullmatch(str(authorization.get("commit", ""))) - ): - raise CandidateError( - "beta and release-candidate plans require an immutable beta qualification tag and commit" - ) - - -def load_source_preparation(path: Path = SOURCE_PREPARATION_PATH) -> dict[str, Any]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read release source preparation {path}: {error}") from error - if len(raw) > 64 * 1024: - raise CandidateError("release source preparation exceeds the 64 KiB limit") - try: - preparation = json.loads(raw) - except (UnicodeDecodeError, json.JSONDecodeError) as error: - raise CandidateError(f"release source preparation is not valid JSON: {error}") from error - validate_source_preparation(preparation) - return preparation - - -def validate_source_preparation(preparation: Any) -> None: - expected = { - "$schema", - "schema", - "plan", - "channel", - "train", - "status", - "components", - "authorization", - } - if not isinstance(preparation, dict) or set(preparation) != expected: - raise CandidateError("release source preparation has an invalid top-level shape") - if ( - preparation["$schema"] != "./source-preparation-schema.json" - or preparation["schema"] != SOURCE_PREPARATION_SCHEMA - or preparation["channel"] not in {"beta", "rc"} - or preparation["status"] != "source-prepared" - or not isinstance(preparation["plan"], str) - or not PLAN_PATTERN.fullmatch(preparation["plan"]) - ): - raise CandidateError("release source preparation has an invalid identity") - expected_producer = ( - "protected-beta-authorization" - if preparation["channel"] == "beta" - else "protected-release-candidate-authorization" - ) - if preparation["authorization"] != { - "state": "required-after-source-landing", - "producer": expected_producer, - }: - raise CandidateError("release source preparation must retain the protected authorization boundary") - - components = preparation["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise CandidateError(f"release source preparation components must be exactly {sorted(COMPONENTS)}") - version_pattern = BETA_VERSION_PATTERN if preparation["channel"] == "beta" else RC_VERSION_PATTERN - product_components: dict[str, dict[str, str]] = {} - for name, identity in components.items(): - if not isinstance(identity, dict) or set(identity) != {"version", "commit", "release_notes"}: - raise CandidateError(f"release source preparation component {name} has an invalid shape") - version = identity["version"] - commit = identity["commit"] - if ( - not isinstance(version, str) - or not version_pattern.fullmatch(version) - or not isinstance(commit, str) - or not COMMIT_PATTERN.fullmatch(commit) - ): - raise CandidateError(f"release source preparation component {name} has an invalid identity") - notes = identity["release_notes"] - expected_kind = "changelog-unreleased" if name in SOURCE_CHANGELOGS else "source-commit-message" - expected_note_keys = {"kind", "sha256", "path"} if name in SOURCE_CHANGELOGS else {"kind", "sha256"} - if ( - not isinstance(notes, dict) - or set(notes) != expected_note_keys - or notes.get("kind") != expected_kind - or not re.fullmatch(r"[0-9a-f]{64}", str(notes.get("sha256", ""))) - or (name in SOURCE_CHANGELOGS and notes.get("path") != SOURCE_CHANGELOGS[name]) - ): - raise CandidateError(f"release source preparation component {name} has invalid note authority") - product_components[name] = {"version": version, "commit": commit} - - if preparation["channel"] == "beta": - current = require_current_product_train(product_components) - if preparation["train"] != current: - raise CandidateError("release source preparation does not identify the supported product train") - elif not RC_VERSION_PATTERN.fullmatch(str(preparation["train"])): - raise CandidateError("release source preparation does not identify one coherent release-candidate train") - - -def require_current_source_preparation(plan: dict[str, Any]) -> dict[str, Any] | None: - if plan["channel"] == "alpha": - return None - current_preparation = load_source_preparation(SOURCE_PREPARATION_PATH) - preparation_path = SOURCE_PREPARATION_PATH - if plan["channel"] == "rc" and current_preparation["plan"] != plan["plan"]: - preparation_path = RELEASE_CANDIDATE_SOURCE_PREPARATION_PATH - preparation = load_source_preparation(preparation_path) - if plan["plan"] != preparation["plan"]: - raise CandidateError( - f"{plan['channel']} release plan {plan['plan']} does not match prepared source plan " - f"{preparation['plan']}" - ) - expected_components = { - name: {"version": identity["version"], "commit": identity["commit"]} - for name, identity in preparation["components"].items() - } - if plan["components"] != expected_components: - raise CandidateError( - f"{plan['channel']} release plan does not match the exact prepared seven-component source tuple" - ) - authorization = plan["beta_authorization"] - if plan["channel"] == "beta" and authorization["tag"] != f"beta-authorization/{preparation['plan']}": - raise CandidateError("beta release plan authorization does not match the prepared source plan") - if ( - plan["channel"] == "rc" - and plan["foundation"] == {"tag": FOUNDATION_TAG, "commit": FOUNDATION_COMMIT} - and not isinstance(authorization, dict) - ): - raise CandidateError("historical release-candidate plan does not retain beta qualification") - if ( - plan["channel"] == "rc" - and plan["foundation"] != {"tag": FOUNDATION_TAG, "commit": FOUNDATION_COMMIT} - and authorization is not None - ): - raise CandidateError("aggregate release-candidate plan must use its exact candidate foundation") - return preparation - - -def require_prepared_note_sources( - source_preparation: dict[str, Any], - release_preparation: dict[str, Any], -) -> None: - for name, prepared_identity in source_preparation["components"].items(): - expected = prepared_identity["release_notes"] - actual = release_preparation["components"][name]["release_notes"]["source"] - if actual["kind"] != expected["kind"] or actual["sha256"] != expected["sha256"]: - raise CandidateError(f"{name} release notes differ from the prepared source authority") - - -def resolve_tag(client: PublicClient, repository: str, tag: str) -> str | None: - encoded = urllib.parse.quote(tag, safe="") - url = f"https://api.github.com/repos/{repository}/git/ref/tags/{encoded}" - try: - ref = client.json(url) - except CandidateError as error: - if "(404)" in str(error): - return None - raise - target = ref.get("object", {}) - seen: set[str] = set() - while target.get("type") == "tag": - sha = target.get("sha") - if not isinstance(sha, str) or sha in seen: - raise CandidateError(f"invalid annotated tag chain for {repository}@{tag}") - seen.add(sha) - target = client.json(f"https://api.github.com/repos/{repository}/git/tags/{sha}").get("object", {}) - if target.get("type") != "commit" or not COMMIT_PATTERN.fullmatch(str(target.get("sha", ""))): - raise CandidateError(f"tag {repository}@{tag} does not resolve to a commit") - return str(target["sha"]) - - -def read_public_record(client: PublicClient, tag: str, commit: str, filename: str) -> Any: - resolved = resolve_tag(client, CONTROL_REPOSITORY, tag) - if resolved != commit: - raise CandidateError(f"public record {tag} resolves to {resolved or 'no commit'}, not {commit}") - encoded_name = urllib.parse.quote(filename, safe="/") - raw = client.bytes( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/contents/{encoded_name}?ref={commit}", - accept="application/vnd.github.raw+json", - ) - try: - return json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"public record {tag}:{filename} is not valid JSON") from error - - -def verify_candidate_foundation(client: PublicClient, plan: dict[str, Any]) -> None: - foundation = plan["foundation"] - if foundation["tag"] != FOUNDATION_TAG: - foundation_commit = resolve_tag(client, CONTROL_REPOSITORY, foundation["tag"]) - if foundation_commit != foundation["commit"]: - raise CandidateError("immutable candidate foundation tag does not match its pinned commit") - manifest = read_public_record( - client, - foundation["tag"], - foundation["commit"], - "candidate.json", - ) - if foundation["tag"] == FOUNDATION_TAG: - if manifest.get("candidate") != "beta-continuity-foundation": - raise CandidateError("immutable candidate foundation has an unexpected identity") - return - expected = candidate_manifest(plan) - if canonical_json(manifest) != canonical_json(expected): - raise CandidateError("aggregate release-candidate foundation names a different exact tuple") - verification = read_public_record( - client, - foundation["tag"], - foundation["commit"], - "verification.json", - ) - validate_recorded_verification(verification, manifest) - - -def verify_beta_authorization(client: PublicClient, plan: dict[str, Any]) -> None: - authorization = plan["beta_authorization"] - if authorization is None: - return - record = read_public_record(client, authorization["tag"], authorization["commit"], "beta-authorization.json") - if plan.get("channel", "beta") == "beta": - expected = { - "schema": "durable-workflow.beta-authorization/v1", - "channel": "beta", - "candidate": plan["plan"], - "components": plan["components"], - } - if record != expected: - raise CandidateError("beta authorization does not name the same candidate and seven-component tuple") - return - candidate = record.get("candidate") if isinstance(record, dict) else None - components = record.get("components") if isinstance(record, dict) else None - if ( - not isinstance(record, dict) - or set(record) != {"schema", "channel", "candidate", "components"} - or record.get("schema") != "durable-workflow.beta-authorization/v1" - or record.get("channel") != "beta" - or not isinstance(candidate, str) - or authorization["tag"] != f"beta-authorization/{candidate}" - or not isinstance(components, dict) - or set(components) != set(COMPONENTS) - or any( - not isinstance(identity, dict) - or set(identity) != {"version", "commit"} - or BETA_VERSION_PATTERN.fullmatch(str(identity.get("version", ""))) is None - or COMMIT_PATTERN.fullmatch(str(identity.get("commit", ""))) is None - for identity in components.values() - ) - or len({identity["version"] for identity in components.values()}) != 1 - ): - raise CandidateError("beta qualification does not authorize this prerelease transition") - - -def beta_authorization_manifest(plan: dict[str, Any]) -> dict[str, Any]: - validate_plan(plan) - if plan["channel"] != "beta": - raise CandidateError("current release-plan authorization requires the beta channel") - return { - "schema": "durable-workflow.beta-authorization/v1", - "channel": "beta", - "candidate": plan["plan"], - "components": plan["components"], - } - - -def validate_current_plan_authority( - plan_path: Path = CURRENT_PLAN_PATH, - candidate_path: Path = CURRENT_CANDIDATE_PATH, -) -> tuple[dict[str, Any], dict[str, Any]]: - plan, expected_candidate = materialize_current_plan_authority(plan_path) - candidate = load_manifest(candidate_path) - if canonical_json(candidate) != canonical_json(expected_candidate): - raise CandidateError("current candidate does not match the exact current release plan") - return plan, candidate - - -def materialize_current_plan_authority( - plan_path: Path = CURRENT_PLAN_PATH, -) -> tuple[dict[str, Any], dict[str, Any]]: - plan = load_plan(plan_path) - require_current_product_train(plan["components"]) - require_current_source_preparation(plan) - authorization = plan["beta_authorization"] - if plan["channel"] == "beta": - if authorization["tag"] != f"beta-authorization/{plan['plan']}": - raise CandidateError("current release plan has a mismatched authorization tag") - elif ( - plan["channel"] != "rc" - or authorization is not None - or plan["foundation"]["tag"] != f"beta-candidate/rc-{plan['plan']}" - ): - raise CandidateError("current release-candidate plan has a mismatched candidate foundation") - return plan, candidate_manifest(plan) - - -def record_current_plan_authorization( - repository: Path, - plan_path: Path, - *, - remote: str, - authoritative_authorization: Path, -) -> dict[str, str]: - plan, _candidate = materialize_current_plan_authority(plan_path) - authorization = beta_authorization_manifest(plan) - canonical = canonical_json(authorization) - identity = plan["beta_authorization"] - tag = identity["tag"] - expected_commit = identity["commit"] - existing_ref = fetch_existing_record(repository, remote, tag) - if existing_ref: - existing = read_record_file(repository, existing_ref, "beta-authorization.json") - commit = run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository) - if existing != canonical or commit != expected_commit: - raise CandidateError(f"beta authorization {tag} is immutable and differs") - authoritative_authorization.write_bytes(existing) - return {"status": "existing", "tag": tag, "commit": commit} - - with tempfile.NamedTemporaryFile(prefix="current-plan-authorization-index-", delete=False) as index: - index_path = Path(index.name) - try: - env = os.environ.copy() - env["GIT_INDEX_FILE"] = str(index_path) - index_path.unlink(missing_ok=True) - run_git(["read-tree", "--empty"], cwd=repository, env=env) - blob = ( - subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=repository, - env=env, - input=canonical, - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - run_git( - ["update-index", "--add", "--cacheinfo", f"100644,{blob},beta-authorization.json"], - cwd=repository, - env=env, - ) - tree = run_git(["write-tree"], cwd=repository, env=env) - commit_env = env | { - "GIT_AUTHOR_NAME": "Durable Workflow Product Train", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Product Train", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - "GIT_AUTHOR_DATE": CURRENT_AUTHORIZATION_DATE, - "GIT_COMMITTER_DATE": CURRENT_AUTHORIZATION_DATE, - } - commit = subprocess.run( - ["git", "commit-tree", tree], - cwd=repository, - env=commit_env, - input=f"Authorize completed product train {plan['plan']}\n", - check=True, - text=True, - capture_output=True, - ).stdout.strip() - if commit != expected_commit: - raise CandidateError( - f"current release plan expects beta authorization {expected_commit}, generated {commit}" - ) - process = subprocess.run( - ["git", "push", remote, f"{commit}:refs/tags/{tag}"], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if process.returncode: - existing_ref = fetch_existing_record(repository, remote, tag) - if ( - not existing_ref - or run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository) != expected_commit - or read_record_file(repository, existing_ref, "beta-authorization.json") != canonical - ): - raise CandidateError(f"cannot publish current beta authorization: {process.stderr.strip()}") - authoritative_authorization.write_bytes(canonical) - return {"status": "existing", "tag": tag, "commit": expected_commit} - authoritative_authorization.write_bytes(canonical) - return {"status": "created", "tag": tag, "commit": commit} - finally: - index_path.unlink(missing_ok=True) - - -def sha256_bytes(value: bytes) -> str: - return hashlib.sha256(value).hexdigest() - - -def parse_release_date(value: str) -> str: - try: - parsed = dt.date.fromisoformat(value) - except (TypeError, ValueError) as error: - raise CandidateError("release preparation date must be an exact YYYY-MM-DD date") from error - if parsed.isoformat() != value: - raise CandidateError("release preparation date must be an exact YYYY-MM-DD date") - return value - - -def unreleased_changelog_body(raw: bytes, component_name: str) -> str: - if len(raw) > 1024 * 1024: - raise CandidateError(f"{component_name} CHANGELOG.md exceeds the 1 MiB preparation limit") - try: - source = raw.decode("utf-8").replace("\r\n", "\n") - except UnicodeDecodeError as error: - raise CandidateError(f"{component_name} CHANGELOG.md is not valid UTF-8") from error - heading = re.search(r"(?m)^## \[?Unreleased\]?\s*$", source) - if heading is None: - raise CandidateError(f"{component_name} CHANGELOG.md has no unique Unreleased section") - if re.search(r"(?m)^## \[?Unreleased\]?\s*$", source[heading.end() :]): - raise CandidateError(f"{component_name} CHANGELOG.md has no unique Unreleased section") - following = re.search(r"(?m)^##\s+", source[heading.end() :]) - end = heading.end() + following.start() if following else len(source) - body = source[heading.end() : end].strip() - if not body: - raise CandidateError(f"{component_name} CHANGELOG.md Unreleased section is empty") - return body - - -def component_release_notes( - plan: dict[str, Any], - component_name: str, - client: PublicClient, - release_date: str, -) -> dict[str, Any]: - component = COMPONENTS[component_name] - identity = plan["components"][component_name] - if component_name in SOURCE_CHANGELOGS: - path = SOURCE_CHANGELOGS[component_name] - encoded_path = urllib.parse.quote(path, safe="/") - raw = client.bytes( - f"https://api.github.com/repos/{component.repository}/contents/{encoded_path}?ref={identity['commit']}", - accept="application/vnd.github.raw+json", - ) - body = unreleased_changelog_body(raw, component_name) - source = { - "kind": "changelog-unreleased", - "sha256": sha256_bytes(raw), - "url": f"https://github.com/{component.repository}/blob/{identity['commit']}/{path}", - } - else: - commit = client.json(f"https://api.github.com/repos/{component.repository}/commits/{identity['commit']}") - message = commit.get("commit", {}).get("message") if isinstance(commit, dict) else None - if not isinstance(message, str) or not message.strip(): - raise CandidateError(f"{component_name} source commit has no public release-note summary") - body = message.strip().replace("\r\n", "\n") - source = { - "kind": "source-commit-message", - "sha256": sha256_bytes(body.encode()), - "url": f"https://github.com/{component.repository}/commit/{identity['commit']}", - } - heading = f"## [{identity['version']}] - {release_date}" - markdown = f"{heading}\n\n{body}\n" - return { - "format": MARKDOWN_MEDIA_TYPE, - "heading": heading, - "markdown": markdown, - "release_date": release_date, - "sha256": sha256_bytes(markdown.encode()), - "source": source, - } - - -def prepare_release(plan: dict[str, Any], client: PublicClient, release_date: str) -> dict[str, Any]: - validate_plan(plan) - release_date = parse_release_date(release_date) - preparation = { - "schema": PREPARATION_SCHEMA, - "release_plan": { - "tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "sha256": manifest_digest(plan), - }, - "components": { - name: { - "version": plan["components"][name]["version"], - "source_commit": plan["components"][name]["commit"], - "release_notes": component_release_notes(plan, name, client, release_date), - } - for name in COMPONENTS - }, - } - validate_release_preparation(preparation, plan) - return preparation - - -def validate_release_preparation(preparation: Any, plan: dict[str, Any]) -> None: - validate_recorded_plan(plan) - if not isinstance(preparation, dict) or set(preparation) != { - "schema", - "release_plan", - "components", - }: - raise CandidateError("release preparation has an invalid top-level shape") - if preparation["schema"] != PREPARATION_SCHEMA: - raise CandidateError(f"release preparation schema must be {PREPARATION_SCHEMA}") - expected_plan = { - "tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "sha256": manifest_digest(plan), - } - if preparation["release_plan"] != expected_plan: - raise CandidateError("release preparation names a different immutable release plan") - components = preparation["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise CandidateError(f"release preparation components must be exactly {sorted(COMPONENTS)}") - for name, entry in components.items(): - identity = plan["components"][name] - if not isinstance(entry, dict) or set(entry) != { - "version", - "source_commit", - "release_notes", - }: - raise CandidateError(f"release preparation component {name} has an invalid shape") - if entry["version"] != identity["version"] or entry["source_commit"] != identity["commit"]: - raise CandidateError(f"release preparation component {name} names a different planned identity") - notes = entry["release_notes"] - if not isinstance(notes, dict) or set(notes) != { - "format", - "heading", - "markdown", - "release_date", - "sha256", - "source", - }: - raise CandidateError(f"release preparation component {name} has invalid release notes") - release_date = parse_release_date(notes["release_date"]) - expected_heading = f"## [{identity['version']}] - {release_date}" - if notes["format"] != MARKDOWN_MEDIA_TYPE or notes["heading"] != expected_heading: - raise CandidateError(f"release preparation component {name} has a mismatched versioned heading") - markdown = notes["markdown"] - if ( - not isinstance(markdown, str) - or not markdown.startswith(f"{expected_heading}\n\n") - or not markdown.endswith("\n") - or notes["sha256"] != sha256_bytes(markdown.encode()) - ): - raise CandidateError(f"release preparation component {name} has mismatched note content") - source = notes["source"] - expected_kind = "changelog-unreleased" if name in SOURCE_CHANGELOGS else "source-commit-message" - expected_source_url = ( - f"https://github.com/{COMPONENTS[name].repository}/blob/{identity['commit']}/{SOURCE_CHANGELOGS[name]}" - if name in SOURCE_CHANGELOGS - else f"https://github.com/{COMPONENTS[name].repository}/commit/{identity['commit']}" - ) - if ( - not isinstance(source, dict) - or set(source) != {"kind", "sha256", "url"} - or source["kind"] != expected_kind - or not re.fullmatch(r"[0-9a-f]{64}", str(source["sha256"])) - or source["url"] != expected_source_url - ): - raise CandidateError(f"release preparation component {name} has invalid note-source evidence") - - -def revalidate_release_preparation(preparation: dict[str, Any], plan: dict[str, Any], client: PublicClient) -> None: - validate_release_preparation(preparation, plan) - dates = {entry["release_notes"]["release_date"] for entry in preparation["components"].values()} - if len(dates) != 1: - raise CandidateError("release preparation components do not share one release date") - expected = prepare_release(plan, client, dates.pop()) - if canonical_json(preparation) != canonical_json(expected): - raise CandidateError("release preparation no longer matches its immutable source evidence") - - -is_immediate_version_successor = recovery_discovery.is_immediate_version_successor - - -def conflict_component_names(conflicts: Any) -> list[str]: - if isinstance(conflicts, str): - names = [conflicts] - elif isinstance(conflicts, list): - names = [conflict.get("component") if isinstance(conflict, dict) else conflict for conflict in conflicts] - else: - raise CandidateError("release plan failure conflicts must be a non-empty list") - if ( - not names - or any(not isinstance(name, str) or name not in COMPONENTS for name in names) - or len(names) != len(set(names)) - ): - raise CandidateError(f"conflicting components must be unique names from {sorted(COMPONENTS)}") - expected_order = [name for name in COMPONENTS if name in names] - if names != expected_order: - raise CandidateError("conflicting components must follow release-plan component order") - return names - - -def parse_conflict_components(value: str) -> list[str]: - names = [name.strip() for name in value.split(",") if name.strip()] - return conflict_component_names(names) - - -def validate_successor_transition( - failed_plan: dict[str, Any], successor_plan: dict[str, Any], conflicts: str | list[Any] -) -> None: - validate_recorded_plan(failed_plan) - validate_recorded_plan(successor_plan) - conflict_names = conflict_component_names(conflicts) - if successor_plan["plan"] == failed_plan["plan"]: - raise CandidateError("a superseding release plan must use a new plan identity") - if successor_plan["channel"] != failed_plan["channel"]: - raise CandidateError("a superseding release plan cannot change the release channel") - if successor_plan["foundation"] != failed_plan["foundation"]: - raise CandidateError("a superseding release plan cannot change the candidate foundation") - for name, identity in failed_plan["components"].items(): - successor_identity = successor_plan["components"][name] - if name not in conflict_names and successor_identity != identity: - raise CandidateError(f"superseding release plan changes unaffected component {name}") - for name in conflict_names: - failed_identity = failed_plan["components"][name] - successor_identity = successor_plan["components"][name] - if successor_identity == failed_identity: - raise CandidateError(f"superseding release plan leaves conflict unresolved for {name}") - - if isinstance(conflicts, str): - conflict_records: list[Any] = [{"component": conflicts, "reason": SUPERSESSION_REASON}] - else: - conflict_records = conflicts - if not all(isinstance(conflict, dict) for conflict in conflict_records): - return - for conflict in conflict_records: - name = conflict["component"] - failed_identity = failed_plan["components"][name] - successor_identity = successor_plan["components"][name] - if conflict.get("reason") == SUPERSESSION_REASON: - if successor_identity["commit"] != failed_identity["commit"]: - raise CandidateError(f"superseding release plan must retain {name}'s conflicting planned commit") - if not is_immediate_version_successor(failed_identity["version"], successor_identity["version"]): - raise CandidateError(f"superseding release plan must allocate {name}'s immediate next version") - elif conflict.get("reason") == SOURCE_MANIFEST_REASON: - if successor_identity["version"] != failed_identity["version"]: - raise CandidateError(f"superseding release plan must retain {name}'s intended version") - if successor_identity["commit"] == failed_identity["commit"]: - raise CandidateError(f"superseding release plan must replace {name}'s incompatible source commit") - elif conflict.get("reason") == OCCUPIED_SOURCE_MANIFEST_REASON: - if not is_immediate_version_successor(failed_identity["version"], successor_identity["version"]): - raise CandidateError(f"superseding release plan must allocate {name}'s immediate next version") - if successor_identity["commit"] == failed_identity["commit"]: - raise CandidateError( - f"superseding release plan must replace {name}'s incompatible tagged source commit" - ) - else: - raise CandidateError(f"release plan failure has an unsupported conflict reason for {name}") - - -def validate_environment_protection_evidence(protection: Any) -> None: - expected_keys = { - "custom_branch_policies", - "deployment_branch_policy", - "environment_id", - "environment_url", - "required_reviewer_rule_ids", - } - if not isinstance(protection, dict) or set(protection) != expected_keys: - raise CandidateError("release plan failure environment protection evidence has an invalid shape") - reviewer_rule_ids = protection["required_reviewer_rule_ids"] - branch_policy = protection["deployment_branch_policy"] - custom_policies = protection["custom_branch_policies"] - if ( - type(protection["environment_id"]) is not int - or protection["environment_id"] < 1 - or protection["environment_url"] != SUPERSESSION_ENVIRONMENT_URL - or not isinstance(reviewer_rule_ids, list) - or not reviewer_rule_ids - or any(type(rule_id) is not int or rule_id < 1 for rule_id in reviewer_rule_ids) - or reviewer_rule_ids != sorted(set(reviewer_rule_ids)) - ): - raise CandidateError("release plan failure lacks protected-environment reviewer evidence") - if branch_policy != {"custom_branch_policies": True, "protected_branches": False}: - raise CandidateError("release plan failure lacks the protected environment custom-branch policy") - if ( - not isinstance(custom_policies, list) - or len(custom_policies) != 1 - or not isinstance(custom_policies[0], dict) - or set(custom_policies[0]) != {"id", "name"} - or type(custom_policies[0]["id"]) is not int - or custom_policies[0]["id"] < 1 - or custom_policies[0]["name"] != "main" - ): - raise CandidateError("release plan failure lacks the protected environment custom main-branch policy") - - -def validate_environment_approval_evidence(approval: Any, authorization: dict[str, Any]) -> None: - expected_keys = {"comment", "environments", "run_attempt", "run_id", "state", "user"} - if not isinstance(approval, dict) or set(approval) != expected_keys: - raise CandidateError("release plan failure environment approval evidence has an invalid shape") - environments = approval["environments"] - user = approval["user"] - protection = authorization["environment_protection"] - if ( - approval["state"] != "approved" - or not isinstance(approval["comment"], str) - or approval["run_id"] != authorization["run_id"] - or approval["run_attempt"] != authorization["run_attempt"] - or not isinstance(environments, list) - or len(environments) != 1 - or not isinstance(environments[0], dict) - or set(environments[0]) != {"html_url", "id", "name", "node_id", "url"} - ): - raise CandidateError("release plan failure lacks an approved deployment bound to its workflow run") - environment = environments[0] - if ( - environment["id"] != protection["environment_id"] - or type(environment["id"]) is not int - or environment["name"] != SUPERSESSION_ENVIRONMENT - or environment["url"] != SUPERSESSION_ENVIRONMENT_API_URL - or environment["html_url"] != SUPERSESSION_ENVIRONMENT_URL - or not isinstance(environment["node_id"], str) - or not environment["node_id"] - ): - raise CandidateError("release plan failure approval names the wrong protected environment") - if not isinstance(user, dict) or set(user) != {"html_url", "id", "login", "node_id", "url"}: - raise CandidateError("release plan failure approving user evidence has an invalid shape") - login = user["login"] - if ( - type(user["id"]) is not int - or user["id"] < 1 - or not isinstance(user["node_id"], str) - or not user["node_id"] - or not isinstance(login, str) - or not re.fullmatch(r"[A-Za-z0-9-]{1,39}", login) - or user["url"] != f"https://api.github.com/users/{login}" - or user["html_url"] != f"https://github.com/{login}" - ): - raise CandidateError("release plan failure lacks a durable approving user identity") - - -def validate_source_manifest_evidence( - evidence: Any, - component_name: str, - identity: dict[str, str], - *, - must_match_version: bool, -) -> None: - expected_keys = {"declared_version", "package", "path", "sha256", "source_commit", "url"} - specification = SOURCE_MANIFESTS.get(component_name) - if ( - specification is None - or not isinstance(evidence, dict) - or set(evidence) != expected_keys - or evidence["path"] != specification["path"] - or evidence["package"] != specification["package"] - or evidence["source_commit"] != identity["commit"] - or not re.fullmatch(r"[0-9a-f]{64}", str(evidence["sha256"])) - or not isinstance(evidence["declared_version"], str) - or not VERSION_PATTERN.fullmatch(evidence["declared_version"]) - or evidence["url"] - != ( - f"https://github.com/{COMPONENTS[component_name].repository}/blob/" - f"{identity['commit']}/{specification['path']}" - ) - ): - raise CandidateError(f"release plan failure has invalid source-manifest evidence for {component_name}") - version_matches = evidence["declared_version"] == identity["version"] - if version_matches is not must_match_version: - state = "match" if must_match_version else "conflict with" - raise CandidateError( - f"release plan failure source manifest does not {state} {component_name} version allocation" - ) - - -def publication_absence_locations(component_name: str, version: str) -> tuple[dict[str, str], dict[str, str]]: - component = COMPONENTS[component_name] - encoded_version = urllib.parse.quote(version, safe="") - release = { - "api_url": (f"https://api.github.com/repos/{component.repository}/releases/tags/{encoded_version}"), - "status": "absent", - "url": f"https://github.com/{component.repository}/releases/tag/{encoded_version}", - } - encoded_package = urllib.parse.quote(component.package, safe="") - if component.distribution == "pypi": - distribution = { - "api_url": f"https://pypi.org/pypi/{encoded_package}/{encoded_version}/json", - "kind": "pypi", - "status": "absent", - "url": f"https://pypi.org/project/{encoded_package}/{encoded_version}/", - } - elif component.distribution == "crates.io": - distribution = { - "api_url": f"https://crates.io/api/v1/crates/{encoded_package}/{encoded_version}", - "kind": "crates.io", - "status": "absent", - "url": f"https://crates.io/crates/{encoded_package}/{encoded_version}", - } - else: - raise CandidateError(f"{component_name} has no supported source-manifest distribution absence proof") - return release, distribution - - -def validate_occupied_source_manifest_evidence( - conflict: dict[str, Any], component_name: str, identity: dict[str, str] -) -> None: - component = COMPONENTS[component_name] - source_tag = conflict["source_tag"] - if ( - not isinstance(source_tag, dict) - or set(source_tag) != {"commit", "repository", "tag", "tag_object", "url"} - or source_tag["repository"] != component.repository - or source_tag["tag"] != identity["version"] - or source_tag["commit"] != identity["commit"] - or not COMMIT_PATTERN.fullmatch(str(source_tag["tag_object"])) - or source_tag["url"] != f"https://github.com/{component.repository}/tree/{identity['version']}" - ): - raise CandidateError(f"release plan failure does not prove {component_name}'s occupied planned source tag") - expected_release, expected_distribution = publication_absence_locations(component_name, identity["version"]) - if conflict["github_release"] != expected_release: - raise CandidateError(f"release plan failure lacks {component_name} GitHub Release absence evidence") - if conflict["distribution"] != expected_distribution: - raise CandidateError(f"release plan failure lacks {component_name} distribution absence evidence") - - -def validate_conflict_record( - conflict: Any, - failed_plan: dict[str, Any], - successor_plan: dict[str, Any], -) -> None: - if not isinstance(conflict, dict): - raise CandidateError("release plan failure conflict evidence has an invalid shape") - component_name = conflict.get("component") - if component_name not in COMPONENTS: - raise CandidateError("release plan failure names an unknown conflicting component") - identity = failed_plan["components"][component_name] - successor_identity = successor_plan["components"][component_name] - common_identity_matches = ( - conflict.get("version") == identity["version"] and conflict.get("planned_commit") == identity["commit"] - ) - reason = conflict.get("reason") - if reason == SUPERSESSION_REASON: - expected_keys = { - "component", - "version", - "planned_commit", - "observed_commit", - "reason", - "github_release", - "distribution", - } - if ( - set(conflict) != expected_keys - or not common_identity_matches - or not COMMIT_PATTERN.fullmatch(str(conflict.get("observed_commit", ""))) - or conflict["observed_commit"] == identity["commit"] - ): - raise CandidateError("release plan failure conflict does not prove a different public source identity") - release = conflict["github_release"] - if ( - not isinstance(release, dict) - or set(release) != {"id", "url"} - or type(release["id"]) is not int - or release["id"] < 1 - or not isinstance(release["url"], str) - or not release["url"].startswith(f"https://github.com/{COMPONENTS[component_name].repository}/releases/") - ): - raise CandidateError("release plan failure lacks durable GitHub Release evidence") - distribution = conflict["distribution"] - if not isinstance(distribution, dict) or distribution.get("kind") != COMPONENTS[component_name].distribution: - raise CandidateError("release plan failure lacks matching distribution evidence") - require_distribution_identity( - distribution, - component_name, - conflict["version"], - conflict["observed_commit"], - ) - elif reason == SOURCE_MANIFEST_REASON: - expected_keys = { - "component", - "version", - "planned_commit", - "reason", - "source_manifest", - "successor_source_manifest", - } - if set(conflict) != expected_keys or not common_identity_matches: - raise CandidateError("release plan failure manifest conflict evidence has an invalid shape") - validate_source_manifest_evidence( - conflict["source_manifest"], component_name, identity, must_match_version=False - ) - validate_source_manifest_evidence( - conflict["successor_source_manifest"], - component_name, - successor_identity, - must_match_version=True, - ) - elif reason == OCCUPIED_SOURCE_MANIFEST_REASON: - expected_keys = { - "component", - "version", - "planned_commit", - "reason", - "source_manifest", - "source_tag", - "github_release", - "distribution", - "successor_source_manifest", - } - if set(conflict) != expected_keys or not common_identity_matches: - raise CandidateError("release plan failure occupied manifest conflict evidence has an invalid shape") - validate_source_manifest_evidence( - conflict["source_manifest"], component_name, identity, must_match_version=False - ) - validate_source_manifest_evidence( - conflict["successor_source_manifest"], - component_name, - successor_identity, - must_match_version=True, - ) - validate_occupied_source_manifest_evidence(conflict, component_name, identity) - else: - raise CandidateError(f"release plan failure has an unsupported conflict reason for {component_name}") - - -def validate_supersession_record( - record: Any, - failed_plan: dict[str, Any], - failed_plan_commit: str, - successor_plan: dict[str, Any], -) -> None: - if not isinstance(record, dict): - raise CandidateError("release plan failure record must be a JSON object") - expected = { - "schema", - "outcome", - "failed_plan", - "conflicts", - "successor_plan", - "authorization", - } - if set(record) != expected: - raise CandidateError(f"release plan failure record keys must be exactly {sorted(expected)}") - expected_failed = { - "tag": f"{PLAN_TAG_PREFIX}{failed_plan['plan']}", - "commit": failed_plan_commit, - "sha256": manifest_digest(failed_plan), - } - if record["schema"] != "durable-workflow.release-plan-failure/v1": - raise CandidateError("release plan failure record has an unsupported schema") - if record["outcome"] != "terminal-failure" or record["failed_plan"] != expected_failed: - raise CandidateError("release plan failure record does not terminate this exact immutable plan") - - expected_successor = { - "tag": f"{PLAN_TAG_PREFIX}{successor_plan['plan']}", - "sha256": manifest_digest(successor_plan), - } - if record["successor_plan"] != expected_successor: - raise CandidateError("release plan failure record names a different successor plan") - conflicts = record["conflicts"] - conflict_component_names(conflicts) - for conflict in conflicts: - validate_conflict_record(conflict, failed_plan, successor_plan) - validate_successor_transition(failed_plan, successor_plan, conflicts) - - authorization = record["authorization"] - authorization_keys = { - "actor", - "environment", - "environment_approval", - "environment_protection", - "repository", - "run_attempt", - "run_id", - "run_url", - "workflow_commit", - "workflow_ref", - } - if not isinstance(authorization, dict) or set(authorization) != authorization_keys: - raise CandidateError("release plan failure authorization evidence has an invalid shape") - protection = authorization["environment_protection"] - validate_environment_protection_evidence(protection) - workflow_ref = f"{CONTROL_REPOSITORY}/{SUPERSESSION_WORKFLOW}@refs/heads/main" - if ( - authorization.get("repository") != CONTROL_REPOSITORY - or authorization.get("environment") != SUPERSESSION_ENVIRONMENT - or authorization.get("workflow_ref") != workflow_ref - or not COMMIT_PATTERN.fullmatch(str(authorization.get("workflow_commit", ""))) - or not re.fullmatch(r"[A-Za-z0-9-]{1,39}", str(authorization.get("actor", ""))) - or type(authorization.get("run_id")) is not int - or authorization["run_id"] < 1 - or type(authorization.get("run_attempt")) is not int - or authorization["run_attempt"] != 1 - or authorization.get("run_url") - != f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{authorization.get('run_id')}" - ): - raise CandidateError("release plan failure was not authorized by the protected supersession workflow") - validate_environment_approval_evidence(authorization["environment_approval"], authorization) - - -def require_distribution_identity( - distribution: dict[str, Any], component_name: str, version: str, observed_commit: str -) -> None: - component = COMPONENTS[component_name] - if distribution.get("kind") != component.distribution: - raise CandidateError("public distribution evidence has the wrong kind") - if component.distribution == "composer": - matches = ( - distribution.get("source_reference") == observed_commit - and distribution.get("dist_reference") == observed_commit - ) - elif component.distribution == "github-release": - package_source = distribution.get("package_source") - matches = ( - isinstance(package_source, dict) - and set(package_source) == {"commit", "embedded_phar_identity"} - and package_source.get("commit") == observed_commit - and package_source.get("embedded_phar_identity") - == canonical_cli_embedded_identity(version, observed_commit) - ) - authority = distribution.get("build_attestation_authority") - exact_tag_authority = { - "mode": "exact-tag", - "ref": f"refs/tags/{version}", - "commit": observed_commit, - } - qualified_main_authority = { - "mode": "qualified-main-workflow", - "ref": "refs/heads/main", - "workflow": f"{component.repository}/.github/workflows/release.yml", - } - if distribution.get("build_attestations_verified") is not True or authority not in ( - exact_tag_authority, - qualified_main_authority, - ): - raise CandidateError("public distribution evidence has an untrusted build attestation authority") - elif component.distribution == "pypi": - source = distribution.get("source_identity") - matches = isinstance(source, dict) and source.get("source_commit") == observed_commit - elif component.distribution == "crates.io": - matches = distribution.get("archive_vcs_commit") == observed_commit - else: - configs = distribution.get("configs") - matches = ( - isinstance(configs, list) - and bool(configs) - and all( - isinstance(config, dict) - and isinstance(config.get("labels"), dict) - and config["labels"].get("org.opencontainers.image.revision") == observed_commit - for config in configs - ) - ) - if not matches: - raise CandidateError("public distribution evidence does not bind the observed source commit") - - -def github_release_conflict_evidence( - client: PublicClient, - component_name: str, - version: str, -) -> dict[str, Any]: - component = COMPONENTS[component_name] - encoded_version = urllib.parse.quote(version, safe="") - release = client.json(f"https://api.github.com/repos/{component.repository}/releases/tags/{encoded_version}") - if not isinstance(release, dict) or release.get("draft") or release.get("tag_name") != version: - raise CandidateError(f"{component_name} version {version} has no public GitHub Release conflict") - return { - "id": release.get("id"), - "url": release.get("html_url"), - } - - -def source_manifest_evidence( - client: PublicClient, - component_name: str, - identity: dict[str, str], -) -> dict[str, Any]: - specification = SOURCE_MANIFESTS.get(component_name) - if specification is None: - raise CandidateError(f"{component_name} has no supported source-manifest conflict proof") - encoded_path = urllib.parse.quote(specification["path"], safe="/") - raw = client.bytes( - f"https://api.github.com/repos/{COMPONENTS[component_name].repository}/contents/" - f"{encoded_path}?ref={identity['commit']}", - accept="application/vnd.github.raw+json", - ) - if len(raw) > 1024 * 1024: - raise CandidateError(f"{component_name} source manifest exceeds the 1 MiB evidence limit") - try: - manifest = tomllib.loads(raw.decode("utf-8")) - except (UnicodeDecodeError, tomllib.TOMLDecodeError) as error: - raise CandidateError(f"{component_name} source manifest is not valid UTF-8 TOML") from error - package = manifest.get(specification["table"]) - declared_version = package.get("version") if isinstance(package, dict) else None - declared_package = package.get("name") if isinstance(package, dict) else None - if declared_package != specification["package"] or not isinstance(declared_version, str): - raise CandidateError(f"{component_name} source manifest has no exact package identity") - evidence = { - "declared_version": declared_version, - "package": declared_package, - "path": specification["path"], - "sha256": hashlib.sha256(raw).hexdigest(), - "source_commit": identity["commit"], - "url": ( - f"https://github.com/{COMPONENTS[component_name].repository}/blob/" - f"{identity['commit']}/{specification['path']}" - ), - } - return evidence - - -def conflict_components_from_public_evidence(failed_plan: dict[str, Any], client: PublicClient) -> list[str]: - conflicts = [] - for name in COMPONENTS: - identity = failed_plan["components"][name] - if name in SOURCE_MANIFESTS: - manifest = source_manifest_evidence(client, name, identity) - if manifest["declared_version"] != identity["version"]: - conflicts.append(name) - continue - observed_commit = resolve_tag(client, COMPONENTS[name].repository, identity["version"]) - if observed_commit not in {None, identity["commit"]}: - conflicts.append(name) - return conflicts - - -def revalidate_conflict_public_evidence( - conflict: dict[str, Any], - failed_plan: dict[str, Any], - successor_plan: dict[str, Any], - client: PublicClient, -) -> None: - component_name = conflict["component"] - component = COMPONENTS[component_name] - if conflict["reason"] == SUPERSESSION_REASON: - observed_commit = resolve_tag(client, component.repository, conflict["version"]) - if observed_commit != conflict["observed_commit"]: - raise CandidateError( - f"terminal conflict source tag {component.repository}@{conflict['version']} moved from " - f"{conflict['observed_commit']} to {observed_commit or 'no commit'}" - ) - try: - live_release = github_release_conflict_evidence( - client, - component_name, - conflict["version"], - ) - except CandidateError as error: - raise CandidateError( - f"terminal conflict GitHub Release evidence for {component_name} no longer matches GitHub: {error}" - ) from error - if live_release != conflict["github_release"]: - raise CandidateError( - f"terminal conflict GitHub Release evidence for {component_name} no longer matches GitHub" - ) - try: - with tempfile.TemporaryDirectory(prefix="release-plan-failure-revalidation-") as temporary: - if component.distribution == "github-release": - live_distribution = verify_github_release( - client, - component, - conflict["version"], - conflict["observed_commit"], - Path(temporary), - ) - else: - live_distribution = VERIFIERS[component.distribution]( - client, - component, - conflict["version"], - conflict["observed_commit"], - Path(temporary), - ) - require_distribution_identity( - live_distribution, - component_name, - conflict["version"], - conflict["observed_commit"], - ) - except CandidateError as error: - raise CandidateError( - f"terminal conflict distribution evidence for {component_name} no longer matches its registry: {error}" - ) from error - if live_distribution != conflict["distribution"]: - raise CandidateError( - f"terminal conflict distribution evidence for {component_name} no longer matches its registry" - ) - return - failed_identity = failed_plan["components"][component_name] - successor_identity = successor_plan["components"][component_name] - if conflict["reason"] == SOURCE_MANIFEST_REASON: - source_tag_commit = resolve_tag( - client, - component.repository, - failed_identity["version"], - ) - if source_tag_commit is not None: - raise CandidateError( - f"terminal conflict source tag {component.repository}@{failed_identity['version']} " - f"appeared at {source_tag_commit}" - ) - elif conflict["reason"] == OCCUPIED_SOURCE_MANIFEST_REASON: - source_tag = resolve_github_tag(client, component.repository, failed_identity["version"]) - if source_tag != conflict["source_tag"]: - raise CandidateError( - f"terminal conflict source tag {component.repository}@{failed_identity['version']} moved" - ) - try: - live_release, live_distribution = prove_publication_absence( - client, - component_name, - failed_identity["version"], - ) - except CandidateError as error: - raise CandidateError( - f"terminal conflict publication absence evidence for {component_name} no longer matches " - f"GitHub and its registry: {error}" - ) from error - if live_release != conflict["github_release"] or live_distribution != conflict["distribution"]: - raise CandidateError( - f"terminal conflict publication absence evidence for {component_name} no longer matches " - "GitHub and its registry" - ) - if source_manifest_evidence(client, component_name, failed_identity) != conflict["source_manifest"]: - raise CandidateError(f"terminal conflict source manifest for {component_name} no longer matches GitHub") - if source_manifest_evidence(client, component_name, successor_identity) != conflict["successor_source_manifest"]: - raise CandidateError(f"terminal successor source manifest for {component_name} no longer matches GitHub") - - -def revalidate_supersession_public_evidence( - record: dict[str, Any], - failed_plan: dict[str, Any], - successor_plan: dict[str, Any], - client: PublicClient, -) -> None: - for conflict in record["conflicts"]: - revalidate_conflict_public_evidence(conflict, failed_plan, successor_plan, client) - component_name = conflict["component"] - component = COMPONENTS[component_name] - successor_identity = successor_plan["components"][component_name] - existing_successor_version = resolve_tag( - client, - component.repository, - successor_identity["version"], - ) - if existing_successor_version not in {None, successor_identity["commit"]}: - raise CandidateError( - f"successor version {component.repository}@{successor_identity['version']} already points to " - f"{existing_successor_version}" - ) - - revalidate_supersession_authority(record, client, require_success=False) - - -def load_public_supersession( - failed_plan: dict[str, Any], failed_plan_commit: str, client: PublicClient -) -> tuple[str, str, dict[str, Any], dict[str, Any]] | None: - tag = f"{FAILURE_TAG_PREFIX}{failed_plan['plan']}" - commit = resolve_tag(client, CONTROL_REPOSITORY, tag) - if commit is None: - return None - record = read_public_record(client, tag, commit, "release-plan-failure.json") - successor = read_public_record(client, tag, commit, "successor-release-plan.json") - validate_recorded_plan(successor) - validate_supersession_record(record, failed_plan, failed_plan_commit, successor) - revalidate_supersession_authority(record, client, require_success=True) - return tag, commit, record, successor - - -def validate_continuity_supersession( - successor_plan: dict[str, Any], - prior_plan: dict[str, Any], - prior_plan_commit: str, - client: PublicClient, - *, - accepted_tag: str, - accepted_commit: str, - accepted_evidence: Any, - accepted_plan: Any, -) -> dict[str, str]: - validate_recorded_plan(accepted_plan) - successor_tag = f"{PLAN_TAG_PREFIX}{successor_plan['plan']}" - successor_digest = manifest_digest(successor_plan) - if ( - not isinstance(accepted_evidence, dict) - or accepted_tag != f"{CONTINUITY_TAG_PREFIX}{successor_plan['plan']}/accepted" - or canonical_json(accepted_plan) != canonical_json(successor_plan) - or accepted_evidence.get("schema") != CONTINUITY_EVIDENCE_SCHEMA - or accepted_evidence.get("phase") != "accepted" - or accepted_evidence.get("outcome") != "accepted" - or accepted_evidence.get("release_plan") != {"tag": successor_tag, "sha256": successor_digest} - or accepted_evidence.get("candidate_identity") - != {"components": successor_plan["components"], "plan_sha256": successor_digest} - ): - raise CandidateError( - f"accepted continuity record {accepted_tag} does not prove exact requested plan {successor_tag}" - ) - - prior_tag = f"{PLAN_TAG_PREFIX}{prior_plan['plan']}" - prior_digest = manifest_digest(prior_plan) - interruption_tag = f"{CONTINUITY_TAG_PREFIX}{prior_plan['plan']}/interrupted" - superseded = accepted_evidence.get("superseded_interruption") - if ( - not isinstance(superseded, dict) - or set(superseded) != {"commit", "evidence_sha256", "plan_sha256", "reason", "tag"} - or superseded.get("tag") != interruption_tag - or superseded.get("reason") != CONTINUITY_SUPERSESSION_REASON - or not COMMIT_PATTERN.fullmatch(str(superseded.get("commit", ""))) - or not re.fullmatch(r"[0-9a-f]{64}", str(superseded.get("evidence_sha256", ""))) - or superseded.get("plan_sha256") != prior_digest - ): - raise CandidateError(f"accepted continuity record {accepted_tag} has invalid superseded interruption identity") - - interruption_commit = resolve_tag(client, CONTROL_REPOSITORY, interruption_tag) - if interruption_commit != superseded["commit"]: - raise CandidateError( - f"superseded interruption {interruption_tag} resolves to " - f"{interruption_commit or 'no commit'}, not {superseded['commit']}" - ) - interruption_evidence = read_public_record( - client, - interruption_tag, - interruption_commit, - "continuity-evidence.json", - ) - interruption_plan = read_public_record( - client, - interruption_tag, - interruption_commit, - "release-plan.json", - ) - validate_recorded_plan(interruption_plan) - if ( - not isinstance(interruption_evidence, dict) - or canonical_json(interruption_plan) != canonical_json(prior_plan) - or manifest_digest(interruption_plan) != superseded["plan_sha256"] - or manifest_digest(interruption_evidence) != superseded["evidence_sha256"] - or interruption_evidence.get("schema") != CONTINUITY_EVIDENCE_SCHEMA - or interruption_evidence.get("phase") != "interrupted" - or interruption_evidence.get("outcome") != "intentionally-interrupted" - or interruption_evidence.get("release_plan") != {"tag": prior_tag, "sha256": prior_digest} - or interruption_evidence.get("plan_record") - != {"tag": prior_tag, "commit": prior_plan_commit, "sha256": prior_digest} - ): - raise CandidateError(f"superseded interruption {interruption_tag} does not prove prior plan {prior_tag}") - return { - "accepted_tag": accepted_tag, - "accepted_commit": accepted_commit, - "interruption_tag": interruption_tag, - "interruption_commit": interruption_commit, - "outcome": "superseded-diagnostic-interruption", - } - - -def load_continuity_supersession( - requested_plan: dict[str, Any], - prior_plan: dict[str, Any], - prior_plan_commit: str, - client: PublicClient, -) -> dict[str, str] | None: - accepted_tag = f"{CONTINUITY_TAG_PREFIX}{requested_plan['plan']}/accepted" - accepted_commit = resolve_tag(client, CONTROL_REPOSITORY, accepted_tag) - if accepted_commit is None: - return None - - accepted_evidence = read_public_record( - client, - accepted_tag, - accepted_commit, - "continuity-evidence.json", - ) - accepted_plan = read_public_record( - client, - accepted_tag, - accepted_commit, - "release-plan.json", - ) - return validate_continuity_supersession( - requested_plan, - prior_plan, - prior_plan_commit, - client, - accepted_tag=accepted_tag, - accepted_commit=accepted_commit, - accepted_evidence=accepted_evidence, - accepted_plan=accepted_plan, - ) - - -def validate_continuity_resolution_authority( - resolution: Any, - client: PublicClient, -) -> dict[str, Any]: - if ( - not isinstance(resolution, dict) - or set(resolution) - != { - "interruption", - "qualification", - "schema", - "selected_successor", - "successor_claims", - } - or resolution.get("schema") != CONTINUITY_RESOLUTION_SCHEMA - ): - raise CandidateError("continuity successor resolution has an invalid document shape") - interruption = resolution.get("interruption") - claims = resolution.get("successor_claims") - selected = resolution.get("selected_successor") - if ( - not isinstance(interruption, dict) - or set(interruption) != {"evidence", "plan"} - or not isinstance(claims, list) - or len(claims) < 2 - ): - raise CandidateError("continuity successor resolution does not describe an interrupted fork") - - def require_identity(value: Any, label: str) -> dict[str, str]: - if ( - not isinstance(value, dict) - or set(value) != {"commit", "sha256", "tag"} - or not isinstance(value.get("tag"), str) - or not isinstance(value.get("commit"), str) - or not COMMIT_PATTERN.fullmatch(value["commit"]) - or not isinstance(value.get("sha256"), str) - or not re.fullmatch(r"[0-9a-f]{64}", value["sha256"]) - ): - raise CandidateError(f"continuity successor resolution has an invalid {label} identity") - return value - - interrupted_plan_identity = require_identity(interruption.get("plan"), "interrupted plan") - interruption_identity = require_identity(interruption.get("evidence"), "interruption evidence") - interrupted_name = interrupted_plan_identity["tag"].removeprefix(PLAN_TAG_PREFIX) - if ( - interrupted_plan_identity["tag"] != f"{PLAN_TAG_PREFIX}{interrupted_name}" - or not PLAN_PATTERN.fullmatch(interrupted_name) - or interruption_identity["tag"] != f"{CONTINUITY_TAG_PREFIX}{interrupted_name}/interrupted" - ): - raise CandidateError("continuity successor resolution has conflicting interruption tags") - interrupted_commit = resolve_tag(client, CONTROL_REPOSITORY, interrupted_plan_identity["tag"]) - if interrupted_commit != interrupted_plan_identity["commit"]: - raise CandidateError("continuity successor resolution names a moved interrupted plan") - interrupted_plan = read_public_record( - client, - interrupted_plan_identity["tag"], - interrupted_commit, - "release-plan.json", - ) - validate_recorded_plan(interrupted_plan) - if ( - interrupted_plan["plan"] != interrupted_name - or manifest_digest(interrupted_plan) != interrupted_plan_identity["sha256"] - ): - raise CandidateError("continuity successor resolution has a mismatched interrupted plan") - interruption_commit = resolve_tag(client, CONTROL_REPOSITORY, interruption_identity["tag"]) - if interruption_commit != interruption_identity["commit"]: - raise CandidateError("continuity successor resolution names moved interruption evidence") - interruption_evidence = read_public_record( - client, - interruption_identity["tag"], - interruption_commit, - "continuity-evidence.json", - ) - if manifest_digest(interruption_evidence) != interruption_identity["sha256"]: - raise CandidateError("continuity successor resolution has mismatched interruption evidence") - - validated_claims: list[dict[str, dict[str, str]]] = [] - for claim in claims: - if not isinstance(claim, dict) or set(claim) != {"acceptance", "plan"}: - raise CandidateError("continuity successor resolution has an invalid successor claim") - plan_identity = require_identity(claim.get("plan"), "successor plan") - acceptance_identity = require_identity(claim.get("acceptance"), "successor acceptance") - successor_name = plan_identity["tag"].removeprefix(PLAN_TAG_PREFIX) - if ( - plan_identity["tag"] != f"{PLAN_TAG_PREFIX}{successor_name}" - or not PLAN_PATTERN.fullmatch(successor_name) - or acceptance_identity["tag"] != f"{CONTINUITY_TAG_PREFIX}{successor_name}/accepted" - ): - raise CandidateError("continuity successor resolution has conflicting successor tags") - successor_commit = resolve_tag(client, CONTROL_REPOSITORY, plan_identity["tag"]) - if successor_commit != plan_identity["commit"]: - raise CandidateError("continuity successor resolution names a moved successor plan") - successor_plan = read_public_record( - client, - plan_identity["tag"], - successor_commit, - "release-plan.json", - ) - validate_recorded_plan(successor_plan) - if successor_plan["plan"] != successor_name or manifest_digest(successor_plan) != plan_identity["sha256"]: - raise CandidateError("continuity successor resolution has a mismatched successor plan") - accepted_commit = resolve_tag(client, CONTROL_REPOSITORY, acceptance_identity["tag"]) - if accepted_commit != acceptance_identity["commit"]: - raise CandidateError("continuity successor resolution names moved acceptance evidence") - accepted_evidence = read_public_record( - client, - acceptance_identity["tag"], - accepted_commit, - "continuity-evidence.json", - ) - accepted_plan = read_public_record( - client, - acceptance_identity["tag"], - accepted_commit, - "release-plan.json", - ) - if manifest_digest(accepted_evidence) != acceptance_identity["sha256"]: - raise CandidateError("continuity successor resolution has mismatched acceptance evidence") - validate_continuity_supersession( - successor_plan, - interrupted_plan, - interrupted_plan_identity["commit"], - client, - accepted_tag=acceptance_identity["tag"], - accepted_commit=accepted_commit, - accepted_evidence=accepted_evidence, - accepted_plan=accepted_plan, - ) - validated_claims.append({"plan": plan_identity, "acceptance": acceptance_identity}) - - if ( - validated_claims != sorted(validated_claims, key=lambda claim: claim["plan"]["tag"]) - or len({claim["plan"]["tag"] for claim in validated_claims}) != len(validated_claims) - or selected not in [claim["plan"] for claim in validated_claims] - ): - raise CandidateError("continuity successor resolution does not select one exact sorted successor claim") - try: - recovery_discovery.validate_continuity_resolution_qualification( - resolution["qualification"], - client, - ) - except recovery_discovery.RecoveryError as error: - raise CandidateError(str(error)) from error - return resolution - - -def continuity_resolution_tag(resolution: dict[str, Any]) -> str: - interrupted_plan = resolution["interruption"]["plan"]["tag"].removeprefix(PLAN_TAG_PREFIX) - return f"{CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted_plan}/{manifest_digest(resolution)}" - - -def select_completed_continuity_resolution( - prior_plan: dict[str, Any], - prior_plan_commit: str, - matches: list[dict[str, str]], - client: PublicClient, -) -> dict[str, str]: - prefix = f"{CONTINUITY_RESOLUTION_TAG_PREFIX}{prior_plan['plan']}/" - refs = client.json(f"https://api.github.com/repos/{CONTROL_REPOSITORY}/git/matching-refs/tags/{prefix}") - if not isinstance(refs, list): - raise CandidateError("GitHub did not return the immutable continuity-resolution tag registry") - resolution_tags: list[str] = [] - for ref in refs: - value = ref.get("ref") if isinstance(ref, dict) else None - tag = value.removeprefix("refs/tags/") if isinstance(value, str) else "" - if ( - value != f"refs/tags/{tag}" - or not tag.startswith(prefix) - or not re.fullmatch(r"[0-9a-f]{64}", tag.removeprefix(prefix)) - ): - raise CandidateError("GitHub returned a malformed immutable continuity-resolution tag registry entry") - resolution_tags.append(tag) - if not resolution_tags: - raise CandidateError(f"release plan {PLAN_TAG_PREFIX}{prior_plan['plan']} has multiple continuity successors") - if len(resolution_tags) != 1 or len(set(resolution_tags)) != 1: - raise CandidateError( - f"release plan {PLAN_TAG_PREFIX}{prior_plan['plan']} has multiple continuity successor resolutions" - ) - resolution_tag = resolution_tags[0] - resolution_commit = resolve_tag(client, CONTROL_REPOSITORY, resolution_tag) - if resolution_commit is None: - raise CandidateError(f"continuity successor resolution {resolution_tag} is absent") - resolution = read_public_record( - client, - resolution_tag, - resolution_commit, - "continuity-successor-resolution.json", - ) - expected_claims = sorted( - ( - { - "plan": { - "tag": match["successor_plan_tag"], - "commit": match["successor_plan_commit"], - "sha256": match["successor_plan_sha256"], - }, - "acceptance": { - "tag": match["accepted_tag"], - "commit": match["accepted_commit"], - "sha256": match["acceptance_sha256"], - }, - } - for match in matches - ), - key=lambda claim: claim["plan"]["tag"], - ) - expected_interruption = { - "plan": { - "tag": f"{PLAN_TAG_PREFIX}{prior_plan['plan']}", - "commit": prior_plan_commit, - "sha256": manifest_digest(prior_plan), - }, - "evidence": { - "tag": matches[0]["interruption_tag"], - "commit": matches[0]["interruption_commit"], - "sha256": matches[0]["interruption_evidence_sha256"], - }, - } - selected = resolution.get("selected_successor") if isinstance(resolution, dict) else None - if ( - not isinstance(resolution, dict) - or set(resolution) - != { - "interruption", - "qualification", - "schema", - "selected_successor", - "successor_claims", - } - or resolution.get("schema") != CONTINUITY_RESOLUTION_SCHEMA - or resolution.get("interruption") != expected_interruption - or resolution.get("successor_claims") != expected_claims - or selected not in [claim["plan"] for claim in expected_claims] - or resolution_tag != continuity_resolution_tag(resolution) - ): - raise CandidateError( - f"release plan {PLAN_TAG_PREFIX}{prior_plan['plan']} has an invalid immutable " - "continuity successor resolution" - ) - try: - recovery_discovery.validate_continuity_resolution_qualification( - resolution["qualification"], - client, - ) - except recovery_discovery.RecoveryError as error: - raise CandidateError(str(error)) from error - return next(match for match in matches if match["successor_plan_tag"] == selected["tag"]) - - -def load_plan_completion( - plan: dict[str, Any], - plan_record_commit: str, - client: PublicClient, - *, - record_label: str, -) -> dict[str, str] | None: - plan_tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - completion_tag = f"{COMPLETION_TAG_PREFIX}{plan['channel']}/{plan['plan']}" - completion_commit = resolve_tag(client, CONTROL_REPOSITORY, completion_tag) - if completion_commit is None: - return None - completion = read_public_record( - client, - completion_tag, - completion_commit, - "release-candidate.json", - ) - try: - preparation = read_public_record( - client, - plan_tag, - plan_record_commit, - "release-preparation.json", - ) - except CandidateError as error: - if "(404)" not in str(error): - raise - preparation = None - if preparation is not None: - validate_release_preparation(preparation, plan) - if completion != completion_manifest(plan, plan_record_commit, preparation): - raise CandidateError(f"{record_label} completion record {completion_tag} does not prove {plan_tag}") - if load_public_supersession(plan, plan_record_commit, client) is not None: - raise CandidateError( - f"{record_label} release plan {plan_tag} has conflicting completion and terminal-failure records" - ) - return { - "completion_tag": completion_tag, - "completion_commit": completion_commit, - "outcome": "completed", - } - - -def discover_completed_continuity_supersession( - prior_plan: dict[str, Any], - prior_plan_commit: str, - release_plan_tags: list[str], - client: PublicClient, -) -> dict[str, str] | None: - interruption_tag = f"{CONTINUITY_TAG_PREFIX}{prior_plan['plan']}/interrupted" - matches: list[dict[str, str]] = [] - for successor_tag in release_plan_tags: - successor_name = successor_tag.removeprefix(PLAN_TAG_PREFIX) - accepted_tag = f"{CONTINUITY_TAG_PREFIX}{successor_name}/accepted" - accepted_commit = resolve_tag(client, CONTROL_REPOSITORY, accepted_tag) - if accepted_commit is None: - continue - accepted_evidence = read_public_record( - client, - accepted_tag, - accepted_commit, - "continuity-evidence.json", - ) - superseded = accepted_evidence.get("superseded_interruption") if isinstance(accepted_evidence, dict) else None - if not isinstance(superseded, dict) or superseded.get("tag") != interruption_tag: - continue - - accepted_plan = read_public_record( - client, - accepted_tag, - accepted_commit, - "release-plan.json", - ) - validate_recorded_plan(accepted_plan) - supersession = validate_continuity_supersession( - accepted_plan, - prior_plan, - prior_plan_commit, - client, - accepted_tag=accepted_tag, - accepted_commit=accepted_commit, - accepted_evidence=accepted_evidence, - accepted_plan=accepted_plan, - ) - - if successor_tag != f"{PLAN_TAG_PREFIX}{accepted_plan['plan']}": - raise CandidateError(f"accepted continuity successor {accepted_tag} has a different plan identity") - successor_commit = resolve_tag(client, CONTROL_REPOSITORY, successor_tag) - if successor_commit is None: - raise CandidateError(f"accepted continuity successor {successor_tag} has no immutable release plan record") - public_successor = read_public_record( - client, - successor_tag, - successor_commit, - "release-plan.json", - ) - validate_recorded_plan(public_successor) - if canonical_json(public_successor) != canonical_json(accepted_plan): - raise CandidateError(f"recorded continuity successor {successor_tag} differs from {accepted_tag}") - completion = load_plan_completion( - accepted_plan, - successor_commit, - client, - record_label=f"continuity successor {successor_tag}", - ) - if completion is None: - raise CandidateError(f"accepted continuity successor {successor_tag} has no immutable completion record") - matches.append( - { - **supersession, - "interruption_evidence_sha256": str(accepted_evidence["superseded_interruption"]["evidence_sha256"]), - "successor_plan_tag": successor_tag, - "successor_plan_commit": successor_commit, - "successor_plan_sha256": manifest_digest(accepted_plan), - "acceptance_sha256": manifest_digest(accepted_evidence), - "completion_tag": completion["completion_tag"], - "completion_commit": completion["completion_commit"], - } - ) - - if not matches: - return None - if len(matches) == 1: - return matches[0] - return select_completed_continuity_resolution(prior_plan, prior_plan_commit, matches, client) - - -def require_prior_plans_completed(plan: dict[str, Any], client: PublicClient) -> dict[str, dict[str, str]]: - refs = client.json(f"https://api.github.com/repos/{CONTROL_REPOSITORY}/git/matching-refs/tags/{PLAN_TAG_PREFIX}") - if not isinstance(refs, list): - raise CandidateError("GitHub did not return the immutable release-plan tag registry") - requested_tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - release_plan_tags: list[str] = [] - for ref in refs: - if not isinstance(ref, dict) or not isinstance(ref.get("ref"), str): - raise CandidateError("GitHub returned a malformed immutable release-plan tag registry entry") - tag = ref["ref"].removeprefix("refs/tags/") - plan_name = tag.removeprefix(PLAN_TAG_PREFIX) - if ( - ref["ref"] != f"refs/tags/{tag}" - or not tag.startswith(PLAN_TAG_PREFIX) - or not PLAN_PATTERN.fullmatch(plan_name) - ): - raise CandidateError("GitHub returned a malformed immutable release-plan tag registry entry") - release_plan_tags.append(tag) - if len(set(release_plan_tags)) != len(release_plan_tags): - raise CandidateError("immutable release-plan tag registry contains duplicate authorities") - - completed: dict[str, dict[str, str]] = {} - release_plan_tags.sort() - for tag in release_plan_tags: - if tag == requested_tag: - continue - record_commit = resolve_tag(client, CONTROL_REPOSITORY, tag) - if record_commit is None: - raise CandidateError(f"prior release plan {tag} has no immutable Git record") - prior = read_public_record(client, tag, record_commit, "release-plan.json") - validate_recorded_plan(prior) - if tag != f"{PLAN_TAG_PREFIX}{prior['plan']}": - raise CandidateError(f"prior release plan {tag} has a different document identity") - completion = load_plan_completion( - prior, - record_commit, - client, - record_label="prior", - ) - if completion is None: - supersession = load_public_supersession(prior, record_commit, client) - if supersession is None: - continuity_supersession = load_continuity_supersession( - plan, - prior, - record_commit, - client, - ) - if continuity_supersession is None: - continuity_supersession = discover_completed_continuity_supersession( - prior, - record_commit, - release_plan_tags, - client, - ) - if continuity_supersession is None: - raise CandidateError( - f"cannot record {requested_tag} while prior plan {tag} is incomplete; " - f"resume its repository Release plan recovery actions" - ) - completed[tag] = continuity_supersession - continue - failure_tag, failure_commit, failure, successor = supersession - successor_tag = failure["successor_plan"]["tag"] - successor_commit = resolve_tag(client, CONTROL_REPOSITORY, successor_tag) - if successor_commit is None: - if requested_tag != successor_tag or canonical_json(plan) != canonical_json(successor): - raise CandidateError( - f"terminal plan {tag} admits only exact successor {successor_tag} at " - f"sha256 {failure['successor_plan']['sha256']}" - ) - else: - public_successor = read_public_record( - client, - successor_tag, - successor_commit, - "release-plan.json", - ) - validate_recorded_plan(public_successor) - if canonical_json(public_successor) != canonical_json(successor): - raise CandidateError(f"recorded successor {successor_tag} differs from {failure_tag}") - completed[tag] = { - "failure_tag": failure_tag, - "failure_commit": failure_commit, - "outcome": "terminal-failure", - "successor_tag": successor_tag, - } - continue - completed[tag] = completion - return completed - - -def preflight_plan(plan: dict[str, Any], client: PublicClient, *, release_date: str | None = None) -> dict[str, Any]: - source_preparation = require_current_source_preparation(plan) - verify_candidate_foundation(client, plan) - - plan_tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - plan_commit = resolve_tag(client, CONTROL_REPOSITORY, plan_tag) - if plan_commit is not None: - supersession = load_public_supersession(plan, plan_commit, client) - if supersession is not None: - _failure_tag, _failure_commit, failure, _successor = supersession - raise CandidateError( - f"release plan {plan_tag} has terminally failed; dispatch exact successor " - f"{failure['successor_plan']['tag']} at sha256 {failure['successor_plan']['sha256']}" - ) - - prior_plans = require_prior_plans_completed(plan, client) - recovery_authority, recovery_authority_source = load_recovery_workflow_authority(client) - branches: dict[str, str] = {} - recovery_workflows: dict[str, dict[str, Any]] = {} - source_manifests: dict[str, dict[str, Any]] = {} - tags: dict[str, str] = {} - for name, component in COMPONENTS.items(): - repository = client.json(f"https://api.github.com/repos/{component.repository}") - default_branch = repository.get("default_branch") - expected_branch = EXPECTED_DEFAULT_BRANCHES[name] - if default_branch != expected_branch: - raise CandidateError( - f"{component.repository} default branch is {default_branch!r}; " - f"release plans require {expected_branch!r}" - ) - branches[name] = default_branch - - expected_workflow = recovery_authority[name] - workflow = client.json( - f"https://api.github.com/repos/{component.repository}/actions/workflows/release-plan-recovery.yml" - ) - expected_path = expected_workflow["path"] - if workflow.get("path") != expected_path or workflow.get("state") != expected_workflow["state"]: - raise CandidateError( - f"{component.repository} does not expose an active {expected_path} on its default branch" - ) - contents_url = ( - f"https://api.github.com/repos/{component.repository}/contents/{expected_path}?ref={expected_branch}" - ) - workflow_source = client.bytes(contents_url, accept="application/vnd.github.raw+json").decode("utf-8") - try: - workflow_sha256 = verify_workflow_source(name, workflow_source, expected_workflow["sha256"]) - except RecoveryWorkflowAuthorityError as error: - raise CandidateError(str(error)) from error - helper_path = "scripts/ci/component-release-recovery.py" - helper_source = client.bytes( - f"https://api.github.com/repos/{component.repository}/contents/{helper_path}?ref={expected_branch}", - accept="application/vnd.github.raw+json", - ).decode("utf-8") - if ( - 'CONTINUITY_TAG_PREFIX = "beta-continuity/"' not in helper_source - or "def scheduled_continuity_pause(" not in helper_source - or "if args.plan_tag is None" not in helper_source - or '"phase": "continuity-gate"' not in helper_source - ): - raise CandidateError( - f"{component.repository} recovery helper lacks deterministic scheduled continuity gating " - "with exact-plan manual recovery" - ) - recovery_workflows[name] = { - "authority": recovery_authority_source, - "continuity_gate": "scheduled-pause-with-exact-plan-recovery", - "default_branch": expected_branch, - "path": expected_path, - "sha256": workflow_sha256, - "state": workflow["state"], - "workflow_id": workflow.get("id"), - "url": workflow.get("html_url"), - } - - identity = plan["components"][name] - client.json(f"https://api.github.com/repos/{component.repository}/commits/{identity['commit']}") - if name in SOURCE_MANIFESTS: - manifest = source_manifest_evidence(client, name, identity) - if manifest["declared_version"] != identity["version"]: - raise CandidateError( - f"{name} source manifest declares {manifest['declared_version']}, " - f"not planned version {identity['version']}" - ) - source_manifests[name] = manifest - existing = resolve_tag(client, component.repository, identity["version"]) - if existing is not None and existing != identity["commit"]: - raise CandidateError( - f"existing version tag {component.repository}@{identity['version']} points to {existing}, " - f"not {identity['commit']}" - ) - tags[name] = existing or "absent" - - verify_beta_authorization(client, plan) - preparation = prepare_release( - plan, - client, - release_date or dt.datetime.now(dt.UTC).date().isoformat(), - ) - if source_preparation is not None: - require_prepared_note_sources(source_preparation, preparation) - evidence = { - "default_branches": branches, - "prior_plans": prior_plans, - "recovery_workflows": recovery_workflows, - "release_preparation": preparation, - "source_manifests": source_manifests, - "version_tags": tags, - } - if source_preparation is not None: - source_preparation_path = ( - "release-plans/current-source-preparation.json" - if source_preparation["plan"] == load_source_preparation(SOURCE_PREPARATION_PATH)["plan"] - else "release-plans/first-release-candidate-source-preparation.json" - ) - evidence["source_preparation"] = { - "path": source_preparation_path, - "plan": source_preparation["plan"], - "sha256": manifest_digest(source_preparation), - } - return evidence - - -def check_plan_compatibility(repository: Path, plan_path: Path, *, remote: str) -> dict[str, str]: - plan = load_plan(plan_path) - canonical = canonical_json(plan) - tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - existing_ref = fetch_existing_record(repository, remote, tag) - if not existing_ref: - return {"status": "new", "plan": plan["plan"], "tag": tag} - existing = read_record_file(repository, existing_ref, "release-plan.json") - if existing != canonical: - raise CandidateError(f"release plan {plan['plan']} is immutable and the requested tuple is different") - try: - preparation = json.loads(read_record_file(repository, existing_ref, "release-preparation.json")) - except json.JSONDecodeError as error: - raise CandidateError(f"release plan {plan['plan']} has invalid preparation authority") from error - validate_release_preparation(preparation, plan) - return { - "status": "existing", - "plan": plan["plan"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - "preparation_sha256": manifest_digest(preparation), - } - - -def load_release_preparation(path: Path, plan: dict[str, Any]) -> dict[str, Any]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read release preparation {path}: {error}") from error - if len(raw) > 2 * 1024 * 1024: - raise CandidateError("release preparation exceeds the 2 MiB limit") - try: - preparation = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"release preparation is not valid JSON: {error}") from error - validate_release_preparation(preparation, plan) - return preparation - - -def record_plan( - repository: Path, - plan_path: Path, - preparation_path: Path, - *, - remote: str, - authoritative_plan: Path, - authoritative_preparation: Path, -) -> dict[str, str]: - plan = load_plan(plan_path) - canonical = canonical_json(plan) - preparation = load_release_preparation(preparation_path, plan) - canonical_preparation = canonical_json(preparation) - tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - existing_ref = fetch_existing_record(repository, remote, tag) - if existing_ref: - existing = read_record_file(repository, existing_ref, "release-plan.json") - if existing != canonical: - raise CandidateError(f"release plan {plan['plan']} is immutable and the requested tuple is different") - existing_preparation = read_record_file(repository, existing_ref, "release-preparation.json") - try: - validate_release_preparation(json.loads(existing_preparation), plan) - except json.JSONDecodeError as error: - raise CandidateError(f"release plan {plan['plan']} has invalid immutable preparation authority") from error - authoritative_plan.write_bytes(existing) - authoritative_preparation.write_bytes(existing_preparation) - return { - "status": "existing", - "plan": plan["plan"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - "preparation_sha256": manifest_digest(json.loads(existing_preparation)), - } - - with tempfile.NamedTemporaryFile(prefix="release-plan-index-", delete=False) as index: - index_path = Path(index.name) - try: - env = os.environ.copy() - env["GIT_INDEX_FILE"] = str(index_path) - index_path.unlink(missing_ok=True) - run_git(["read-tree", "--empty"], cwd=repository, env=env) - for filename, content in ( - ("release-plan.json", canonical), - ("release-preparation.json", canonical_preparation), - ): - blob = ( - subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=repository, - env=env, - input=content, - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - run_git( - ["update-index", "--add", "--cacheinfo", f"100644,{blob},{filename}"], - cwd=repository, - env=env, - ) - tree = run_git(["write-tree"], cwd=repository, env=env) - commit_env = env | { - "GIT_AUTHOR_NAME": "Durable Workflow Release Planner", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Release Planner", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - } - commit = subprocess.run( - ["git", "commit-tree", tree], - cwd=repository, - env=commit_env, - input=f"Record release plan {plan['plan']}\n", - check=True, - text=True, - capture_output=True, - ).stdout.strip() - process = subprocess.run( - ["git", "push", remote, f"{commit}:refs/tags/{tag}"], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if process.returncode: - recovered = check_plan_compatibility(repository, plan_path, remote=remote) - if recovered["status"] != "existing": - raise CandidateError(f"cannot publish immutable release plan: {process.stderr.strip()}") - authoritative_plan.write_bytes(canonical) - recovered_ref = fetch_existing_record(repository, remote, tag) - if not recovered_ref: - raise CandidateError("immutable release preparation disappeared during recovery") - authoritative_preparation.write_bytes( - read_record_file(repository, recovered_ref, "release-preparation.json") - ) - return recovered - authoritative_plan.write_bytes(canonical) - authoritative_preparation.write_bytes(canonical_preparation) - return { - "status": "created", - "plan": plan["plan"], - "tag": tag, - "commit": commit, - "preparation_sha256": manifest_digest(preparation), - } - finally: - index_path.unlink(missing_ok=True) - - -def protected_environment_evidence( - client: PublicClient, -) -> tuple[dict[str, Any], set[tuple[int, str]]]: - encoded = urllib.parse.quote(SUPERSESSION_ENVIRONMENT, safe="") - environment = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - if not isinstance(environment, dict): - raise CandidateError(f"GitHub environment {SUPERSESSION_ENVIRONMENT} has invalid public evidence") - rules = environment.get("protection_rules") - if not isinstance(rules, list): - raise CandidateError(f"GitHub environment {SUPERSESSION_ENVIRONMENT} has no protection rules") - reviewer_rules = [ - rule - for rule in rules or [] - if isinstance(rule, dict) and rule.get("type") == "required_reviewers" and rule.get("reviewers") - ] - rule_ids = sorted(rule["id"] for rule in reviewer_rules if type(rule.get("id")) is int and rule["id"] > 0) - if len(reviewer_rules) != 1 or len(rule_ids) != 1: - raise CandidateError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} must contain one explicit reviewer policy" - ) - reviewer_user_ids: list[int] = [] - required_reviewers: set[tuple[int, str]] = set() - for reviewer in reviewer_rules[0]["reviewers"]: - identity = reviewer.get("reviewer") if isinstance(reviewer, dict) else None - if ( - not isinstance(identity, dict) - or reviewer.get("type") != "User" - or type(identity.get("id")) is not int - or identity["id"] < 1 - or not isinstance(identity.get("login"), str) - or not re.fullmatch(r"[A-Za-z0-9-]{1,39}", identity["login"]) - ): - raise CandidateError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} has an unverifiable required reviewer" - ) - reviewer_user_ids.append(identity["id"]) - required_reviewers.add((identity["id"], identity["login"])) - reviewer_user_ids = sorted(set(reviewer_user_ids)) - prevent_self_review = reviewer_rules[0].get("prevent_self_review") - if not reviewer_user_ids or type(prevent_self_review) is not bool: - raise CandidateError( - f"GitHub environment {SUPERSESSION_ENVIRONMENT} has an invalid required reviewer policy" - ) - environment_id = environment.get("id") - environment_url = environment.get("html_url") - deployment_branch_policy = environment.get("deployment_branch_policy") - if ( - type(environment_id) is not int - or environment_id < 1 - or not isinstance(environment_url, str) - or environment_url != SUPERSESSION_ENVIRONMENT_URL - ): - raise CandidateError(f"GitHub environment {SUPERSESSION_ENVIRONMENT} has invalid public evidence") - if ( - not isinstance(deployment_branch_policy, dict) - or deployment_branch_policy.get("custom_branch_policies") is not True - or deployment_branch_policy.get("protected_branches") is not False - ): - raise CandidateError(f"GitHub environment {SUPERSESSION_ENVIRONMENT} must enable custom branch policies") - policies = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}/" - "deployment-branch-policies?per_page=100", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - branch_policies = policies.get("branch_policies") if isinstance(policies, dict) else None - if ( - not isinstance(branch_policies, list) - or type(policies.get("total_count")) is not int - or policies["total_count"] != 1 - or len(branch_policies) != 1 - or not isinstance(branch_policies[0], dict) - or type(branch_policies[0].get("id")) is not int - or branch_policies[0]["id"] < 1 - or branch_policies[0].get("name") != "main" - or branch_policies[0].get("type", "branch") != "branch" - ): - raise CandidateError(f"GitHub environment {SUPERSESSION_ENVIRONMENT} must allow only the main branch") - evidence = { - "custom_branch_policies": [ - { - "id": branch_policies[0]["id"], - "name": branch_policies[0]["name"], - } - ], - "deployment_branch_policy": { - "custom_branch_policies": True, - "protected_branches": False, - }, - "environment_id": environment_id, - "environment_url": environment_url, - "required_reviewer_rule_ids": rule_ids, - } - validate_environment_protection_evidence(evidence) - return evidence, required_reviewers - - -def protected_run_approval_evidence( - client: PublicClient, - *, - actor: str, - run_id: int, - run_attempt: int, - workflow_commit: str, - environment_protection: dict[str, Any], - required_reviewers: set[tuple[int, str]], - require_success: bool = False, -) -> dict[str, Any]: - run = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - run_actor = run.get("actor") if isinstance(run, dict) else None - run_repository = run.get("repository") if isinstance(run, dict) else None - run_path = run.get("path") if isinstance(run, dict) else None - accepted_run_paths = {SUPERSESSION_WORKFLOW, f"{SUPERSESSION_WORKFLOW}@main"} - if ( - not isinstance(run, dict) - or not isinstance(run_actor, dict) - or run_actor.get("login") != actor - or not isinstance(run_repository, dict) - or run_repository.get("full_name") != CONTROL_REPOSITORY - or type(run.get("id")) is not int - or run["id"] != run_id - or type(run.get("run_attempt")) is not int - or run["run_attempt"] != run_attempt - or run.get("event") != "workflow_dispatch" - or run_path not in accepted_run_paths - or run.get("head_branch") != "main" - or run.get("head_sha") != workflow_commit - or run.get("html_url") != f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id}" - or ( - require_success - and (run.get("status") != "completed" or run.get("conclusion") != "success") - ) - ): - raise CandidateError("protected supersession workflow run evidence does not match GitHub") - if run_attempt != 1: - raise CandidateError( - "GitHub approval history cannot prove protected approval for a rerun attempt" - ) - - history = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}/approvals", - headers={"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, - accept="application/vnd.github+json", - ) - if ( - not isinstance(history, list) - or len(history) != 1 - or not isinstance(history[0], dict) - or history[0].get("state") != "approved" - ): - raise CandidateError("protected supersession run must contain exactly one approved review") - review = history[0] - environments = review.get("environments") - user = review.get("user") - if ( - not isinstance(review.get("comment"), str) - or not isinstance(environments, list) - or len(environments) != 1 - or not isinstance(environments[0], dict) - or not isinstance(user, dict) - ): - raise CandidateError("protected supersession approval history is malformed") - environment = environments[0] - evidence = { - "comment": review["comment"], - "environments": [ - { - "html_url": environment.get("html_url"), - "id": environment.get("id"), - "name": environment.get("name"), - "node_id": environment.get("node_id"), - "url": environment.get("url"), - } - ], - "run_attempt": run_attempt, - "run_id": run_id, - "state": review["state"], - "user": { - "html_url": user.get("html_url"), - "id": user.get("id"), - "login": user.get("login"), - "node_id": user.get("node_id"), - "url": user.get("url"), - }, - } - validate_environment_approval_evidence( - evidence, - { - "environment_approval": evidence, - "environment_protection": environment_protection, - "run_attempt": run_attempt, - "run_id": run_id, - }, - ) - if (evidence["user"]["id"], evidence["user"]["login"]) not in required_reviewers: - raise CandidateError( - "protected supersession approving user is not authorized by the current reviewer policy" - ) - return evidence - - -def revalidate_supersession_authority( - record: dict[str, Any], - client: PublicClient, - *, - require_success: bool, -) -> None: - authorization = record["authorization"] - live_protection, required_reviewers = protected_environment_evidence(client) - if live_protection != authorization["environment_protection"]: - raise CandidateError("release plan failure protected environment policy no longer matches GitHub") - live_approval = protected_run_approval_evidence( - client, - actor=authorization["actor"], - run_id=authorization["run_id"], - run_attempt=authorization["run_attempt"], - workflow_commit=authorization["workflow_commit"], - environment_protection=live_protection, - required_reviewers=required_reviewers, - require_success=require_success, - ) - if live_approval != authorization["environment_approval"]: - raise CandidateError("release plan failure approved deployment evidence no longer matches GitHub") - - -def prove_publication_absence( - client: PublicClient, component_name: str, version: str -) -> tuple[dict[str, str], dict[str, str]]: - release, distribution = publication_absence_locations(component_name, version) - for surface, evidence in ( - ("GitHub Release", release), - ("public distribution", distribution), - ): - try: - client.json(evidence["api_url"]) - except CandidateError as error: - if "(404)" in str(error): - continue - raise CandidateError(f"cannot prove {component_name} {surface} absence for {version}: {error}") from error - raise CandidateError( - f"{component_name} version {version} already has a {surface}; " - "an occupied source-manifest conflict requires it to be absent" - ) - return release, distribution - - -def prepare_conflict_evidence( - failed_plan: dict[str, Any], - successor_plan: dict[str, Any], - conflict_component: str, - client: PublicClient, -) -> dict[str, Any]: - identity = failed_plan["components"][conflict_component] - successor_identity = successor_plan["components"][conflict_component] - component = COMPONENTS[conflict_component] - failed_manifest = ( - source_manifest_evidence(client, conflict_component, identity) - if conflict_component in SOURCE_MANIFESTS - else None - ) - observed_commit = resolve_tag(client, component.repository, identity["version"]) - if failed_manifest is not None and failed_manifest["declared_version"] != identity["version"]: - successor_manifest = source_manifest_evidence( - client, - conflict_component, - successor_identity, - ) - if observed_commit is None: - conflict = { - "component": conflict_component, - "version": identity["version"], - "planned_commit": identity["commit"], - "reason": SOURCE_MANIFEST_REASON, - "source_manifest": failed_manifest, - "successor_source_manifest": successor_manifest, - } - elif observed_commit == identity["commit"]: - source_tag = resolve_github_tag(client, component.repository, identity["version"]) - if source_tag["commit"] != observed_commit: - raise CandidateError( - f"{conflict_component} version {identity['version']} changed while proving its source" - ) - release_absence, distribution_absence = prove_publication_absence( - client, conflict_component, identity["version"] - ) - conflict = { - "component": conflict_component, - "version": identity["version"], - "planned_commit": identity["commit"], - "reason": OCCUPIED_SOURCE_MANIFEST_REASON, - "source_manifest": failed_manifest, - "source_tag": source_tag, - "github_release": release_absence, - "distribution": distribution_absence, - "successor_source_manifest": successor_manifest, - } - else: - raise CandidateError( - f"{conflict_component} version {identity['version']} has both a source-manifest conflict " - f"and a version tag at different commit {observed_commit}" - ) - else: - if observed_commit is None: - raise CandidateError(f"{conflict_component} version {identity['version']} has no terminal public conflict") - source = resolve_github_tag(client, component.repository, identity["version"]) - if source["commit"] != observed_commit: - raise CandidateError(f"{conflict_component} version {identity['version']} changed while proving its source") - if observed_commit == identity["commit"]: - raise CandidateError( - f"{conflict_component} version {identity['version']} still resolves to the planned source commit" - ) - release = github_release_conflict_evidence( - client, - conflict_component, - identity["version"], - ) - with tempfile.TemporaryDirectory(prefix="release-plan-failure-") as temporary: - distribution = VERIFIERS[component.distribution]( - client, - component, - identity["version"], - observed_commit, - Path(temporary), - ) - require_distribution_identity( - distribution, - conflict_component, - identity["version"], - observed_commit, - ) - conflict = { - "component": conflict_component, - "version": identity["version"], - "planned_commit": identity["commit"], - "observed_commit": observed_commit, - "reason": SUPERSESSION_REASON, - "github_release": release, - "distribution": distribution, - } - existing_successor_version = resolve_tag( - client, - component.repository, - successor_identity["version"], - ) - if existing_successor_version not in {None, successor_identity["commit"]}: - raise CandidateError( - f"successor version {component.repository}@{successor_identity['version']} already points to " - f"{existing_successor_version}" - ) - return conflict - - -def prepare_supersession( - failed_plan_tag: str, - conflict_components: str | list[str], - successor_plan: dict[str, Any], - client: PublicClient, - *, - actor: str, - run_id: str, - run_attempt: str, - workflow_ref: str, - workflow_commit: str, -) -> tuple[dict[str, Any], dict[str, Any]]: - if not failed_plan_tag.startswith(PLAN_TAG_PREFIX): - raise CandidateError(f"failed release plan tag must start with {PLAN_TAG_PREFIX}") - failed_plan_commit = resolve_tag(client, CONTROL_REPOSITORY, failed_plan_tag) - if failed_plan_commit is None: - raise CandidateError(f"failed release plan tag {failed_plan_tag} does not exist") - failed_plan = read_public_record( - client, - failed_plan_tag, - failed_plan_commit, - "release-plan.json", - ) - validate_recorded_plan(failed_plan) - if failed_plan_tag != f"{PLAN_TAG_PREFIX}{failed_plan['plan']}": - raise CandidateError("failed release plan tag and document identity differ") - component_names = conflict_component_names(conflict_components) - validate_successor_transition(failed_plan, successor_plan, component_names) - required_conflicts = conflict_components_from_public_evidence(failed_plan, client) - missing_conflicts = [name for name in required_conflicts if name not in component_names] - if missing_conflicts: - raise CandidateError( - "conflicting components omit independently proven public conflicts: " + ", ".join(missing_conflicts) - ) - - existing = load_public_supersession(failed_plan, failed_plan_commit, client) - if existing is not None: - _tag, _commit, record, authoritative_successor = existing - if canonical_json(successor_plan) != canonical_json(authoritative_successor): - raise CandidateError("release plan failure is immutable and names a different successor") - if conflict_component_names(record["conflicts"]) != component_names: - raise CandidateError("release plan failure is immutable and names different conflicts") - return record, authoritative_successor - - completion_tag = f"{COMPLETION_TAG_PREFIX}{failed_plan['channel']}/{failed_plan['plan']}" - if resolve_tag(client, CONTROL_REPOSITORY, completion_tag) is not None: - raise CandidateError(f"completed release plan {failed_plan_tag} cannot be terminally failed") - - conflicts = [prepare_conflict_evidence(failed_plan, successor_plan, name, client) for name in component_names] - - try: - run_id_value = int(run_id) - run_attempt_value = int(run_attempt) - except ValueError as error: - raise CandidateError("protected workflow run identity must be numeric") from error - protection, required_reviewers = protected_environment_evidence(client) - approval = protected_run_approval_evidence( - client, - actor=actor, - run_id=run_id_value, - run_attempt=run_attempt_value, - workflow_commit=workflow_commit, - environment_protection=protection, - required_reviewers=required_reviewers, - ) - record = { - "schema": "durable-workflow.release-plan-failure/v1", - "outcome": "terminal-failure", - "failed_plan": { - "tag": failed_plan_tag, - "commit": failed_plan_commit, - "sha256": manifest_digest(failed_plan), - }, - "conflicts": conflicts, - "successor_plan": { - "tag": f"{PLAN_TAG_PREFIX}{successor_plan['plan']}", - "sha256": manifest_digest(successor_plan), - }, - "authorization": { - "actor": actor, - "environment": SUPERSESSION_ENVIRONMENT, - "environment_approval": approval, - "environment_protection": protection, - "repository": CONTROL_REPOSITORY, - "run_attempt": run_attempt_value, - "run_id": run_id_value, - "run_url": f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id_value}", - "workflow_commit": workflow_commit, - "workflow_ref": workflow_ref, - }, - } - validate_supersession_record(record, failed_plan, failed_plan_commit, successor_plan) - return record, successor_plan - - -def load_supersession_file(path: Path) -> dict[str, Any]: - try: - value = json.loads(path.read_bytes()) - except (OSError, json.JSONDecodeError) as error: - raise CandidateError(f"cannot read release plan failure record {path}: {error}") from error - if not isinstance(value, dict): - raise CandidateError("release plan failure record must be a JSON object") - return value - - -def validate_supersession_handoff( - record_path: Path, - successor_plan_path: Path, - destination: Path, - *, - expected_failed_plan_tag: str, - expected_conflict_components: str | list[str], -) -> dict[str, Any]: - record = load_supersession_file(record_path) - successor = load_plan(successor_plan_path) - failed = record.get("failed_plan") - if not isinstance(failed, dict) or failed.get("tag") != expected_failed_plan_tag: - raise CandidateError("release plan failure record does not match the trusted failed plan dispatch input") - expected_components = ( - parse_conflict_components(expected_conflict_components) - if isinstance(expected_conflict_components, str) - else conflict_component_names(expected_conflict_components) - ) - if conflict_component_names(record.get("conflicts")) != expected_components: - raise CandidateError("release plan failure record does not match the trusted conflict component dispatch input") - if record.get("successor_plan") != { - "tag": f"{PLAN_TAG_PREFIX}{successor['plan']}", - "sha256": manifest_digest(successor), - }: - raise CandidateError("release plan failure record does not bind the supplied successor document") - destination.write_bytes(canonical_json(record)) - return record - - -def record_supersession( - repository: Path, - record_path: Path, - successor_plan_path: Path, - *, - remote: str, - authoritative_record: Path, - authoritative_successor: Path, - client: PublicClient, -) -> dict[str, str]: - record = load_supersession_file(record_path) - successor = load_plan(successor_plan_path) - failed = record.get("failed_plan") - if not isinstance(failed, dict) or not str(failed.get("tag", "")).startswith(PLAN_TAG_PREFIX): - raise CandidateError("release plan failure record has no failed plan identity") - failed_plan_name = str(failed["tag"]).removeprefix(PLAN_TAG_PREFIX) - if not PLAN_PATTERN.fullmatch(failed_plan_name): - raise CandidateError("release plan failure record has an invalid failed plan identity") - if record.get("successor_plan") != { - "tag": f"{PLAN_TAG_PREFIX}{successor['plan']}", - "sha256": manifest_digest(successor), - }: - raise CandidateError("release plan failure record does not bind the supplied successor document") - failed_plan_commit = resolve_tag(client, CONTROL_REPOSITORY, failed["tag"]) - if failed_plan_commit is None: - raise CandidateError(f"failed release plan tag {failed['tag']} does not exist") - failed_plan = read_public_record( - client, - failed["tag"], - failed_plan_commit, - "release-plan.json", - ) - validate_recorded_plan(failed_plan) - validate_supersession_record(record, failed_plan, failed_plan_commit, successor) - canonical_record = canonical_json(record) - canonical_successor = canonical_json(successor) - tag = f"{FAILURE_TAG_PREFIX}{failed_plan_name}" - existing_ref = fetch_existing_record(repository, remote, tag) - if existing_ref: - existing_record = read_record_file(repository, existing_ref, "release-plan-failure.json") - existing_successor = read_record_file(repository, existing_ref, "successor-release-plan.json") - if existing_record != canonical_record or existing_successor != canonical_successor: - raise CandidateError(f"release plan failure {failed_plan_name} is immutable and differs") - authoritative_record.write_bytes(existing_record) - authoritative_successor.write_bytes(existing_successor) - return { - "status": "existing", - "failed_plan": failed_plan_name, - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - - completion_tag = f"{COMPLETION_TAG_PREFIX}{failed_plan['channel']}/{failed_plan['plan']}" - if resolve_tag(client, CONTROL_REPOSITORY, completion_tag) is not None: - raise CandidateError(f"completed release plan {failed['tag']} cannot be terminally failed") - revalidate_supersession_public_evidence(record, failed_plan, successor, client) - - with tempfile.NamedTemporaryFile(prefix="release-plan-failure-index-", delete=False) as index: - index_path = Path(index.name) - try: - env = os.environ.copy() - env["GIT_INDEX_FILE"] = str(index_path) - index_path.unlink(missing_ok=True) - run_git(["read-tree", "--empty"], cwd=repository, env=env) - for filename, content in ( - ("release-plan-failure.json", canonical_record), - ("successor-release-plan.json", canonical_successor), - ): - blob = ( - subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=repository, - env=env, - input=content, - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - run_git( - ["update-index", "--add", "--cacheinfo", f"100644,{blob},{filename}"], - cwd=repository, - env=env, - ) - tree = run_git(["write-tree"], cwd=repository, env=env) - commit_env = env | { - "GIT_AUTHOR_NAME": "Durable Workflow Release Planner", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Release Planner", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - } - commit = subprocess.run( - ["git", "commit-tree", tree], - cwd=repository, - env=commit_env, - input=f"Record terminal failure for release plan {failed_plan_name}\n", - check=True, - text=True, - capture_output=True, - ).stdout.strip() - process = subprocess.run( - ["git", "push", remote, f"{commit}:refs/tags/{tag}"], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if process.returncode: - existing_ref = fetch_existing_record(repository, remote, tag) - if ( - not existing_ref - or read_record_file(repository, existing_ref, "release-plan-failure.json") != canonical_record - or read_record_file(repository, existing_ref, "successor-release-plan.json") != canonical_successor - ): - raise CandidateError(f"cannot publish immutable release plan failure: {process.stderr.strip()}") - authoritative_record.write_bytes(canonical_record) - authoritative_successor.write_bytes(canonical_successor) - return { - "status": "existing", - "failed_plan": failed_plan_name, - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - authoritative_record.write_bytes(canonical_record) - authoritative_successor.write_bytes(canonical_successor) - return {"status": "created", "failed_plan": failed_plan_name, "tag": tag, "commit": commit} - finally: - index_path.unlink(missing_ok=True) - - -def candidate_manifest(plan: dict[str, Any]) -> dict[str, Any]: - validate_recorded_plan(plan) - return { - "schema": LEGACY_CANDIDATE_SCHEMA if plan["schema"] == LEGACY_SCHEMA else CANDIDATE_SCHEMA, - "candidate": f"{plan['channel']}-{plan['plan']}", - "components": plan["components"], - } - - -def completion_manifest( - plan: dict[str, Any], - plan_record_commit: str, - preparation: dict[str, Any] | None = None, -) -> dict[str, Any]: - completion = { - "schema": "durable-workflow.release-candidate/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "release_plan": { - "tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "commit": plan_record_commit, - "sha256": manifest_digest(plan), - }, - "components": plan["components"], - } - if preparation is not None: - validate_release_preparation(preparation, plan) - completion["release_preparation_sha256"] = manifest_digest(preparation) - return completion - - -def validate_completion_verification( - value: Any, - plan: dict[str, Any], - candidate: dict[str, Any], - current_verification: dict[str, Any] | None, - preparation: dict[str, Any] | None, -) -> None: - expected_keys = { - "schema", - "candidate", - "channel", - "release_plan_sha256", - "public_verification", - } - if preparation is not None: - expected_keys.add("release_preparation_sha256") - if ( - not isinstance(value, dict) - or set(value) != expected_keys - or value.get("schema") != "durable-workflow.release-candidate-verification/v1" - or value.get("candidate") != plan["plan"] - or value.get("channel") != plan["channel"] - or value.get("release_plan_sha256") != manifest_digest(plan) - or (preparation is not None and value.get("release_preparation_sha256") != manifest_digest(preparation)) - ): - raise CandidateError(f"completed release candidate {plan['plan']} has invalid verification authority") - recorded = value["public_verification"] - validate_recorded_verification(recorded, candidate) - if current_verification is not None and recorded["components"] != current_verification["components"]: - raise CandidateError( - f"completed release candidate {plan['plan']} verification differs from current public evidence" - ) - - -def read_existing_completion_record( - repository: Path, - plan: dict[str, Any], - plan_record_commit: str, - preparation: dict[str, Any] | None, - *, - remote: str, - authoritative_completion: Path, - authoritative_verification: Path, -) -> dict[str, str] | None: - candidate = candidate_manifest(plan) - completion = completion_manifest(plan, plan_record_commit, preparation) - canonical_completion = canonical_json(completion) - tag = f"{COMPLETION_TAG_PREFIX}{plan['channel']}/{plan['plan']}" - existing_ref = fetch_existing_record(repository, remote, tag) - if not existing_ref: - return None - existing = read_record_file(repository, existing_ref, "release-candidate.json") - if existing != canonical_completion: - raise CandidateError(f"completed release candidate {plan['plan']} is immutable and differs") - existing_verification = read_record_file(repository, existing_ref, "verification.json") - try: - existing_verification_value = json.loads(existing_verification) - except json.JSONDecodeError as error: - raise CandidateError( - f"completed release candidate {plan['plan']} has invalid verification authority" - ) from error - validate_completion_verification( - existing_verification_value, - plan, - candidate, - None, - preparation, - ) - authoritative_completion.write_bytes(existing) - authoritative_verification.write_bytes(existing_verification) - return { - "status": "existing", - "candidate": plan["plan"], - "channel": plan["channel"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - - -def read_current_completion_authority( - repository: Path, - plan_path: Path, - *, - remote: str, - authoritative_plan: Path, - authoritative_preparation: Path, - authoritative_completion: Path, - authoritative_verification: Path, -) -> dict[str, str]: - plan = load_plan(plan_path) - plan_tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - plan_ref = fetch_existing_record(repository, remote, plan_tag) - base = { - "status": "new", - "candidate": plan["plan"], - "channel": plan["channel"], - "plan_tag": plan_tag, - "completion_tag": f"{COMPLETION_TAG_PREFIX}{plan['channel']}/{plan['plan']}", - } - if not plan_ref: - return base - existing_plan = read_record_file(repository, plan_ref, "release-plan.json") - if existing_plan != canonical_json(plan): - raise CandidateError(f"release plan {plan['plan']} is immutable and the requested tuple is different") - plan_commit = run_git(["rev-parse", f"{plan_ref}^{{commit}}"], cwd=repository) - try: - existing_preparation = read_record_file(repository, plan_ref, "release-preparation.json") - preparation = json.loads(existing_preparation) - except json.JSONDecodeError as error: - raise CandidateError(f"release plan {plan['plan']} has invalid preparation authority") from error - validate_release_preparation(preparation, plan) - completed = read_existing_completion_record( - repository, - plan, - plan_commit, - preparation, - remote=remote, - authoritative_completion=authoritative_completion, - authoritative_verification=authoritative_verification, - ) - if completed is None: - return {**base, "plan_commit": plan_commit} - authoritative_plan.write_bytes(existing_plan) - authoritative_preparation.write_bytes(existing_preparation) - return { - **completed, - "plan_tag": plan_tag, - "plan_commit": plan_commit, - "completion_tag": completed["tag"], - "completion_commit": completed["commit"], - } - - -def _load_canonical_evidence_record(path: Path, description: str) -> tuple[Any, bytes]: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read {description} {path}: {error}") from error - if len(raw) > 2 * 1024 * 1024: - raise CandidateError(f"{description} exceeds the 2 MiB limit") - try: - value = json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"{description} is not valid JSON: {error}") from error - if canonical_json(value) != raw: - raise CandidateError(f"{description} is not canonical JSON") - return value, raw - - -def stage_current_plan_evidence( - plan_path: Path, - destination: Path, - *, - completion_status: str, - authoritative_plan: Path, - authoritative_preparation: Path, - authoritative_completion: Path, - authoritative_verification: Path, - preflight: Path | None = None, -) -> dict[str, str]: - if completion_status not in {"new", "existing"}: - raise CandidateError(f"invalid current completion status {completion_status!r}") - - plan = load_plan(plan_path) - recorded_plan, recorded_plan_bytes = _load_canonical_evidence_record( - authoritative_plan, - "authoritative release plan", - ) - if canonical_json(recorded_plan) != canonical_json(plan): - raise CandidateError("authoritative release plan differs from the selected current plan") - - preparation, preparation_bytes = _load_canonical_evidence_record( - authoritative_preparation, - "authoritative release preparation", - ) - validate_release_preparation(preparation, plan) - - completion, completion_bytes = _load_canonical_evidence_record( - authoritative_completion, - "authoritative release candidate", - ) - release_plan = completion.get("release_plan") if isinstance(completion, dict) else None - plan_commit = release_plan.get("commit") if isinstance(release_plan, dict) else None - if not isinstance(plan_commit, str) or COMMIT_PATTERN.fullmatch(plan_commit) is None: - raise CandidateError("authoritative release candidate lacks an immutable release-plan commit") - if canonical_json(completion) != canonical_json(completion_manifest(plan, plan_commit, preparation)): - raise CandidateError("authoritative release candidate differs from the selected current plan") - - verification, verification_bytes = _load_canonical_evidence_record( - authoritative_verification, - "authoritative completion verification", - ) - validate_completion_verification( - verification, - plan, - candidate_manifest(plan), - None, - preparation, - ) - - payloads = { - "authoritative-release-candidate.json": completion_bytes, - "authoritative-release-plan.json": recorded_plan_bytes, - "authoritative-release-preparation.json": preparation_bytes, - "authoritative-verification.json": verification_bytes, - } - preflight_status = "not-run-existing-authority" - if preflight is not None and preflight.exists(): - preflight_value, preflight_bytes = _load_canonical_evidence_record( - preflight, - "release-plan preflight evidence", - ) - if ( - not isinstance(preflight_value, dict) - or preflight_value.get("schema") != "durable-workflow.release-plan-preflight/v1" - or preflight_value.get("plan") != plan["plan"] - or preflight_value.get("channel") != plan["channel"] - or preflight_value.get("outcome") != "verified" - or preflight_value.get("release_preparation_sha256") != manifest_digest(preparation) - ): - raise CandidateError("release-plan preflight evidence differs from the selected current plan") - payloads["release-plan-preflight.json"] = preflight_bytes - preflight_status = "included" - elif completion_status == "new": - raise CandidateError("new current authority evidence requires release-plan preflight evidence") - - manifest = { - "schema": "durable-workflow.current-release-plan-evidence/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "completion_status": completion_status, - "preflight": preflight_status, - "records": { - filename: {"sha256": hashlib.sha256(payload).hexdigest()} - for filename, payload in sorted(payloads.items()) - }, - } - payloads["evidence-manifest.json"] = canonical_json(manifest) - - def destination_matches() -> bool: - if destination.is_symlink() or not destination.is_dir(): - return False - entries = list(destination.iterdir()) - if any(not entry.is_file() for entry in entries) or {entry.name for entry in entries} != set(payloads): - return False - return all((destination / filename).read_bytes() == payload for filename, payload in payloads.items()) - - if destination.exists() or destination.is_symlink(): - if destination_matches(): - return { - "status": "existing", - "candidate": plan["plan"], - "destination": str(destination), - "preflight": preflight_status, - } - raise CandidateError(f"current-plan evidence destination {destination} already differs") - - destination.parent.mkdir(parents=True, exist_ok=True) - temporary = Path(tempfile.mkdtemp(prefix=f".{destination.name}-", dir=destination.parent)) - try: - for filename, payload in payloads.items(): - (temporary / filename).write_bytes(payload) - try: - temporary.rename(destination) - except FileExistsError: - if destination_matches(): - return { - "status": "existing", - "candidate": plan["plan"], - "destination": str(destination), - "preflight": preflight_status, - } - raise CandidateError(f"current-plan evidence destination {destination} already differs") from None - finally: - if temporary.exists(): - shutil.rmtree(temporary) - return { - "status": "created", - "candidate": plan["plan"], - "destination": str(destination), - "preflight": preflight_status, - } - - -def record_completion( - repository: Path, - plan_path: Path, - verification_path: Path, - *, - remote: str, - authoritative_completion: Path, - authoritative_verification: Path, - client: PublicClient, -) -> dict[str, str]: - plan = load_plan(plan_path) - plan_tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - plan_record_commit = resolve_tag(client, CONTROL_REPOSITORY, plan_tag) - if plan_record_commit is None: - raise CandidateError(f"release plan tag {plan_tag} is absent") - public_plan = read_public_record( - client, - plan_tag, - plan_record_commit, - "release-plan.json", - ) - validate_recorded_plan(public_plan) - if canonical_json(public_plan) != canonical_json(plan): - raise CandidateError("observed release plan differs from immutable Git authority") - if load_public_supersession(plan, plan_record_commit, client) is not None: - raise CandidateError(f"terminally failed release plan {plan_tag} cannot be completed") - try: - preparation = read_public_record( - client, - plan_tag, - plan_record_commit, - "release-preparation.json", - ) - except CandidateError as error: - if "(404)" not in str(error): - raise - preparation = None - if preparation is not None: - validate_release_preparation(preparation, plan) - candidate = candidate_manifest(plan) - existing = read_existing_completion_record( - repository, - plan, - plan_record_commit, - preparation, - remote=remote, - authoritative_completion=authoritative_completion, - authoritative_verification=authoritative_verification, - ) - if existing is not None: - return existing - - completion = completion_manifest(plan, plan_record_commit, preparation) - canonical_completion = canonical_json(completion) - verification = load_verification(verification_path, candidate) - completion_verification = { - "schema": "durable-workflow.release-candidate-verification/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "release_plan_sha256": manifest_digest(plan), - "public_verification": verification, - } - if preparation is not None: - completion_verification["release_preparation_sha256"] = manifest_digest(preparation) - canonical_verification = canonical_json(completion_verification) - tag = f"{COMPLETION_TAG_PREFIX}{plan['channel']}/{plan['plan']}" - - verification = revalidate_verification(verification, candidate, client) - completion_verification["public_verification"] = verification - canonical_verification = canonical_json(completion_verification) - - with tempfile.NamedTemporaryFile(prefix="release-candidate-index-", delete=False) as index: - index_path = Path(index.name) - try: - env = os.environ.copy() - env["GIT_INDEX_FILE"] = str(index_path) - index_path.unlink(missing_ok=True) - run_git(["read-tree", "--empty"], cwd=repository, env=env) - for filename, content in ( - ("release-candidate.json", canonical_completion), - ("verification.json", canonical_verification), - ): - blob = ( - subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=repository, - env=env, - input=content, - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - run_git(["update-index", "--add", "--cacheinfo", f"100644,{blob},{filename}"], cwd=repository, env=env) - tree = run_git(["write-tree"], cwd=repository, env=env) - commit_env = env | { - "GIT_AUTHOR_NAME": "Durable Workflow Release Observer", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Release Observer", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - } - commit = subprocess.run( - ["git", "commit-tree", tree], - cwd=repository, - env=commit_env, - input=f"Record completed {plan['channel']} release candidate {plan['plan']}\n", - check=True, - text=True, - capture_output=True, - ).stdout.strip() - process = subprocess.run( - ["git", "push", remote, f"{commit}:refs/tags/{tag}"], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if process.returncode: - existing_ref = fetch_existing_record(repository, remote, tag) - if ( - not existing_ref - or read_record_file(repository, existing_ref, "release-candidate.json") != canonical_completion - ): - raise CandidateError(f"cannot publish completed release candidate: {process.stderr.strip()}") - existing_completion = read_record_file(repository, existing_ref, "release-candidate.json") - existing_verification = read_record_file(repository, existing_ref, "verification.json") - try: - existing_verification_value = json.loads(existing_verification) - except json.JSONDecodeError as error: - raise CandidateError( - f"completed release candidate {plan['plan']} has invalid verification authority" - ) from error - validate_completion_verification( - existing_verification_value, - plan, - candidate, - verification, - preparation, - ) - authoritative_completion.write_bytes(existing_completion) - authoritative_verification.write_bytes(existing_verification) - return { - "status": "existing", - "candidate": plan["plan"], - "channel": plan["channel"], - "tag": tag, - "commit": run_git(["rev-parse", f"{existing_ref}^{{commit}}"], cwd=repository), - } - authoritative_completion.write_bytes(canonical_completion) - authoritative_verification.write_bytes(canonical_verification) - return { - "status": "created", - "candidate": plan["plan"], - "channel": plan["channel"], - "tag": tag, - "commit": commit, - } - finally: - index_path.unlink(missing_ok=True) - - -def terminal_failure_state(plan: dict[str, Any], client: PublicClient) -> dict[str, Any] | None: - plan_tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - plan_commit = resolve_tag(client, CONTROL_REPOSITORY, plan_tag) - if plan_commit is None: - raise CandidateError(f"release plan tag {plan_tag} is absent") - supersession = load_public_supersession(plan, plan_commit, client) - if supersession is None: - return None - failure_tag, failure_commit, failure, _successor = supersession - conflicts = failure["conflicts"] - successor = failure["successor_plan"] - reasons = [] - for conflict in conflicts: - if conflict["reason"] == SUPERSESSION_REASON: - detail = f"public source {conflict['observed_commit']}" - elif conflict["reason"] == OCCUPIED_SOURCE_MANIFEST_REASON: - detail = ( - f"occupied planned source tag {conflict['source_tag']['commit']} has manifest version " - f"{conflict['source_manifest']['declared_version']}; successor " - f"{conflict['successor_source_manifest']['source_commit']} declares the next allocation" - ) - else: - detail = ( - f"source manifest declares {conflict['source_manifest']['declared_version']} and " - f"successor source {conflict['successor_source_manifest']['source_commit']} is compatible" - ) - reasons.append( - f"{conflict['component']}: {conflict['version']} is terminally conflicted; " - f"planned source {conflict['planned_commit']}, {detail}" - ) - reason = "; ".join(reasons) - return { - "schema": "durable-workflow.release-state/v1", - "plan": plan["plan"], - "channel": plan["channel"], - "plan_sha256": manifest_digest(plan), - "observed_at": dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"), - "phase": "terminal-failure", - "outcome": "superseded", - "failed_components": [conflict["component"] for conflict in conflicts], - "reason": reason, - "conflicts": conflicts, - "durable_evidence": { - "release_plan": failure["failed_plan"], - "terminal_failure": {"tag": failure_tag, "commit": failure_commit}, - "protected_action": failure["authorization"], - }, - "resume_action": ( - f"Download successor-release-plan.json from {failure_tag}, dispatch the Release plan action for exact " - f"successor {successor['tag']} at sha256 {successor['sha256']}, run its repository Release plan " - "recovery actions, then rerun Release plan observer" - ), - } - - -def observe_plan( - plan: dict[str, Any], preparation: dict[str, Any] | None, client: PublicClient -) -> tuple[dict[str, Any], dict[str, Any]]: - if preparation is not None: - revalidate_release_preparation(preparation, plan, client) - candidate = candidate_manifest(plan) - state: dict[str, Any] = { - "schema": "durable-workflow.release-state/v1", - "plan": plan["plan"], - "channel": plan["channel"], - "plan_sha256": manifest_digest(plan), - "observed_at": dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"), - "phase": "public-artifact-verification", - "outcome": "failed", - "durable_evidence": { - "release_plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "component_actions": "repository Actions runs and public version tags", - }, - "resume_action": OBSERVATION_RECOVERY_ACTION, - } - if preparation is not None: - state["durable_evidence"]["release_preparation_sha256"] = manifest_digest(preparation) - for name, component in COMPONENTS.items(): - version = plan["components"][name]["version"] - encoded = urllib.parse.quote(version, safe="") - try: - release = client.json(f"https://api.github.com/repos/{component.repository}/releases/tags/{encoded}") - except CandidateError as error: - raise CandidateError(f"{name}: GitHub Release lookup failed: {error}") from error - if release.get("draft") or release.get("tag_name") != version: - raise CandidateError(f"{name}: GitHub Release {component.repository}@{version} is not public") - verification = verify_candidate(candidate, client) - state.update( - { - "phase": "complete", - "outcome": "verified", - "components": verification["components"], - "resume_action": "No recovery action is required", - } - ) - return verification, state - - -def failed_observation_state( - plan: dict[str, Any], preparation: dict[str, Any] | None, observed_at: str -) -> dict[str, Any]: - durable_evidence = { - "release_plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "component_actions": "repository Actions runs and public version tags", - } - if preparation is not None: - durable_evidence["release_preparation_sha256"] = manifest_digest(preparation) - return { - "schema": "durable-workflow.release-state/v1", - "plan": plan["plan"], - "channel": plan["channel"], - "plan_sha256": manifest_digest(plan), - "observed_at": observed_at, - "phase": "public-artifact-verification", - "outcome": "failed", - "reason": OBSERVATION_FAILURE_REASON, - "durable_evidence": durable_evidence, - "resume_action": OBSERVATION_RECOVERY_ACTION, - } - - -def validate_observation_handoff( - plan_path: Path, - preparation_path: Path, - candidate_path: Path, - verification_path: Path, - state_path: Path, - output_directory: Path, - *, - authoritative_plan_path: Path, - authoritative_preparation_path: Path, - expected_plan_tag: str, - expected_plan_sha256: str, - expected_preparation_sha256: str, - expected_verification_outcome: str, - client: PublicClient, -) -> dict[str, str]: - plan = load_plan(plan_path) - plan_tag = f"{PLAN_TAG_PREFIX}{plan['plan']}" - if expected_plan_tag != plan_tag: - raise CandidateError("observation handoff does not match the originally selected plan tag") - if expected_plan_sha256 != manifest_digest(plan): - raise CandidateError("observation handoff does not match the originally selected release plan") - preparation = load_release_preparation(preparation_path, plan) if preparation_path.exists() else None - authoritative_plan = load_plan(authoritative_plan_path) - authoritative_preparation = ( - load_release_preparation(authoritative_preparation_path, authoritative_plan) - if authoritative_preparation_path.exists() - else None - ) - if canonical_json(plan) != canonical_json(authoritative_plan): - raise CandidateError("observation release plan differs from current public authority") - preparation_sha256 = manifest_digest(preparation) if preparation is not None else "absent" - authoritative_preparation_sha256 = ( - manifest_digest(authoritative_preparation) if authoritative_preparation is not None else "absent" - ) - if ( - expected_preparation_sha256 != preparation_sha256 - or expected_preparation_sha256 != authoritative_preparation_sha256 - ): - raise CandidateError("observation handoff does not match the originally selected preparation") - candidate = load_manifest(candidate_path) - if canonical_json(candidate) != canonical_json(candidate_manifest(plan)): - raise CandidateError("observation candidate does not match its release plan") - try: - state_raw = state_path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read release observation state: {error}") from error - if len(state_raw) > OBSERVATION_MAX_BYTES: - raise CandidateError(f"release observation state exceeds the {OBSERVATION_MAX_BYTES // 1024} KiB limit") - try: - state = json.loads(state_raw) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise CandidateError(f"cannot read release observation state: {error}") from error - validate_observation_bounds(state) - if not isinstance(state, dict): - raise CandidateError("release observation state must be a JSON object") - observed_at = state.get("observed_at") - if not isinstance(observed_at, str) or not re.fullmatch( - r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z", observed_at - ): - raise CandidateError("release observation state has an invalid observed_at timestamp") - try: - dt.datetime.strptime(observed_at, "%Y-%m-%dT%H:%M:%SZ") - except ValueError as error: - raise CandidateError("release observation state has an invalid observed_at timestamp") from error - if expected_verification_outcome not in {"success", "failure"}: - raise CandidateError("trusted verification-step outcome must be success or failure") - - outcome = state.get("outcome") - verification: dict[str, Any] | None = None - durable_evidence = { - "release_plan_tag": plan_tag, - "component_actions": "repository Actions runs and public version tags", - } - if preparation is not None: - durable_evidence["release_preparation_sha256"] = preparation_sha256 - if outcome == "verified": - if expected_verification_outcome != "success": - raise CandidateError("verified release observation contradicts the trusted verification-step outcome") - submitted_verification = load_verification(verification_path, candidate) - verification = revalidate_verification(submitted_verification, candidate, client) - expected_state = { - "schema": "durable-workflow.release-state/v1", - "plan": plan["plan"], - "channel": plan["channel"], - "plan_sha256": manifest_digest(plan), - "observed_at": observed_at, - "phase": "complete", - "outcome": "verified", - "components": verification["components"], - "durable_evidence": durable_evidence, - "resume_action": "No recovery action is required", - } - elif outcome == "failed": - if expected_verification_outcome != "failure": - raise CandidateError("failed release observation contradicts the trusted verification-step outcome") - expected_state = failed_observation_state(plan, preparation, observed_at) - if verification_path.exists(): - raise CandidateError("failed release observation unexpectedly contains verification evidence") - elif outcome == "superseded": - if expected_verification_outcome != "failure": - raise CandidateError("superseded release observation contradicts the trusted verification-step outcome") - if verification_path.exists(): - raise CandidateError("superseded release observation unexpectedly contains verification evidence") - expected_state = terminal_failure_state(plan, client) - if expected_state is None: - raise CandidateError("superseded release observation has no current public terminal authority") - expected_state["observed_at"] = observed_at - else: - raise CandidateError("release observation state has an invalid outcome") - if state != expected_state: - raise CandidateError("release observation state differs from the writer's trusted reconstruction") - - trusted_state = expected_state - trusted_state["observed_at"] = dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - output_directory.mkdir(parents=True, exist_ok=True) - (output_directory / "release-plan.json").write_bytes(canonical_json(plan)) - if preparation is not None: - (output_directory / "release-preparation.json").write_bytes(canonical_json(preparation)) - (output_directory / "candidate-verifier-input.json").write_bytes(canonical_json(candidate)) - (output_directory / "release-state.json").write_bytes(canonical_json(trusted_state)) - if verification is not None: - (output_directory / "verification.json").write_bytes(canonical_json(verification)) - return { - "channel": plan["channel"], - "outcome": str(outcome), - "plan": plan["plan"], - "tag": plan_tag, - } - - -def validate_observation_bounds(value: Any, context: str = "release observation state", depth: int = 0) -> None: - if depth > OBSERVATION_MAX_DEPTH: - raise CandidateError(f"{context} exceeds the maximum nesting depth") - if isinstance(value, dict): - if len(value) > OBSERVATION_MAX_ITEMS: - raise CandidateError(f"{context} contains too many object fields") - for key, nested in value.items(): - if not isinstance(key, str) or not key or len(key) > 128: - raise CandidateError(f"{context} contains an invalid object key") - validate_observation_bounds(nested, f"{context}.{key}", depth + 1) - elif isinstance(value, list): - if len(value) > OBSERVATION_MAX_ITEMS: - raise CandidateError(f"{context} contains too many array items") - for index, nested in enumerate(value): - validate_observation_bounds(nested, f"{context}[{index}]", depth + 1) - elif isinstance(value, str): - if len(value) > OBSERVATION_MAX_TEXT or "\x00" in value: - raise CandidateError(f"{context} contains oversized or invalid text") - elif value is not None and type(value) not in {bool, int}: - raise CandidateError(f"{context} contains an unsupported JSON value") - elif type(value) is int and not -(2**63) <= value <= 2**63 - 1: - raise CandidateError(f"{context} contains an oversized integer") - - -def discover_plan(client: PublicClient, requested_tag: str | None) -> tuple[str, dict[str, Any], dict[str, Any] | None]: - if requested_tag: - tag = requested_tag - if not tag.startswith(PLAN_TAG_PREFIX): - raise CandidateError(f"release plan tag must start with {PLAN_TAG_PREFIX}") - else: - - class DiscoveryClient: - def json(self, url: str, **options: Any) -> Any: - try: - return client.json(url, **options) - except CandidateError as error: - if "(404)" in str(error): - raise recovery_discovery.NotFound(str(error), "plan-discovery") from error - raise - - def bytes(self, url: str, **options: Any) -> bytes: - try: - return client.bytes(url, **options) - except CandidateError as error: - if "(404)" in str(error): - raise recovery_discovery.NotFound(str(error), "plan-discovery") from error - raise - - try: - tag = recovery_discovery.select_implicit_plan_authority(DiscoveryClient())["tag"] - except recovery_discovery.RecoveryError as error: - raise CandidateError(str(error)) from error - commit = resolve_tag(client, CONTROL_REPOSITORY, tag) - if commit is None: - raise CandidateError(f"release plan tag {tag} does not exist") - plan = read_public_record(client, tag, commit, "release-plan.json") - validate_recorded_plan(plan) - if tag != f"{PLAN_TAG_PREFIX}{plan['plan']}": - raise CandidateError("release plan tag and document identity differ") - try: - preparation = read_public_record(client, tag, commit, "release-preparation.json") - except CandidateError as error: - if "(404)" not in str(error): - raise - preparation = None - if preparation is not None: - validate_release_preparation(preparation, plan) - release = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/releases/tags/{urllib.parse.quote(tag, safe='')}" - ) - assets = {asset.get("name"): asset for asset in release.get("assets", [])} - records = [("release-plan.json", plan)] - if preparation is not None: - records.append(("release-preparation.json", preparation)) - for filename, value in records: - asset = assets.get(filename) - if not isinstance(asset, dict) or not isinstance(asset.get("browser_download_url"), str): - raise CandidateError(f"release plan {tag} lacks durable {filename} mirror") - if client.bytes(asset["browser_download_url"]) != canonical_json(value): - raise CandidateError(f"release plan {tag} {filename} mirror differs from Git authority") - return tag, plan, preparation - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - - validate = subparsers.add_parser("validate") - validate.add_argument("source", type=Path) - validate.add_argument("destination", type=Path) - - validate_current = subparsers.add_parser("validate-current") - validate_current.add_argument("plan", type=Path) - validate_current.add_argument("candidate", type=Path) - validate_current.add_argument("plan_destination", type=Path) - validate_current.add_argument("candidate_destination", type=Path) - - materialize_current = subparsers.add_parser("materialize-current") - materialize_current.add_argument("plan", type=Path) - materialize_current.add_argument("plan_destination", type=Path) - materialize_current.add_argument("candidate_destination", type=Path) - materialize_current.add_argument("--github-output", type=Path) - - current_completion = subparsers.add_parser( - "current-completion", - help="read and validate an already-completed current authority without regenerating evidence", - ) - current_completion.add_argument("plan", type=Path) - current_completion.add_argument("--remote", default="origin") - current_completion.add_argument("--authoritative-plan", required=True, type=Path) - current_completion.add_argument("--authoritative-preparation", required=True, type=Path) - current_completion.add_argument("--authoritative-completion", required=True, type=Path) - current_completion.add_argument("--authoritative-verification", required=True, type=Path) - current_completion.add_argument("--github-output", type=Path) - - stage_current_evidence = subparsers.add_parser( - "stage-current-evidence", - help="validate and atomically stage current-plan evidence for retention", - ) - stage_current_evidence.add_argument("plan", type=Path) - stage_current_evidence.add_argument("destination", type=Path) - stage_current_evidence.add_argument("--completion-status", required=True, choices=("new", "existing")) - stage_current_evidence.add_argument("--authoritative-plan", required=True, type=Path) - stage_current_evidence.add_argument("--authoritative-preparation", required=True, type=Path) - stage_current_evidence.add_argument("--authoritative-completion", required=True, type=Path) - stage_current_evidence.add_argument("--authoritative-verification", required=True, type=Path) - stage_current_evidence.add_argument("--preflight", type=Path) - - verify_recovery_authority = subparsers.add_parser("verify-recovery-authority") - verify_recovery_authority.add_argument("authority", type=Path) - verify_recovery_authority.add_argument("evidence", type=Path) - - preflight = subparsers.add_parser("preflight") - preflight.add_argument("plan", type=Path) - preflight.add_argument("evidence", type=Path) - preflight.add_argument("--preparation", required=True, type=Path) - preflight.add_argument("--release-date", required=True) - - check = subparsers.add_parser("check") - check.add_argument("plan", type=Path) - check.add_argument("--remote", default="origin") - - record = subparsers.add_parser("record") - record.add_argument("plan", type=Path) - record.add_argument("preparation", type=Path) - record.add_argument("--remote", default="origin") - record.add_argument("--authoritative-plan", required=True, type=Path) - record.add_argument("--authoritative-preparation", required=True, type=Path) - record.add_argument("--github-output", type=Path) - - record_current_authorization = subparsers.add_parser("record-current-authorization") - record_current_authorization.add_argument("plan", type=Path) - record_current_authorization.add_argument("--remote", default="origin") - record_current_authorization.add_argument( - "--authoritative-authorization", - required=True, - type=Path, - ) - record_current_authorization.add_argument("--github-output", type=Path) - - supersede = subparsers.add_parser("prepare-supersession") - supersede.add_argument("failed_plan_tag") - supersede.add_argument("conflict_components") - supersede.add_argument("successor_plan", type=Path) - supersede.add_argument("record", type=Path) - supersede.add_argument("authoritative_successor", type=Path) - supersede.add_argument("--actor", required=True) - supersede.add_argument("--run-id", required=True) - supersede.add_argument("--run-attempt", required=True) - supersede.add_argument("--workflow-ref", required=True) - supersede.add_argument("--workflow-commit", required=True) - - record_supersession_parser = subparsers.add_parser("record-supersession") - record_supersession_parser.add_argument("record", type=Path) - record_supersession_parser.add_argument("successor_plan", type=Path) - record_supersession_parser.add_argument("--remote", default="origin") - record_supersession_parser.add_argument("--authoritative-record", required=True, type=Path) - record_supersession_parser.add_argument("--authoritative-successor", required=True, type=Path) - record_supersession_parser.add_argument("--github-output", type=Path) - - validate_supersession = subparsers.add_parser("validate-supersession-handoff") - validate_supersession.add_argument("record", type=Path) - validate_supersession.add_argument("successor_plan", type=Path) - validate_supersession.add_argument("destination", type=Path) - validate_supersession.add_argument("--expected-failed-plan-tag", required=True) - validate_supersession.add_argument("--expected-conflict-components", required=True) - - discover = subparsers.add_parser("discover") - discover.add_argument("destination", type=Path) - discover.add_argument("--preparation", required=True, type=Path) - discover.add_argument("--tag") - discover.add_argument("--allow-empty", action="store_true") - discover.add_argument("--github-output", type=Path) - - observe = subparsers.add_parser("observe") - observe.add_argument("plan", type=Path) - observe.add_argument("preparation", type=Path) - observe.add_argument("candidate", type=Path) - observe.add_argument("verification", type=Path) - observe.add_argument("state", type=Path) - - validate_observation = subparsers.add_parser("validate-observation-handoff") - validate_observation.add_argument("plan", type=Path) - validate_observation.add_argument("preparation", type=Path) - validate_observation.add_argument("candidate", type=Path) - validate_observation.add_argument("verification", type=Path) - validate_observation.add_argument("state", type=Path) - validate_observation.add_argument("output_directory", type=Path) - validate_observation.add_argument("--authoritative-plan", required=True, type=Path) - validate_observation.add_argument("--authoritative-preparation", required=True, type=Path) - validate_observation.add_argument("--expected-plan-tag", required=True) - validate_observation.add_argument("--expected-plan-sha256", required=True) - validate_observation.add_argument("--expected-preparation-sha256", required=True) - validate_observation.add_argument("--expected-verification-outcome", required=True) - validate_observation.add_argument("--github-output", type=Path) - - complete = subparsers.add_parser("complete") - complete.add_argument("plan", type=Path) - complete.add_argument("verification", type=Path) - complete.add_argument("--remote", default="origin") - complete.add_argument("--authoritative-completion", required=True, type=Path) - complete.add_argument("--authoritative-verification", required=True, type=Path) - complete.add_argument("--github-output", type=Path) - - args = parser.parse_args() - token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") - try: - if args.command == "validate": - plan = load_plan(args.source) - args.destination.write_bytes(canonical_json(plan)) - elif args.command == "validate-current": - plan, candidate = validate_current_plan_authority(args.plan, args.candidate) - args.plan_destination.write_bytes(canonical_json(plan)) - args.candidate_destination.write_bytes(canonical_json(candidate)) - elif args.command == "materialize-current": - plan, candidate = materialize_current_plan_authority(args.plan) - args.plan_destination.write_bytes(canonical_json(plan)) - args.candidate_destination.write_bytes(canonical_json(candidate)) - write_github_output( - args.github_output, - { - "candidate": plan["plan"], - "channel": plan["channel"], - "plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "completion_tag": f"{COMPLETION_TAG_PREFIX}{plan['channel']}/{plan['plan']}", - }, - ) - elif args.command == "current-completion": - result = read_current_completion_authority( - Path.cwd(), - args.plan, - remote=args.remote, - authoritative_plan=args.authoritative_plan, - authoritative_preparation=args.authoritative_preparation, - authoritative_completion=args.authoritative_completion, - authoritative_verification=args.authoritative_verification, - ) - write_github_output(args.github_output, result) - print(json.dumps(result, sort_keys=True)) - elif args.command == "stage-current-evidence": - result = stage_current_plan_evidence( - args.plan, - args.destination, - completion_status=args.completion_status, - authoritative_plan=args.authoritative_plan, - authoritative_preparation=args.authoritative_preparation, - authoritative_completion=args.authoritative_completion, - authoritative_verification=args.authoritative_verification, - preflight=args.preflight, - ) - print(json.dumps(result, sort_keys=True)) - elif args.command == "verify-recovery-authority": - evidence = verify_local_recovery_workflow_authority( - args.authority, - PublicClient(token), - ) - args.evidence.write_bytes(canonical_json(evidence)) - elif args.command == "preflight": - plan = load_plan(args.plan) - evidence = preflight_plan( - plan, - PublicClient(token), - release_date=args.release_date, - ) - preparation = evidence.pop("release_preparation") - args.preparation.write_bytes(canonical_json(preparation)) - evidence["release_preparation_sha256"] = manifest_digest(preparation) - args.evidence.write_bytes( - canonical_json( - { - "schema": "durable-workflow.release-plan-preflight/v1", - "plan": plan["plan"], - "channel": plan["channel"], - "outcome": "verified", - **evidence, - } - ) - ) - elif args.command == "check": - print(json.dumps(check_plan_compatibility(Path.cwd(), args.plan, remote=args.remote), sort_keys=True)) - elif args.command == "record": - result = record_plan( - Path.cwd(), - args.plan, - args.preparation, - remote=args.remote, - authoritative_plan=args.authoritative_plan, - authoritative_preparation=args.authoritative_preparation, - ) - write_github_output(args.github_output, result) - print(json.dumps(result, sort_keys=True)) - elif args.command == "record-current-authorization": - result = record_current_plan_authorization( - Path.cwd(), - args.plan, - remote=args.remote, - authoritative_authorization=args.authoritative_authorization, - ) - write_github_output(args.github_output, result) - print(json.dumps(result, sort_keys=True)) - elif args.command == "prepare-supersession": - successor = load_plan(args.successor_plan) - record_value, authoritative_successor = prepare_supersession( - args.failed_plan_tag, - parse_conflict_components(args.conflict_components), - successor, - PublicClient(token), - actor=args.actor, - run_id=args.run_id, - run_attempt=args.run_attempt, - workflow_ref=args.workflow_ref, - workflow_commit=args.workflow_commit, - ) - args.record.write_bytes(canonical_json(record_value)) - args.authoritative_successor.write_bytes(canonical_json(authoritative_successor)) - elif args.command == "record-supersession": - result = record_supersession( - Path.cwd(), - args.record, - args.successor_plan, - remote=args.remote, - authoritative_record=args.authoritative_record, - authoritative_successor=args.authoritative_successor, - client=PublicClient(token), - ) - write_github_output(args.github_output, result) - print(json.dumps(result, sort_keys=True)) - elif args.command == "validate-supersession-handoff": - validate_supersession_handoff( - args.record, - args.successor_plan, - args.destination, - expected_failed_plan_tag=args.expected_failed_plan_tag, - expected_conflict_components=args.expected_conflict_components, - ) - elif args.command == "discover": - discovered = discover_plan(PublicClient(token), args.tag) - tag, plan, preparation = discovered - args.destination.write_bytes(canonical_json(plan)) - if preparation is not None: - args.preparation.write_bytes(canonical_json(preparation)) - values = { - "available": "true", - "tag": tag, - "plan": plan["plan"], - "channel": plan["channel"], - "plan_sha256": manifest_digest(plan), - "preparation_sha256": manifest_digest(preparation) if preparation is not None else "absent", - } - write_github_output(args.github_output, values) - print(json.dumps(values, sort_keys=True)) - elif args.command == "observe": - plan = load_plan(args.plan) - preparation = load_release_preparation(args.preparation, plan) if args.preparation.exists() else None - candidate = candidate_manifest(plan) - args.candidate.write_bytes(canonical_json(candidate)) - client = PublicClient(token) - terminal_state = terminal_failure_state(plan, client) - if terminal_state is not None: - args.state.write_bytes(canonical_json(terminal_state)) - raise CandidateError(terminal_state["reason"]) - try: - verification, state = observe_plan(plan, preparation, client) - except CandidateError: - failed_state = failed_observation_state( - plan, - preparation, - dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"), - ) - args.state.write_bytes(canonical_json(failed_state)) - raise - args.verification.write_bytes(canonical_json(verification)) - args.state.write_bytes(canonical_json(state)) - elif args.command == "validate-observation-handoff": - result = validate_observation_handoff( - args.plan, - args.preparation, - args.candidate, - args.verification, - args.state, - args.output_directory, - authoritative_plan_path=args.authoritative_plan, - authoritative_preparation_path=args.authoritative_preparation, - expected_plan_tag=args.expected_plan_tag, - expected_plan_sha256=args.expected_plan_sha256, - expected_preparation_sha256=args.expected_preparation_sha256, - expected_verification_outcome=args.expected_verification_outcome, - client=PublicClient(token), - ) - write_github_output(args.github_output, result) - print(json.dumps(result, sort_keys=True)) - elif args.command == "complete": - result = record_completion( - Path.cwd(), - args.plan, - args.verification, - remote=args.remote, - authoritative_completion=args.authoritative_completion, - authoritative_verification=args.authoritative_verification, - client=PublicClient(token), - ) - write_github_output(args.github_output, result) - print(json.dumps(result, sort_keys=True)) - except PublicInfrastructureError as error: - print(f"release plan infrastructure failed: {error}", file=sys.stderr) - return INFRASTRUCTURE_EXIT_CODE - except CandidateError as error: - print(f"release plan error: {error}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/release_recovery_consumer_conformance.py b/scripts/release_recovery_consumer_conformance.py deleted file mode 100644 index c88aa1b..0000000 --- a/scripts/release_recovery_consumer_conformance.py +++ /dev/null @@ -1,1961 +0,0 @@ -#!/usr/bin/env python3 -"""Run the versioned release-recovery consumer conformance contract.""" - -from __future__ import annotations - -import argparse -import copy -import datetime as dt -import hashlib -import importlib.util -import json -import os -import re -import subprocess -import sys -import tempfile -import urllib.error -import urllib.parse -import urllib.request -from pathlib import Path, PurePosixPath -from types import ModuleType -from typing import Any -from unittest import mock - -CONTRACT_SCHEMA = "durable-workflow.release-recovery-consumer-conformance/v1" -ADAPTER_SCHEMA = "durable-workflow.release-recovery-consumer-adapter/v1" -EVIDENCE_SCHEMA = "durable-workflow.release-recovery-consumer-conformance-evidence/v1" -VERSION_PATTERN = re.compile( - r"(?P0|[1-9][0-9]*)\.(?P0|[1-9][0-9]*)\.(?P0|[1-9][0-9]*)" - r"(?:-(?P(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)" - r"(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?" - r"(?:\+(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?" -) -COMMIT_PATTERN = re.compile(r"[0-9a-f]{40}") -SHA256_PATTERN = re.compile(r"[0-9a-f]{64}") -CURRENT_PLAN_SCHEMA = "durable-workflow.release-plan/v2" -HISTORICAL_PLAN_SCHEMA = "durable-workflow.release-plan/v1" -EXPECTED_LEGACY_PLAN_DIGESTS = frozenset( - { - "0be354d5ea603170b6aef8ae0d9861886c4ccc0f75e6acb763239b30dd5d8ba3", - "295a3f654716ea8cd8dc693c1cd15a4b487737e5f01184bad7363fbde6717c40", - "486d9ef7c5a7f4443a89566cab33d7f2bccc518254ab6698d918a431d6a1c9ce", - "498804a2c7fd5b0e34f93ef080bea3073bc98e420e8bf84a98ca4cdb94729973", - "7bd737c92f139eec33026bc88a6491dc635d819a87a61c985e14e06aca645582", - "80e88698fa37b6d738d111dd2be3e3c145607973f8147c54cc25e5d91d415b17", - "9c0a5879652a2d5f4806a9167399687328c1764fa10dbc8d76215b43ac83b9d6", - "db90616c98f305c61d7eb2fb9ed03cc28f06963e9ca020c8ef6d7c6a8557f7bc", - "e1fc6e20c9d2ded0b5e7ac4d6be75ba861d31fc4b2db651dc0272dca623f2c7f", - } -) -REQUIRED_CASES = ( - "immutable-plan-enumeration", - "current-plan-schema", - "completed-plan-lifecycle", - "superseded-plan-lifecycle", - "exact-successor-identity", - "malformed-authority-rejection", - "continuity-ambiguity-rejection", - "explicit-terminal-plan-rejection", - "bounded-authority-convergence", - "release-candidate-beta-qualification", - "authoritative-rc-foundation", - "scheduled-empty-no-op", - "trusted-github-api-transport", - "transport-fail-closed-publication", -) -CONSUMERS = ( - { - "component": "workflow", - "repository": "durable-workflow/workflow", - "target_branch": "v2", - }, - { - "component": "waterline", - "repository": "durable-workflow/waterline", - "target_branch": "v2", - }, - { - "component": "server", - "repository": "durable-workflow/server", - "target_branch": "main", - }, - { - "component": "cli", - "repository": "durable-workflow/cli", - "target_branch": "main", - }, - { - "component": "sdk-php", - "repository": "durable-workflow/sdk-php", - "target_branch": "main", - }, - { - "component": "sdk-python", - "repository": "durable-workflow/sdk-python", - "target_branch": "main", - }, - { - "component": "sdk-rust", - "repository": "durable-workflow/sdk-rust", - "target_branch": "main", - }, -) - - -class ConformanceError(RuntimeError): - """The contract, adapter, or consumer does not conform.""" - - -def canonical_json(value: Any) -> bytes: - return (json.dumps(value, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - - -def sha256_bytes(value: bytes) -> str: - return hashlib.sha256(value).hexdigest() - - -def load_json_object(path: Path, label: str) -> tuple[dict[str, Any], bytes]: - try: - raw = path.read_bytes() - value = json.loads(raw) - except (OSError, json.JSONDecodeError) as error: - raise ConformanceError(f"{label} is not readable canonical JSON: {path}") from error - if not isinstance(value, dict): - raise ConformanceError(f"{label} must be a JSON object: {path}") - if raw != canonical_json(value): - raise ConformanceError(f"{label} must use canonical sorted JSON formatting: {path}") - return value, raw - - -def relative_file(root: Path, value: Any, label: str) -> Path: - if not isinstance(value, str) or not value: - raise ConformanceError(f"{label} must be a non-empty repository-relative path") - path = PurePosixPath(value) - if path.is_absolute() or ".." in path.parts: - raise ConformanceError(f"{label} must stay within the repository") - repository_root = root.resolve() - resolved = root.joinpath(*path.parts).resolve() - try: - resolved.relative_to(repository_root) - except ValueError as error: - raise ConformanceError(f"{label} must stay within the repository") from error - if not resolved.is_file(): - raise ConformanceError(f"{label} does not exist: {value}") - return resolved - - -def validate_contract( - contract: dict[str, Any], - contract_raw: bytes, - suite_path: Path, -) -> str: - expected_keys = {"cases", "consumers", "schema", "suite", "version"} - if set(contract) != expected_keys or contract.get("schema") != CONTRACT_SCHEMA: - raise ConformanceError("shared contract does not satisfy the v1 document shape") - version = contract.get("version") - if not isinstance(version, str) or VERSION_PATTERN.fullmatch(version) is None: - raise ConformanceError("shared contract version must be exact SemVer") - if contract.get("consumers") != list(CONSUMERS): - raise ConformanceError("shared contract must declare the exact seven-consumer target topology") - suite = contract.get("suite") - if ( - not isinstance(suite, dict) - or set(suite) != {"sha256"} - or not isinstance(suite.get("sha256"), str) - or SHA256_PATTERN.fullmatch(suite["sha256"]) is None - ): - raise ConformanceError("shared contract suite must contain one exact SHA-256") - actual_suite_sha256 = sha256_bytes(suite_path.read_bytes()) - if suite["sha256"] != actual_suite_sha256: - raise ConformanceError("shared conformance runner differs from the suite digest declared by the contract") - cases = contract.get("cases") - if not isinstance(cases, list): - raise ConformanceError("shared contract cases must be a JSON array") - case_ids: list[str] = [] - for case in cases: - if ( - not isinstance(case, dict) - or set(case) != {"id", "requirement"} - or not isinstance(case.get("id"), str) - or not isinstance(case.get("requirement"), str) - or not case["requirement"] - ): - raise ConformanceError("every shared contract case needs exactly an id and requirement") - case_ids.append(case["id"]) - if tuple(case_ids) != REQUIRED_CASES: - raise ConformanceError("shared contract omits or reorders required authority cases") - return sha256_bytes(contract_raw) - - -def parse_semver( - version: Any, - label: str, -) -> tuple[tuple[int, int, int], tuple[str, ...] | None]: - if not isinstance(version, str): - raise ConformanceError(f"{label} must be exact SemVer") - match = VERSION_PATTERN.fullmatch(version) - if match is None: - raise ConformanceError(f"{label} must be exact SemVer") - core = tuple(int(match.group(field)) for field in ("major", "minor", "patch")) - prerelease = match.group("prerelease") - return core, None if prerelease is None else tuple(prerelease.split(".")) - - -def compare_semver_precedence(left: str, right: str) -> int: - left_core, left_prerelease = parse_semver(left, "left version") - right_core, right_prerelease = parse_semver(right, "right version") - if left_core != right_core: - return 1 if left_core > right_core else -1 - if left_prerelease is None or right_prerelease is None: - if left_prerelease == right_prerelease: - return 0 - return 1 if left_prerelease is None else -1 - for left_identifier, right_identifier in zip(left_prerelease, right_prerelease, strict=False): - if left_identifier == right_identifier: - continue - left_numeric = left_identifier.isdigit() - right_numeric = right_identifier.isdigit() - if left_numeric and right_numeric: - return 1 if int(left_identifier) > int(right_identifier) else -1 - if left_numeric != right_numeric: - return -1 if left_numeric else 1 - return 1 if left_identifier > right_identifier else -1 - if len(left_prerelease) == len(right_prerelease): - return 0 - return 1 if len(left_prerelease) > len(right_prerelease) else -1 - - -def validate_adapter( - adapter: dict[str, Any], - contract: dict[str, Any], - contract_sha256: str, - repository_root: Path, - current_suite: Path, - current_contract: Path, -) -> tuple[Path, list[str]]: - expected_keys = { - "component", - "consumer", - "contract", - "distribution_verification", - "repository", - "schema", - "suite", - "target_branch", - } - if set(adapter) != expected_keys or adapter.get("schema") != ADAPTER_SCHEMA: - raise ConformanceError("consumer adapter does not satisfy the v1 document shape") - identity = { - "component": adapter.get("component"), - "repository": adapter.get("repository"), - "target_branch": adapter.get("target_branch"), - } - if identity not in CONSUMERS or identity not in contract["consumers"]: - raise ConformanceError("consumer adapter is not in the contract target topology") - contract_pin = adapter.get("contract") - if not isinstance(contract_pin, dict) or set(contract_pin) != {"path", "sha256", "version"}: - raise ConformanceError("consumer adapter does not declare the exact contract pin shape") - adapter_contract = relative_file(repository_root, contract_pin["path"], "adapter contract") - invoked_contract = current_contract.resolve() - if adapter_contract != invoked_contract: - raise ConformanceError("the invoked contract is not the adapter's declared contract") - declared_contract, declared_contract_raw = load_json_object(adapter_contract, "adapter contract") - if declared_contract.get("version") != contract_pin.get("version") or sha256_bytes( - declared_contract_raw - ) != contract_pin.get("sha256"): - raise ConformanceError("the adapter's declared contract does not match its version and digest pins") - if contract_pin.get("version") != contract["version"] or contract_pin.get("sha256") != contract_sha256: - raise ConformanceError("consumer adapter does not pin the exact invoked contract version and digest") - suite_pin = adapter.get("suite") - if ( - not isinstance(suite_pin, dict) - or set(suite_pin) != {"path", "sha256"} - or suite_pin.get("sha256") != contract["suite"]["sha256"] - ): - raise ConformanceError("consumer adapter does not pin the exact shared suite digest") - adapter_suite = relative_file(repository_root, suite_pin["path"], "adapter suite") - if adapter_suite.resolve() != current_suite.resolve(): - raise ConformanceError("the invoked suite is not the adapter's declared suite") - consumer = relative_file(repository_root, adapter.get("consumer"), "adapter consumer") - distribution = adapter.get("distribution_verification") - if ( - not isinstance(distribution, dict) - or set(distribution) != {"command"} - or not isinstance(distribution.get("command"), list) - or len(distribution["command"]) < 2 - or distribution["command"][0] != "{python}" - or not all(isinstance(item, str) and item for item in distribution["command"]) - ): - raise ConformanceError("distribution verification must declare a local Python command") - relative_file( - repository_root, - distribution["command"][1], - "distribution verification entry point", - ) - return consumer, distribution["command"] - - -def previous_contract( - repository_root: Path, - contract_path: Path, - previous_ref: str | None, -) -> dict[str, Any] | None: - if previous_ref is None: - return None - if COMMIT_PATTERN.fullmatch(previous_ref) is None or previous_ref == "0" * 40: - raise ConformanceError("previous contract ref must be an exact nonzero commit") - relative = contract_path.resolve().relative_to(repository_root.resolve()).as_posix() - commit = subprocess.run( - ["git", "cat-file", "-e", f"{previous_ref}^{{commit}}"], - cwd=repository_root, - check=False, - capture_output=True, - text=False, - ) - if commit.returncode != 0: - raise ConformanceError("previous contract commit is unavailable") - tree = subprocess.run( - ["git", "ls-tree", "--name-only", "-z", previous_ref, "--", relative], - cwd=repository_root, - check=False, - capture_output=True, - text=False, - ) - if tree.returncode != 0: - raise ConformanceError("previous contract commit cannot be inspected") - if tree.stdout == b"": - return None - if tree.stdout != f"{relative}\0".encode(): - raise ConformanceError("previous contract path could not be resolved exactly") - result = subprocess.run( - ["git", "show", f"{previous_ref}:{relative}"], - cwd=repository_root, - check=False, - capture_output=True, - text=False, - ) - if result.returncode != 0: - raise ConformanceError("previous shared contract is unreadable") - try: - value = json.loads(result.stdout) - except json.JSONDecodeError as error: - raise ConformanceError("previous shared contract is not valid JSON") from error - if not isinstance(value, dict): - raise ConformanceError("previous shared contract is not a JSON object") - return value - - -def require_versioned_contract_change( - previous: dict[str, Any] | None, - current: dict[str, Any], -) -> None: - if previous is None or previous == current: - return - previous_version = previous.get("version") - current_version = current.get("version") - parse_semver(previous_version, "previous shared contract version") - parse_semver(current_version, "current shared contract version") - if compare_semver_precedence(current_version, previous_version) <= 0: - raise ConformanceError("shared contract content changed without a strictly advancing SemVer version") - - -def load_consumer(path: Path) -> ModuleType: - parent = str(path.parent) - if parent not in sys.path: - sys.path.insert(0, parent) - module_name = f"release_recovery_consumer_{sha256_bytes(str(path).encode())[:12]}" - spec = importlib.util.spec_from_file_location(module_name, path) - if spec is None or spec.loader is None: - raise ConformanceError(f"cannot load recovery consumer: {path}") - module = importlib.util.module_from_spec(spec) - sys.modules[module_name] = module - try: - spec.loader.exec_module(module) - except Exception as error: - raise ConformanceError(f"cannot import recovery consumer: {path}") from error - return module - - -def plan(module: ModuleType, identity: str = "conformance") -> dict[str, Any]: - components: dict[str, dict[str, str]] = {} - for index, name in enumerate(module.COMPONENTS): - components[name] = { - "version": (f"2.0.0-beta.{index + 1}" if name in {"workflow", "waterline"} else f"1.{index}.0"), - "commit": f"{index + 1:040x}", - } - return { - "schema": module.SCHEMA, - "plan": identity, - "channel": "beta", - "foundation": { - "tag": module.FOUNDATION_TAG, - "commit": module.FOUNDATION_COMMIT, - }, - "components": components, - "beta_authorization": { - "tag": f"beta-authorization/{identity}", - "commit": "f" * 40, - }, - } - - -def legacy_beta_one_plan() -> dict[str, Any]: - return { - "schema": HISTORICAL_PLAN_SCHEMA, - "plan": "beta-1-e743e3760000", - "channel": "beta", - "foundation": { - "tag": "beta-candidate/beta-continuity-foundation", - "commit": "4995052410bd4301c5796ffba54e0b6d2f490ed1", - }, - "components": { - "workflow": { - "version": "2.0.0-beta.1", - "commit": "22bbf2a1469f4a38b1a6e1006ca8e46835c2fea4", - }, - "waterline": { - "version": "2.0.0-beta.1", - "commit": "0fb3caaba1e8a77f9bfa63ba3dcb2bcbaa825c31", - }, - "server": { - "version": "0.2.699", - "commit": "d6e8fb6c76c1d71cc7d3a1d38bdebd324150acad", - }, - "cli": { - "version": "0.1.95", - "commit": "bc036e94604329612b65a2a9effe2e929f91f4e1", - }, - "sdk-php": { - "version": "0.1.16", - "commit": "3b79813b1bbcb811277cc30d8dcfc359ea53f65c", - }, - "sdk-python": { - "version": "0.4.106", - "commit": "13037ddcb1f55d72c24256591e346b991ad64273", - }, - "sdk-rust": { - "version": "0.1.22", - "commit": "6fa98425c8ec7690ef96f8296a21407aa8d03067", - }, - }, - "beta_authorization": { - "tag": "beta-authorization/beta-1-e743e3760000", - "commit": "bef98bfd61b604d48459c15e968e3ace8e5124b0", - }, - } - - -def authority( - module: ModuleType, - candidate: dict[str, Any], - lifecycle: str, - successor: dict[str, Any] | None = None, -) -> dict[str, Any]: - return { - "tag": f"{module.PLAN_TAG_PREFIX}{candidate['plan']}", - "commit": "a" * 40, - "recorded_at": dt.datetime(2026, 7, 25, tzinfo=dt.UTC), - "plan": candidate, - "preparation": None, - "lifecycle": lifecycle, - "successor": successor, - } - - -def expect_recovery_error(module: ModuleType, action: Any, message: str) -> None: - try: - action() - except module.RecoveryError: - return - raise ConformanceError(message) - - -def continuity_resolution_qualification(module: ModuleType) -> dict[str, Any]: - return { - "repository": module.CONTROL_REPOSITORY, - "workflow": module.CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW, - "event": module.CONTINUITY_RESOLUTION_QUALIFICATION_EVENT, - "head_branch": module.CONTINUITY_RESOLUTION_QUALIFICATION_BRANCH, - "head_sha": "9" * 40, - "run_id": 987, - "run_attempt": 2, - "status": "completed", - "conclusion": "success", - } - - -def continuity_resolution_qualification_run( - module: ModuleType, - qualification: dict[str, Any], -) -> dict[str, Any]: - return { - "id": qualification["run_id"], - "run_attempt": qualification["run_attempt"], - "repository": {"full_name": module.CONTROL_REPOSITORY}, - "head_repository": {"full_name": module.CONTROL_REPOSITORY}, - "path": ( - f"{module.CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW}" - f"@{module.CONTINUITY_RESOLUTION_QUALIFICATION_BRANCH}" - ), - "event": qualification["event"], - "head_branch": qualification["head_branch"], - "head_sha": qualification["head_sha"], - "status": qualification["status"], - "conclusion": qualification["conclusion"], - } - - -def continuity_resolution_fixture( - module: ModuleType, -) -> tuple[dict[str, Any], list[dict[str, Any]], dict[str, Any], dict[str, Any]]: - interrupted_plan = {"plan": "interrupted-conformance"} - interrupted = { - "tag": "release-plan/interrupted-conformance", - "commit": "a" * 40, - "plan": interrupted_plan, - } - interruption = { - "tag": "beta-continuity/interrupted-conformance/interrupted", - "commit": "b" * 40, - "evidence_sha256": "c" * 64, - } - successors: list[dict[str, Any]] = [] - for index, name in enumerate(("first-successor", "second-successor"), start=1): - successors.append( - { - "tag": f"release-plan/{name}", - "supersession": { - **interruption, - "continuity_claim": { - "plan": { - "tag": f"release-plan/{name}", - "commit": str(index) * 40, - "sha256": str(index + 2) * 64, - }, - "acceptance": { - "tag": f"beta-continuity/{name}/accepted", - "commit": str(index + 4) * 40, - "sha256": str(index + 6) * 64, - }, - }, - }, - } - ) - claims = sorted( - (successor["supersession"]["continuity_claim"] for successor in successors), - key=lambda claim: claim["plan"]["tag"], - ) - qualification = continuity_resolution_qualification(module) - resolution = { - "schema": module.CONTINUITY_RESOLUTION_SCHEMA, - "qualification": qualification, - "interruption": { - "plan": { - "tag": interrupted["tag"], - "commit": interrupted["commit"], - "sha256": module.manifest_digest(interrupted_plan), - }, - "evidence": { - "tag": interruption["tag"], - "commit": interruption["commit"], - "sha256": interruption["evidence_sha256"], - }, - }, - "successor_claims": claims, - "selected_successor": claims[1]["plan"], - } - return ( - interrupted, - successors, - resolution, - continuity_resolution_qualification_run(module, qualification), - ) - - -def continuity_resolution_tag( - module: ModuleType, - interrupted: dict[str, Any], - resolution: dict[str, Any], -) -> str: - return ( - f"{module.CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted['plan']['plan']}/" - f"{module.manifest_digest(resolution)}" - ) - - -def exercise_continuity_resolution( - module: ModuleType, - interrupted: dict[str, Any], - successors: list[dict[str, Any]], - resolution: Any, - resolution_tags: list[str], - resolution_commit: str | None, - qualification_run: Any, -) -> str: - client = mock.Mock() - resolution_tag = resolution_tags[0] if len(resolution_tags) == 1 else None - qualification = resolution.get("qualification") if isinstance(resolution, dict) else None - resolution_prefix = ( - f"{module.CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted['plan']['plan']}/" - ) - registry_url = ( - f"https://api.github.com/repos/{module.CONTROL_REPOSITORY}" - f"/git/matching-refs/tags/{resolution_prefix}" - ) - tag_url = ( - ( - f"https://api.github.com/repos/{module.CONTROL_REPOSITORY}/git/ref/tags/" - f"{urllib.parse.quote(resolution_tag, safe='')}" - ) - if resolution_tag is not None - else None - ) - record_url = ( - ( - f"https://api.github.com/repos/{module.CONTROL_REPOSITORY}/contents/" - f"continuity-successor-resolution.json?ref={resolution_commit}" - ) - if resolution_tag is not None and resolution_commit is not None - else None - ) - qualification_url = ( - ( - f"https://api.github.com/repos/{module.CONTROL_REPOSITORY}/actions/runs/" - f"{qualification['run_id']}/attempts/{qualification['run_attempt']}" - ) - if ( - isinstance(qualification, dict) - and isinstance(qualification.get("repository"), str) - and type(qualification.get("run_id")) is int - and type(qualification.get("run_attempt")) is int - ) - else None - ) - json_urls: list[str] = [] - bytes_urls: list[str] = [] - - def read_json(url: Any, **kwargs: Any) -> Any: - if not isinstance(url, str) or kwargs: - raise ConformanceError( - f"consumer used invalid JSON transport arguments for continuity authority: {url!r}" - ) - json_urls.append(url) - if url == registry_url: - return [{"ref": f"refs/tags/{tag}"} for tag in resolution_tags] - if tag_url is not None and url == tag_url: - if resolution_commit is None: - raise module.NotFound( - f"continuity resolution tag is absent: {resolution_tag}", - "plan-discovery", - ) - return {"object": {"sha": resolution_commit, "type": "commit"}} - if qualification_url is not None and url == qualification_url: - return qualification_run - raise ConformanceError( - f"consumer queried an undeclared continuity JSON authority: {url}" - ) - - def read_bytes(url: Any, **kwargs: Any) -> bytes: - if ( - not isinstance(url, str) - or url != record_url - or kwargs != {"accept": "application/vnd.github.raw+json"} - ): - raise ConformanceError( - f"consumer queried an undeclared continuity record authority: {url!r}" - ) - bytes_urls.append(url) - return canonical_json(resolution) - - client.json.side_effect = read_json - client.bytes.side_effect = read_bytes - selected = module.resolve_continuity_successor_fork(client, interrupted, successors) - required_json_urls = {registry_url, tag_url, qualification_url} - missing_json_urls = { - url for url in required_json_urls if url is not None and url not in json_urls - } - if missing_json_urls or record_url is None or record_url not in bytes_urls: - raise ConformanceError( - "consumer returned a continuity successor without reading every exact declared authority" - ) - return selected - - -def expect_continuity_transport_rejection( - module: ModuleType, - action: Any, - message: str, -) -> None: - try: - action() - except ConformanceError: - return - except module.RecoveryError as error: - raise ConformanceError( - f"{message}; the focused mutant did not reach the strict transport" - ) from error - raise ConformanceError(message) - - -def assert_continuity_transport_mutants_rejected( - module: ModuleType, - interrupted: dict[str, Any], - successors: list[dict[str, Any]], - resolution: dict[str, Any], - resolution_tag: str, - resolution_commit: str, - qualification_run: dict[str, Any], -) -> None: - def exercise() -> str: - return exercise_continuity_resolution( - module, - interrupted, - successors, - resolution, - [resolution_tag], - resolution_commit, - qualification_run, - ) - - def wrong_registry_route(client: Any, interrupted_plan: str) -> list[str]: - prefix = f"{module.CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted_plan}/" - client.json( - f"https://api.github.com/repos/{module.CONTROL_REPOSITORY}" - f"/git/matching-refs/heads/{prefix}" - ) - return [resolution_tag] - - with mock.patch.object( - module, - "list_continuity_resolution_tags", - side_effect=wrong_registry_route, - ): - expect_continuity_transport_rejection( - module, - exercise, - "shared conformance accepted a wrong continuity registry route", - ) - - resolve_tag = module.resolve_tag - tag_mutants = ( - ( - lambda client, _repository, tag: resolve_tag( - client, - "durable-workflow/unrelated", - tag, - ), - "shared conformance accepted a continuity tag lookup in the wrong repository", - ), - ( - lambda client, repository, _tag: resolve_tag( - client, - repository, - "main", - ), - "shared conformance accepted a mutable continuity tag ref", - ), - ) - for mutant, message in tag_mutants: - with mock.patch.object(module, "resolve_tag", side_effect=mutant): - expect_continuity_transport_rejection(module, exercise, message) - - def record_ref_mutant(ref: str) -> Any: - def read_record( - client: Any, - _tag: str, - _commit: str, - filename: str, - ) -> Any: - encoded_filename = urllib.parse.quote(filename, safe="/") - raw = client.bytes( - f"https://api.github.com/repos/{module.CONTROL_REPOSITORY}/contents/" - f"{encoded_filename}?ref={ref}", - accept="application/vnd.github.raw+json", - ) - return json.loads(raw) - - return read_record - - record_ref_mutants = ( - ( - "main", - "shared conformance accepted a mutable continuity record ref", - ), - ( - "e" * 40, - "shared conformance accepted an unrelated continuity record ref", - ), - ) - for ref, message in record_ref_mutants: - with mock.patch.object( - module, - "read_record", - side_effect=record_ref_mutant(ref), - ): - expect_continuity_transport_rejection(module, exercise, message) - - def qualification_route_mutant( - repository: str, - run_id: int, - ) -> Any: - def validate(qualification: dict[str, Any], client: Any) -> dict[str, Any]: - client.json( - f"https://api.github.com/repos/{repository}/actions/runs/{run_id}" - f"/attempts/{qualification['run_attempt']}" - ) - return qualification - - return validate - - qualification = resolution["qualification"] - qualification_route_mutants = ( - ( - "durable-workflow/unrelated", - qualification["run_id"], - "shared conformance accepted a qualification lookup in the wrong repository", - ), - ( - module.CONTROL_REPOSITORY, - qualification["run_id"] + 1, - "shared conformance accepted the wrong qualification run lookup", - ), - ) - for repository, run_id, message in qualification_route_mutants: - with mock.patch.object( - module, - "validate_continuity_resolution_qualification", - side_effect=qualification_route_mutant(repository, run_id), - ): - expect_continuity_transport_rejection(module, exercise, message) - - -def case_immutable_plan_enumeration(module: ModuleType) -> None: - tags = ["release-plan/conformance-a", "release-plan/conformance-b"] - client = mock.Mock() - client.json.return_value = [{"ref": f"refs/tags/{tag}"} for tag in tags] - if module.list_release_plan_tags(client) != tags: - raise ConformanceError("consumer did not enumerate the complete immutable tag registry") - client.json.return_value.append({"ref": f"refs/tags/{tags[0]}"}) - expect_recovery_error( - module, - lambda: module.list_release_plan_tags(client), - "consumer accepted duplicate immutable plan authority", - ) - client.json.return_value = [] - expect_recovery_error( - module, - lambda: module.list_release_plan_tags(client), - "consumer accepted a missing immutable plan registry", - ) - malformed_registry_entries = ( - None, - {}, - {"ref": 7}, - {"ref": f"refs/heads/{tags[0]}"}, - {"ref": "refs/tags/release-plan/"}, - {"ref": "refs/tags/release-plan/Invalid"}, - ) - for malformed in malformed_registry_entries: - client.json.return_value = [malformed] - expect_recovery_error( - module, - lambda: module.list_release_plan_tags(client), - f"consumer accepted malformed immutable plan authority: {malformed!r}", - ) - - -def case_current_plan_schema(module: ModuleType) -> None: - if getattr(module, "SCHEMA", None) != CURRENT_PLAN_SCHEMA: - raise ConformanceError("consumer does not accept the current release-plan schema") - if getattr(module, "LEGACY_SCHEMA", None) != HISTORICAL_PLAN_SCHEMA: - raise ConformanceError("consumer does not identify the historical release-plan schema") - if getattr(module, "LEGACY_PLAN_DIGESTS", None) != EXPECTED_LEGACY_PLAN_DIGESTS: - raise ConformanceError("consumer does not pin the exact historical release-plan authorities") - - current = plan(module, "current-schema-conformance") - module.validate_plan(current) - - historical = legacy_beta_one_plan() - if module.manifest_digest(historical) != "e1fc6e20c9d2ded0b5e7ac4d6be75ba861d31fc4b2db651dc0272dca623f2c7f": - raise ConformanceError("shared historical release-plan fixture has an unexpected digest") - module.validate_plan(historical) - - unrecorded = copy.deepcopy(historical) - unrecorded["plan"] = "beta-1-replacement" - expect_recovery_error( - module, - lambda: module.validate_plan(unrecorded), - "consumer accepted an unrecorded historical release plan", - ) - - unsupported = copy.deepcopy(current) - unsupported["schema"] = "durable-workflow.release-plan/v3" - expect_recovery_error( - module, - lambda: module.validate_plan(unsupported), - "consumer accepted an unsupported current release-plan schema", - ) - - -def case_completed_plan_lifecycle(module: ModuleType) -> None: - completed = authority(module, plan(module, "completed-conformance"), "completed") - with mock.patch.object(module, "classify_plan_authorities", return_value=[completed]): - selected, snapshot = module.classify_implicit_plan_authority(mock.Mock()) - if selected != completed or snapshot != [completed]: - raise ConformanceError("consumer did not select the completed current plan for verification") - - preparation = { - "components": { - "sdk-php": { - "release_notes": { - "release_date": "2026-07-25", - "sha256": "c" * 64, - "source": {}, - } - } - } - } - completed["preparation"] = preparation - implicit_authority = { - **completed, - "selection": "implicit", - "authority_snapshot": [completed], - } - component = module.COMPONENTS["sdk-php"] - with ( - mock.patch.object(module, "verify_plan_authority", return_value=({}, {})), - mock.patch.object(module, "validate_release_preparation"), - mock.patch.object(module, "resolve_tag", return_value=None), - mock.patch.object( - module, - "classify_implicit_plan_authority", - return_value=(completed, [completed]), - ), - mock.patch.object( - module, - "continuity_authority_snapshot", - return_value={ - "accepted": {"tag": None, "commit": None}, - "resumed": {"tag": None, "commit": None}, - }, - create=True, - ), - mock.patch.object( - module, - "scheduled_continuity_pause", - return_value=None, - create=True, - ), - mock.patch.dict( - module.VERIFIERS, - {component.distribution: mock.Mock(side_effect=module.NotFound("not published"))}, - ), - ): - expect_recovery_error( - module, - lambda: module.resolve_component( - mock.Mock(), - "sdk-php", - completed["tag"], - completed["commit"], - completed["plan"], - preparation, - implicit_authority, - ), - "consumer returned publication-ready for an implicitly selected completed plan", - ) - - -def supersession_pair(module: ModuleType) -> tuple[dict[str, Any], dict[str, Any]]: - predecessor_plan = plan(module, "superseded-conformance") - successor_plan = copy.deepcopy(predecessor_plan) - successor_plan["plan"] = "successor-conformance" - predecessor_plan["components"]["server"]["version"] = "3.0.0" - predecessor_plan["components"]["cli"]["version"] = "3.0.1" - successor_plan["components"]["server"]["version"] = "3.0.1" - successor_plan["components"]["cli"]["version"] = "3.0.0" - successor = authority(module, successor_plan, "actionable") - predecessor = authority( - module, - predecessor_plan, - "superseded", - { - "tag": successor["tag"], - "sha256": module.manifest_digest(successor_plan), - "plan": successor_plan, - }, - ) - predecessor["commit"] = "b" * 40 - predecessor["recorded_at"] = dt.datetime(2026, 7, 24, tzinfo=dt.UTC) - return predecessor, successor - - -def case_superseded_plan_lifecycle(module: ModuleType) -> None: - predecessor, successor = supersession_pair(module) - selected = module.current_product_train_authorities([predecessor, successor]) - if [item["tag"] for item in selected] != [successor["tag"]]: - raise ConformanceError("consumer did not resolve a superseded plan to its successor") - - -def case_exact_successor_identity(module: ModuleType) -> None: - predecessor, successor = supersession_pair(module) - predecessor["successor"] = {**predecessor["successor"], "sha256": "0" * 64} - expect_recovery_error( - module, - lambda: module.current_product_train_authorities([predecessor, successor]), - "consumer accepted an inexact successor digest", - ) - - predecessor, successor = supersession_pair(module) - predecessor["successor"] = { - **predecessor["successor"], - "tag": "release-plan/wrong-successor-conformance", - } - expect_recovery_error( - module, - lambda: module.current_product_train_authorities([predecessor, successor]), - "consumer accepted an inexact successor tag", - ) - - predecessor, successor = supersession_pair(module) - mismatched_plan = copy.deepcopy(successor["plan"]) - mismatched_plan["components"]["sdk-rust"]["commit"] = "c" * 40 - predecessor["successor"] = { - **predecessor["successor"], - "plan": mismatched_plan, - } - expect_recovery_error( - module, - lambda: module.current_product_train_authorities([predecessor, successor]), - "consumer accepted an inexact successor plan document", - ) - - -def case_malformed_authority_rejection(module: ModuleType) -> None: - for malformed in ("01.0.0", "1.0.0-alpha.01", "1.0.0-alpha..1", 100): - candidate = plan(module, "malformed-conformance") - candidate["components"]["server"]["version"] = malformed - expect_recovery_error( - module, - lambda candidate=candidate: module.validate_plan(candidate), - f"consumer accepted malformed authority value: {malformed!r}", - ) - - -def case_continuity_ambiguity_rejection(module: ModuleType) -> None: - interrupted, successors, resolution, qualification_run = continuity_resolution_fixture(module) - resolution_tag = continuity_resolution_tag(module, interrupted, resolution) - expected_selected = resolution["selected_successor"]["tag"] - - for ordering in (successors, list(reversed(successors))): - selected = exercise_continuity_resolution( - module, - interrupted, - ordering, - resolution, - [resolution_tag], - "f" * 40, - qualification_run, - ) - if selected != expected_selected: - raise ConformanceError( - "consumer did not select the exact digest-bound continuity successor independent of enumeration order" - ) - - assert_continuity_transport_mutants_rejected( - module, - interrupted, - successors, - resolution, - resolution_tag, - "f" * 40, - qualification_run, - ) - - absent_authorities = ( - ([], "f" * 40, "consumer accepted continuity successors without a resolution authority"), - ( - [resolution_tag], - None, - "consumer accepted a continuity resolution authority without an immutable record", - ), - ( - [ - resolution_tag, - ( - f"{module.CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted['plan']['plan']}/" - f"{'e' * 64}" - ), - ], - "f" * 40, - "consumer accepted multiple continuity resolution authorities", - ), - ) - for resolution_tags, resolution_commit, message in absent_authorities: - expect_recovery_error( - module, - lambda resolution_tags=resolution_tags, resolution_commit=resolution_commit: exercise_continuity_resolution( - module, - interrupted, - successors, - resolution, - resolution_tags, - resolution_commit, - qualification_run, - ), - message, - ) - - malformed_records: tuple[Any, ...] = ( - None, - {**resolution, "unexpected": True}, - ) - for malformed in malformed_records: - malformed_tag = ( - resolution_tag - if not isinstance(malformed, dict) - else continuity_resolution_tag(module, interrupted, malformed) - ) - expect_recovery_error( - module, - lambda malformed=malformed, malformed_tag=malformed_tag: exercise_continuity_resolution( - module, - interrupted, - successors, - malformed, - [malformed_tag], - "f" * 40, - qualification_run, - ), - "consumer accepted a malformed continuity resolution record", - ) - - interruption_mismatch = copy.deepcopy(resolution) - interruption_mismatch["interruption"]["plan"]["commit"] = "e" * 40 - claim_set_mismatch = copy.deepcopy(resolution) - claim_set_mismatch["successor_claims"][0]["acceptance"]["sha256"] = "e" * 64 - selected_outside_claim_set = copy.deepcopy(resolution) - selected_outside_claim_set["selected_successor"] = { - "tag": "release-plan/outside-claim-set", - "commit": "e" * 40, - "sha256": "e" * 64, - } - invalid_qualification = copy.deepcopy(resolution) - invalid_qualification["qualification"]["repository"] = "durable-workflow/untrusted" - semantic_mismatches = ( - (interruption_mismatch, "consumer accepted a continuity resolution for another interruption"), - (claim_set_mismatch, "consumer accepted a continuity resolution for another claim set"), - (selected_outside_claim_set, "consumer accepted a successor outside the exact claim set"), - (invalid_qualification, "consumer accepted an invalid qualification identity"), - ) - for mismatched, message in semantic_mismatches: - mismatched_tag = continuity_resolution_tag(module, interrupted, mismatched) - expect_recovery_error( - module, - lambda mismatched=mismatched, mismatched_tag=mismatched_tag: exercise_continuity_resolution( - module, - interrupted, - successors, - mismatched, - [mismatched_tag], - "f" * 40, - qualification_run, - ), - message, - ) - - digest_mismatch_tag = ( - f"{module.CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted['plan']['plan']}/" - f"{'0' * 64}" - ) - expect_recovery_error( - module, - lambda: exercise_continuity_resolution( - module, - interrupted, - successors, - resolution, - [digest_mismatch_tag], - "f" * 40, - qualification_run, - ), - "consumer accepted a continuity resolution with the wrong immutable digest", - ) - - mismatched_run = {**qualification_run, "head_sha": "8" * 40} - expect_recovery_error( - module, - lambda: exercise_continuity_resolution( - module, - interrupted, - successors, - resolution, - [resolution_tag], - "f" * 40, - mismatched_run, - ), - "consumer accepted qualification evidence for another source identity", - ) - - -def case_explicit_terminal_plan_rejection(module: ModuleType) -> None: - candidate = plan(module, "terminal-conformance") - completed = authority(module, candidate, "completed") - with mock.patch.object(module, "classify_plan_authorities", return_value=[completed]): - selected = module.select_explicit_plan_authority( - mock.Mock(), - completed["tag"], - completed["commit"], - candidate, - None, - ) - if selected != {**completed, "selection": "explicit"}: - raise ConformanceError("consumer did not select an explicitly requested completed plan") - - superseded = authority(module, candidate, "superseded") - with mock.patch.object(module, "classify_plan_authorities", return_value=[superseded]): - expect_recovery_error( - module, - lambda: module.select_explicit_plan_authority( - mock.Mock(), - superseded["tag"], - superseded["commit"], - candidate, - None, - ), - "consumer accepted an explicitly selected superseded plan", - ) - - -def case_bounded_authority_convergence(module: ModuleType) -> None: - candidate = authority(module, plan(module, "convergence-conformance"), "actionable") - with ( - mock.patch.object( - module, - "classify_implicit_plan_authority", - return_value=(candidate, [candidate]), - ) as classify, - mock.patch.object( - module, - "implicit_plan_authority_converged", - return_value=False, - ) as converged, - ): - expect_recovery_error( - module, - lambda: module.select_implicit_plan_authority(mock.Mock()), - "consumer did not fail closed after bounded authority churn", - ) - expected = module.IMPLICIT_AUTHORITY_MAX_ATTEMPTS - if classify.call_count != expected or converged.call_count != expected: - raise ConformanceError("consumer did not enforce the declared convergence attempt bound") - - -def case_release_candidate_beta_qualification(module: ModuleType) -> None: - candidate = plan(module, "release-candidate-conformance") - candidate["channel"] = "rc" - for identity in candidate["components"].values(): - identity["version"] = "2.0.0-rc.1" - module.validate_plan(candidate) - - beta_components = copy.deepcopy(candidate["components"]) - for identity in beta_components.values(): - identity["version"] = "2.0.0-beta.21" - record = { - "schema": "durable-workflow.beta-authorization/v1", - "channel": "beta", - "candidate": "coherent-beta-qualification", - "components": beta_components, - } - candidate["beta_authorization"]["tag"] = "beta-authorization/coherent-beta-qualification" - if not module.beta_authorization_matches_plan(candidate, candidate["beta_authorization"], record): - raise ConformanceError("consumer rejected coherent beta qualification for a release-candidate plan") - - record["components"]["server"]["version"] = "2.0.0-rc.1" - if module.beta_authorization_matches_plan(candidate, candidate["beta_authorization"], record): - raise ConformanceError("consumer accepted non-beta qualification for a release-candidate plan") - - -def aggregate_rc_plan(module: ModuleType) -> dict[str, Any]: - candidate = plan(module, "authoritative-rc-conformance") - candidate["channel"] = "rc" - for identity in candidate["components"].values(): - identity["version"] = "2.0.0-rc.5" - candidate["foundation"] = { - "tag": f"beta-candidate/rc-{candidate['plan']}", - "commit": "e" * 40, - } - candidate["beta_authorization"] = None - return candidate - - -def aggregate_rc_verification(module: ModuleType, candidate: dict[str, Any]) -> dict[str, Any]: - foundation = { - "schema": "durable-workflow.beta-candidate/v2", - "candidate": f"rc-{candidate['plan']}", - "components": candidate["components"], - } - return { - "schema": "durable-workflow.beta-candidate-verification/v2", - "candidate": foundation["candidate"], - "manifest_sha256": module.manifest_digest(foundation), - "outcome": "verified", - "components": { - name: { - "version": identity["version"], - "commit": identity["commit"], - "outcome": "verified", - } - for name, identity in candidate["components"].items() - }, - } - - -def case_authoritative_rc_foundation(module: ModuleType) -> None: - candidate = aggregate_rc_plan(module) - module.validate_plan(candidate) - - malformed_tag = copy.deepcopy(candidate) - malformed_tag["foundation"]["tag"] = "beta-candidate/rc-substitution" - expect_recovery_error( - module, - lambda: module.validate_plan(malformed_tag), - "consumer accepted an aggregate foundation for a different release plan", - ) - - malformed_commit = copy.deepcopy(candidate) - malformed_commit["foundation"]["commit"] = "not-a-commit" - expect_recovery_error( - module, - lambda: module.validate_plan(malformed_commit), - "consumer accepted a malformed aggregate foundation commit", - ) - - unapproved = copy.deepcopy(candidate) - unapproved["beta_authorization"] = { - "tag": f"beta-authorization/{candidate['plan']}", - "commit": "f" * 40, - } - expect_recovery_error( - module, - lambda: module.validate_plan(unapproved), - "consumer accepted conflicting authority for an aggregate foundation", - ) - - foundation = { - "schema": "durable-workflow.beta-candidate/v2", - "candidate": f"rc-{candidate['plan']}", - "components": candidate["components"], - } - verification = aggregate_rc_verification(module, candidate) - - class FoundationAccepted(RuntimeError): - pass - - with ( - mock.patch.object( - module, - "resolve_tag", - return_value=candidate["foundation"]["commit"], - ), - mock.patch.object( - module, - "read_record", - side_effect=(foundation, verification), - ), - mock.patch.object( - module, - "load_recovery_workflow_authority", - side_effect=FoundationAccepted, - ), - ): - try: - module.verify_plan_authority(mock.Mock(), candidate) - except FoundationAccepted: - pass - else: - raise ConformanceError( - "consumer did not continue after verifying the exact aggregate foundation" - ) - - with ( - mock.patch.object(module, "resolve_tag", return_value="d" * 40), - mock.patch.object(module, "read_record") as read_record, - ): - expect_recovery_error( - module, - lambda: module.verify_plan_authority(mock.Mock(), candidate), - "consumer accepted a moved aggregate foundation tag", - ) - read_record.assert_not_called() - - substituted_foundation = copy.deepcopy(foundation) - substituted_foundation["components"]["server"]["commit"] = "d" * 40 - with ( - mock.patch.object( - module, - "resolve_tag", - return_value=candidate["foundation"]["commit"], - ), - mock.patch.object( - module, - "read_record", - return_value=substituted_foundation, - ), - ): - expect_recovery_error( - module, - lambda: module.verify_plan_authority(mock.Mock(), candidate), - "consumer accepted a substituted aggregate component tuple", - ) - - malformed_verification = copy.deepcopy(verification) - malformed_verification["components"]["server"]["outcome"] = "failed" - with ( - mock.patch.object( - module, - "resolve_tag", - return_value=candidate["foundation"]["commit"], - ), - mock.patch.object( - module, - "read_record", - side_effect=(foundation, malformed_verification), - ), - ): - expect_recovery_error( - module, - lambda: module.verify_plan_authority(mock.Mock(), candidate), - "consumer accepted aggregate foundation evidence that was not verified", - ) - - -def case_scheduled_empty_no_op(module: ModuleType) -> None: - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - evidence = root / "release-recovery-evidence.json" - github_output = root / "github-output" - component = next(iter(module.COMPONENTS)) - arguments = [ - "component-release-recovery.py", - "resolve", - "--component", - component, - "--plan-output", - str(root / "release-plan.json"), - "--preparation-output", - str(root / "release-preparation.json"), - "--evidence", - str(evidence), - "--github-output", - str(github_output), - "--allow-empty", - ] - with ( - mock.patch.object(sys, "argv", arguments), - mock.patch.object( - module, - "discover_plan", - side_effect=module.RecoveryError( - "no public release plan is available", - "plan-discovery", - ), - ), - ): - result = module.main() - - state = json.loads(evidence.read_bytes()) - if ( - result != 0 - or state.get("phase") != "plan-discovery" - or state.get("outcome") != "no-op" - or github_output.read_text(encoding="utf-8") != "action=none\n" - ): - raise ConformanceError( - "scheduled recovery without eligible work did not record a neutral no-op" - ) - - failure_evidence = root / "release-recovery-failure-evidence.json" - failure_output = root / "failure-github-output" - failure_arguments = [ - *arguments[: arguments.index("--evidence")], - "--evidence", - str(failure_evidence), - "--github-output", - str(failure_output), - "--allow-empty", - ] - with ( - mock.patch.object(sys, "argv", failure_arguments), - mock.patch.object( - module, - "discover_plan", - side_effect=module.RecoveryError( - "release plan registry is malformed", - "plan-discovery", - ), - ), - ): - failure_result = module.main() - - failure_state = json.loads(failure_evidence.read_bytes()) - if ( - failure_result != 1 - or failure_state.get("outcome") != "failed" - or failure_output.exists() - ): - raise ConformanceError( - "scheduled empty handling weakened unrelated plan-discovery failures" - ) - - -def github_cli_result( - status: int = 200, - body: bytes = b"[]", - *, - stderr: bytes = b"", - **headers: str, -) -> subprocess.CompletedProcess[bytes]: - response_headers = b"".join(f"{name}: {value}\r\n".encode() for name, value in headers.items()) - output = f"HTTP/2.0 {status} response\r\n".encode() + response_headers + b"\r\n" + body - return subprocess.CompletedProcess( - ["gh", "api"], - 0 if 200 <= status <= 299 else 1, - output, - stderr, - ) - - -def case_trusted_github_api_transport(module: ModuleType) -> None: - url = "https://api.github.com/repos/durable-workflow/.github/releases" - sleeps: list[float] = [] - client = module.PublicClient( - token="conformance-token", - max_attempts=2, - retry_base_seconds=1, - sleep=sleeps.append, - ) - with ( - mock.patch.dict(module.os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch.object( - module.urllib.request, - "urlopen", - side_effect=AssertionError("runner transport bypassed the GitHub CLI mock"), - ) as open_url, - mock.patch.object( - module.subprocess, - "run", - side_effect=( - github_cli_result(0, stderr=b"x509: certificate signed by unknown authority"), - github_cli_result(), - ), - ) as run, - ): - result = client.json(url) - command = run.call_args.args[0] - if ( - result != [] - or sleeps != [1] - or open_url.called - or command[:7] != ["gh", "api", "--hostname", "github.com", "--include", "--method", "GET"] - or command[-1] != "repos/durable-workflow/.github/releases" - or "--insecure" in command - or run.call_args.kwargs.get("env", {}).get("GH_TOKEN") != "conformance-token" - or run.call_args.kwargs.get("env", {}).get("GH_PROMPT_DISABLED") != "1" - ): - raise ConformanceError( - "consumer did not retry transient certificate failure through the GitHub CLI trust transport" - ) - - rate_limit_sleeps: list[float] = [] - rate_limited = module.PublicClient( - token="conformance-token", - max_attempts=2, - retry_base_seconds=1, - sleep=rate_limit_sleeps.append, - now=lambda: 100, - ) - with ( - mock.patch.dict(module.os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch.object( - module.subprocess, - "run", - side_effect=( - github_cli_result( - 403, - b'{"message":"Forbidden"}', - **{ - "x-ratelimit-remaining": "0", - "X-rAtElImIt-ReSeT": "112", - }, - ), - github_cli_result(), - ), - ) as run, - ): - result = rate_limited.json(url) - if result != [] or rate_limit_sleeps != [12] or run.call_count != 2: - raise ConformanceError( - "consumer did not classify mixed-case GitHub CLI rate-limit headers or honor reset delay" - ) - - persistent = module.PublicClient( - token="conformance-token", - max_attempts=2, - retry_base_seconds=1, - sleep=lambda _delay: None, - ) - with ( - mock.patch.dict(module.os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch.object( - module.subprocess, - "run", - return_value=github_cli_result(0, stderr=b"x509: certificate signed by unknown authority"), - ) as run, - ): - try: - persistent.json(url) - except module.PublicInfrastructureError as error: - expected = { - "classification": "github-read-transient", - "endpoint_class": "releases-api", - "attempts": 2, - "reason": "retry-exhausted", - "failure": "transport=tls-certificate-verification", - } - if getattr(error, "evidence", None) != expected: - raise ConformanceError( - "persistent certificate failure did not retain structured transport evidence" - ) from error - else: - raise ConformanceError("persistent certificate failure did not fail closed") - if run.call_count != 2: - raise ConformanceError("persistent certificate failure escaped the retry bound") - - api_error = github_cli_result( - 422, - b'{"message":"invalid release authority"}', - ) - - def fail_on_sleep(_delay: float) -> None: - raise ConformanceError("deterministic API failure was retried") - - deterministic = module.PublicClient(token="conformance-token", max_attempts=3, sleep=fail_on_sleep) - with ( - mock.patch.dict(module.os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch.object( - module.subprocess, - "run", - return_value=api_error, - ) as run, - ): - expect_recovery_error( - module, - lambda: deterministic.json(url), - "ordinary GitHub API failure was accepted", - ) - if run.call_count != 1: - raise ConformanceError("ordinary GitHub API failure was retried") - - -def case_transport_fail_closed_publication(module: ModuleType) -> None: - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - evidence = root / "release-recovery-evidence.json" - github_output = root / "github-output" - arguments = [ - "component-release-recovery.py", - "resolve", - "--component", - "workflow", - "--plan-output", - str(root / "release-plan.json"), - "--preparation-output", - str(root / "release-preparation.json"), - "--evidence", - str(evidence), - "--github-output", - str(github_output), - "--allow-empty", - ] - unavailable = module.PublicInfrastructureError( - "releases-api", - 5, - reason="retry-exhausted", - failure="transport=tls-certificate-verification", - ) - with ( - mock.patch.object(sys, "argv", arguments), - mock.patch.object(module, "discover_plan", side_effect=unavailable), - ): - result = module.main() - state = json.loads(evidence.read_bytes()) - if ( - result != module.INFRASTRUCTURE_EXIT_CODE - or state.get("phase") != "runner-transport" - or state.get("outcome") != "runner-transport" - or state.get("transport") != unavailable.evidence - or github_output.read_text(encoding="utf-8") != "action=none\n" - ): - raise ConformanceError( - "persistent transport failure did not suppress publication with runner evidence" - ) - - candidate = plan(module, "authorized-publication-conformance") - preparation = { - "components": { - "workflow": { - "release_notes": { - "release_date": "2026-08-12", - "sha256": "a" * 64, - "source": {}, - } - } - } - } - component = module.COMPONENTS["workflow"] - verifier = mock.Mock(side_effect=module.NotFound("not published")) - authority = mock.Mock(return_value=({}, {})) - with ( - mock.patch.object(module, "verify_plan_authority", authority), - mock.patch.object(module, "validate_release_preparation"), - mock.patch.object( - module, - "source_product_train_evidence", - return_value={}, - create=True, - ), - mock.patch.object(module, "resolve_tag", return_value=None), - mock.patch.dict(module.VERIFIERS, {component.distribution: verifier}), - ): - state, outputs = module.resolve_component( - mock.Mock(), - "workflow", - f"release-plan/{candidate['plan']}", - "b" * 40, - candidate, - preparation, - ) - if ( - outputs.get("action") != "publish" - or state.get("outcome") != "ready" - or authority.call_count != 1 - or verifier.call_count != 1 - ): - raise ConformanceError( - "authorized publication path did not remain available after transport hardening" - ) - - -CASE_RUNNERS = { - "immutable-plan-enumeration": case_immutable_plan_enumeration, - "current-plan-schema": case_current_plan_schema, - "completed-plan-lifecycle": case_completed_plan_lifecycle, - "superseded-plan-lifecycle": case_superseded_plan_lifecycle, - "exact-successor-identity": case_exact_successor_identity, - "malformed-authority-rejection": case_malformed_authority_rejection, - "continuity-ambiguity-rejection": case_continuity_ambiguity_rejection, - "explicit-terminal-plan-rejection": case_explicit_terminal_plan_rejection, - "bounded-authority-convergence": case_bounded_authority_convergence, - "release-candidate-beta-qualification": case_release_candidate_beta_qualification, - "authoritative-rc-foundation": case_authoritative_rc_foundation, - "scheduled-empty-no-op": case_scheduled_empty_no_op, - "trusted-github-api-transport": case_trusted_github_api_transport, - "transport-fail-closed-publication": case_transport_fail_closed_publication, -} - - -def run_cases(module: ModuleType) -> tuple[list[dict[str, str]], list[str]]: - results: list[dict[str, str]] = [] - failures: list[str] = [] - for case_id in REQUIRED_CASES: - try: - CASE_RUNNERS[case_id](module) - except Exception as error: - results.append({"id": case_id, "status": "fail"}) - failures.append(f"{case_id}: {error}") - else: - results.append({"id": case_id, "status": "pass"}) - return results, failures - - -def run_distribution(command: list[str], repository_root: Path) -> tuple[dict[str, Any], str | None]: - resolved = [sys.executable if item == "{python}" else item for item in command] - result = subprocess.run(resolved, cwd=repository_root, check=False) - evidence = { - "command": resolved, - "status": "pass" if result.returncode == 0 else "fail", - } - failure = None - if result.returncode != 0: - failure = f"distribution verification exited with status {result.returncode}" - return evidence, failure - - -def fetch_public(url: str) -> bytes: - request = urllib.request.Request( - url, - headers={"Accept": "application/vnd.github.raw+json", "User-Agent": "release-recovery-conformance"}, - ) - try: - with urllib.request.urlopen(request, timeout=20) as response: - return response.read() - except (OSError, urllib.error.HTTPError) as error: - raise ConformanceError(f"cannot read public conformance target: {url}") from error - - -def audit_public_targets(contract: dict[str, Any], contract_raw: bytes) -> list[dict[str, str]]: - results: list[dict[str, str]] = [] - for consumer in contract["consumers"]: - repository = consumer["repository"].removeprefix("durable-workflow/") - branch = consumer["target_branch"] - base = f"https://raw.githubusercontent.com/durable-workflow/{repository}/{branch}" - remote_contract = fetch_public(f"{base}/scripts/ci/release-recovery-consumer-contract.json") - remote_adapter_raw = fetch_public(f"{base}/scripts/ci/release-recovery-consumer-adapter.json") - remote_suite = fetch_public(f"{base}/scripts/ci/release_recovery_consumer_conformance.py") - try: - remote_adapter = json.loads(remote_adapter_raw) - except json.JSONDecodeError as error: - raise ConformanceError(f"{consumer['component']} adapter is not valid JSON") from error - if remote_contract != contract_raw: - raise ConformanceError(f"{consumer['component']} does not carry the current shared contract") - if not isinstance(remote_adapter, dict): - raise ConformanceError(f"{consumer['component']} adapter is not a JSON object") - expected_identity = { - "component": consumer["component"], - "repository": consumer["repository"], - "target_branch": consumer["target_branch"], - } - if any(remote_adapter.get(field) != value for field, value in expected_identity.items()): - raise ConformanceError(f"{consumer['component']} adapter has the wrong target identity") - if remote_adapter.get("contract") != { - "path": "scripts/ci/release-recovery-consumer-contract.json", - "sha256": sha256_bytes(contract_raw), - "version": contract["version"], - }: - raise ConformanceError(f"{consumer['component']} adapter does not pin the current contract") - if sha256_bytes(remote_suite) != contract["suite"]["sha256"]: - raise ConformanceError(f"{consumer['component']} does not carry the current shared suite") - results.append( - { - "component": consumer["component"], - "status": "pass", - "target_branch": consumer["target_branch"], - } - ) - return results - - -def source_commit(repository_root: Path) -> str: - github_sha = os.environ.get("GITHUB_SHA", "") - if COMMIT_PATTERN.fullmatch(github_sha): - return github_sha - result = subprocess.run( - ["git", "rev-parse", "HEAD"], - cwd=repository_root, - check=False, - capture_output=True, - text=True, - ) - commit = result.stdout.strip() - return commit if result.returncode == 0 and COMMIT_PATTERN.fullmatch(commit) else "unknown" - - -def write_evidence(path: Path, evidence: dict[str, Any]) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(canonical_json(evidence)) - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser() - parser.add_argument("--contract", required=True, type=Path) - parser.add_argument("--adapter", type=Path) - parser.add_argument("--evidence", type=Path) - parser.add_argument("--previous-ref") - parser.add_argument("--shared-only", action="store_true") - parser.add_argument("--audit-public-targets", action="store_true") - return parser.parse_args() - - -def main() -> int: - args = parse_args() - suite_path = Path(__file__).resolve() - contract_path = args.contract.resolve() - repository_root = Path.cwd().resolve() - contract, contract_raw = load_json_object(contract_path, "shared contract") - contract_sha256 = validate_contract(contract, contract_raw, suite_path) - previous = previous_contract(repository_root, contract_path, args.previous_ref) - require_versioned_contract_change(previous, contract) - - if args.audit_public_targets: - targets = audit_public_targets(contract, contract_raw) - evidence = { - "schema": EVIDENCE_SCHEMA, - "contract": { - "sha256": contract_sha256, - "suite_sha256": contract["suite"]["sha256"], - "version": contract["version"], - }, - "generated_at": dt.datetime.now(dt.UTC).isoformat(), - "outcome": "pass", - "source_commit": source_commit(repository_root), - "targets": targets, - } - if args.evidence is not None: - write_evidence(args.evidence, evidence) - print(f"release-recovery consumer contract {contract['version']} passed for {len(targets)} public targets") - return 0 - - if args.adapter is None: - print(f"release-recovery consumer contract {contract['version']} is valid ({contract_sha256})") - return 0 - - adapter, _adapter_raw = load_json_object(args.adapter.resolve(), "consumer adapter") - consumer_path, distribution_command = validate_adapter( - adapter, - contract, - contract_sha256, - repository_root, - suite_path, - contract_path, - ) - module = load_consumer(consumer_path) - cases, failures = run_cases(module) - distribution: dict[str, Any] = {"command": distribution_command, "status": "not-run"} - if not failures and not args.shared_only: - distribution, distribution_failure = run_distribution(distribution_command, repository_root) - if distribution_failure is not None: - failures.append(distribution_failure) - evidence = { - "schema": EVIDENCE_SCHEMA, - "component": adapter["component"], - "contract": { - "sha256": contract_sha256, - "suite_sha256": contract["suite"]["sha256"], - "version": contract["version"], - }, - "cases": cases, - "distribution_verification": distribution, - "generated_at": dt.datetime.now(dt.UTC).isoformat(), - "outcome": "fail" if failures else "pass", - "repository": adapter["repository"], - "source_commit": source_commit(repository_root), - "target_branch": adapter["target_branch"], - } - if failures: - evidence["failures"] = failures - if args.evidence is not None: - write_evidence(args.evidence, evidence) - if failures: - for failure in failures: - print(f"FAIL: {failure}", file=sys.stderr) - return 1 - print( - f"{adapter['component']} satisfies release-recovery consumer contract {contract['version']} ({contract_sha256})" - ) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/release_recovery_target_qualification.py b/scripts/release_recovery_target_qualification.py deleted file mode 100644 index 8b583f7..0000000 --- a/scripts/release_recovery_target_qualification.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 -"""Qualify the shared release-recovery contract across every public target.""" - -from __future__ import annotations - -import argparse -import datetime as dt -import json -import subprocess -import sys -import urllib.error -import urllib.request -from pathlib import Path -from typing import Any - -# GitHub Actions invokes this file directly from the repository root. In that -# mode Python adds scripts/, rather than the repository root, to sys.path. -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts import release_recovery_consumer_conformance as conformance - -EVIDENCE_SCHEMA = "durable-workflow.release-recovery-target-qualification-evidence/v1" -CONTRACT_PATH = "scripts/ci/release-recovery-consumer-contract.json" -ADAPTER_PATH = "scripts/ci/release-recovery-consumer-adapter.json" -SUITE_PATH = "scripts/ci/release_recovery_consumer_conformance.py" - - -def fetch_public(url: str) -> bytes: - request = urllib.request.Request( - url, - headers={"User-Agent": "release-recovery-target-qualification"}, - ) - try: - with urllib.request.urlopen(request, timeout=20) as response: - return response.read() - except (OSError, urllib.error.HTTPError) as error: - raise conformance.ConformanceError(f"cannot read public conformance target: {url}") from error - - -def json_object(raw: bytes, label: str) -> dict[str, Any]: - try: - value = json.loads(raw) - except json.JSONDecodeError as error: - raise conformance.ConformanceError(f"{label} is not valid JSON") from error - if not isinstance(value, dict): - raise conformance.ConformanceError(f"{label} is not a JSON object") - return value - - -def resolve_target_commit(repository: str, branch: str) -> tuple[str, str]: - target_ref = f"refs/heads/{branch}" - try: - result = subprocess.run( - [ - "git", - "ls-remote", - "--exit-code", - "--refs", - f"https://github.com/{repository}.git", - target_ref, - ], - check=False, - capture_output=True, - text=True, - timeout=20, - ) - except (OSError, subprocess.TimeoutExpired) as error: - raise conformance.ConformanceError(f"cannot resolve public target {repository}@{branch}") from error - lines = result.stdout.splitlines() - fields = lines[0].split() if len(lines) == 1 else [] - if ( - result.returncode != 0 - or len(fields) != 2 - or fields[1] != target_ref - or conformance.COMMIT_PATTERN.fullmatch(fields[0]) is None - ): - raise conformance.ConformanceError(f"{repository}@{branch} did not resolve to an exact commit") - return target_ref, fields[0] - - -def validate_adapter( - raw: bytes, - consumer: dict[str, str], - contract: dict[str, Any], - contract_sha256: str, -) -> dict[str, str]: - adapter = json_object(raw, f"{consumer['component']} adapter") - if raw != conformance.canonical_json(adapter): - raise conformance.ConformanceError(f"{consumer['component']} adapter is not canonical JSON") - expected_keys = { - "component", - "consumer", - "contract", - "distribution_verification", - "repository", - "schema", - "suite", - "target_branch", - } - if set(adapter) != expected_keys or adapter.get("schema") != conformance.ADAPTER_SCHEMA: - raise conformance.ConformanceError(f"{consumer['component']} adapter does not satisfy the shared adapter shape") - expected_identity = { - "component": consumer["component"], - "repository": consumer["repository"], - "target_branch": consumer["target_branch"], - } - if any(adapter.get(field) != value for field, value in expected_identity.items()): - raise conformance.ConformanceError(f"{consumer['component']} adapter has the wrong target identity") - if adapter.get("contract") != { - "path": CONTRACT_PATH, - "sha256": contract_sha256, - "version": contract["version"], - }: - raise conformance.ConformanceError(f"{consumer['component']} adapter does not pin the current contract") - if adapter.get("suite") != { - "path": SUITE_PATH, - "sha256": contract["suite"]["sha256"], - }: - raise conformance.ConformanceError(f"{consumer['component']} adapter does not pin the current suite") - return { - **expected_identity, - "sha256": conformance.sha256_bytes(raw), - } - - -def audit_public_targets(contract: dict[str, Any], contract_raw: bytes) -> list[dict[str, Any]]: - contract_sha256 = conformance.sha256_bytes(contract_raw) - results: list[dict[str, Any]] = [] - for consumer in contract["consumers"]: - repository = consumer["repository"] - branch = consumer["target_branch"] - target_ref, commit = resolve_target_commit(repository, branch) - base = f"https://raw.githubusercontent.com/{repository}/{commit}" - remote_contract = fetch_public(f"{base}/{CONTRACT_PATH}") - remote_adapter = fetch_public(f"{base}/{ADAPTER_PATH}") - remote_suite = fetch_public(f"{base}/{SUITE_PATH}") - if remote_contract != contract_raw: - raise conformance.ConformanceError(f"{consumer['component']} does not carry the current shared contract") - adapter_identity = validate_adapter( - remote_adapter, - consumer, - contract, - contract_sha256, - ) - suite_sha256 = conformance.sha256_bytes(remote_suite) - if suite_sha256 != contract["suite"]["sha256"]: - raise conformance.ConformanceError(f"{consumer['component']} does not carry the current shared suite") - results.append( - { - "adapter": adapter_identity, - "contract": { - "sha256": contract_sha256, - "version": contract["version"], - }, - "source_commit": commit, - "status": "pass", - "suite_sha256": suite_sha256, - "target_ref": target_ref, - } - ) - return results - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser() - parser.add_argument("--contract", required=True, type=Path) - parser.add_argument("--evidence", required=True, type=Path) - return parser.parse_args() - - -def main() -> int: - args = parse_args() - contract_path = args.contract.resolve() - contract, contract_raw = conformance.load_json_object(contract_path, "shared contract") - suite_path = Path(conformance.__file__).resolve() - contract_sha256 = conformance.validate_contract(contract, contract_raw, suite_path) - targets = audit_public_targets(contract, contract_raw) - evidence = { - "schema": EVIDENCE_SCHEMA, - "contract": { - "sha256": contract_sha256, - "suite_sha256": contract["suite"]["sha256"], - "version": contract["version"], - }, - "generated_at": dt.datetime.now(dt.UTC).isoformat(), - "outcome": "pass", - "source_commit": conformance.source_commit(Path.cwd().resolve()), - "targets": targets, - } - conformance.write_evidence(args.evidence, evidence) - print(f"release-recovery contract {contract['version']} passed for {len(targets)} exact public target commits") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/stable_authorization.py b/scripts/stable_authorization.py deleted file mode 100644 index b695d56..0000000 --- a/scripts/stable_authorization.py +++ /dev/null @@ -1,1035 +0,0 @@ -#!/usr/bin/env python3 -"""Evaluate and record the protected stable 2.0 authorization decision.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import re -import subprocess -import sys -import tempfile -import urllib.parse -from datetime import datetime -from pathlib import Path -from typing import Any - -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.beta_authorization import user_identity -from scripts.beta_candidate import ( - COMPONENTS, - VERSION_PATTERN, - CandidateError, - PublicClient, - canonical_json, - fetch_existing_record, - manifest_digest, - read_record_file, - run_git, - write_github_output, -) -from scripts.release_plan import read_public_record, resolve_tag - -CONTRACT_SCHEMA = "durable-workflow.stable-authorization.contract/v1" -REQUEST_SCHEMA = "durable-workflow.stable-authorization-request/v1" -EXPERIMENT_EVIDENCE_SCHEMA = "durable-workflow.release-critical-experiment-evidence/v1" -READOUT_SCHEMA = "durable-workflow.stable-authorization-readout/v1" -AUTHORIZATION_SCHEMA = "durable-workflow.stable-authorization/v1" -CONTROL_REPOSITORY = "durable-workflow/.github" -STABLE_VERSION = "2.0.0" -AUTHORIZATION_ENVIRONMENT = "stable-authorization" -AUTHORIZATION_WORKFLOW = ".github/workflows/stable-authorization.yml" -AUTHORIZATION_WORKFLOW_REF = "durable-workflow/.github/.github/workflows/stable-authorization.yml@refs/heads/main" -CONTRACT_URL = "https://raw.githubusercontent.com/durable-workflow/.github/main/stable-authorization/contract.json" -AUTHORIZATION_TAG_PREFIX = f"stable-authorization/{STABLE_VERSION}/" -PRODUCT_OWNER_REVIEWER_ID = 1130888 -API_VERSION = "2022-11-28" -COMPONENT_NAMES = tuple(COMPONENTS) -RELEASE_CRITICAL_EXPERIMENTS = ( - "activities", - "cloud", - "heartbeats", - "namespaces", - "polyglot", - "replay", - "signals-queries", - "timers", - "worker-versioning", - "workflow-lifecycle", - "workflow-updates", - "python", -) -POLYGLOT_CELLS = ("php", "python", "rust") -COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$") -SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") -LOGIN_PATTERN = re.compile(r"^[A-Za-z0-9-]{1,39}$") -CANDIDATE_TAG_PATTERN = re.compile(r"^release-candidate/rc/(?P[a-z0-9][a-z0-9._-]{0,55})$") -MAX_REQUEST_BYTES = 1024 * 1024 -MAX_EVIDENCE_BYTES = 2 * 1024 * 1024 - - -def require_exact_keys(value: Any, expected: set[str], context: str) -> dict[str, Any]: - if not isinstance(value, dict) or set(value) != expected: - raise CandidateError(f"{context} keys must be exactly {sorted(expected)}") - return value - - -def parse_json_file(path: Path, context: str, *, maximum_bytes: int) -> Any: - try: - raw = path.read_bytes() - except OSError as error: - raise CandidateError(f"cannot read {context} {path}: {error}") from error - if len(raw) > maximum_bytes: - raise CandidateError(f"{context} exceeds the {maximum_bytes}-byte limit") - try: - return json.loads(raw) - except json.JSONDecodeError as error: - raise CandidateError(f"{context} is not valid JSON: {error}") from error - - -def validate_contract(value: Any) -> dict[str, Any]: - contract = require_exact_keys( - value, - { - "$schema", - "schema", - "stable_version", - "artifact_components", - "release_critical_experiments", - "required_sdk_cells", - "evidence_policy", - "human_decision", - }, - "stable authorization contract", - ) - expected = { - "$schema": "./contract-schema.json", - "schema": CONTRACT_SCHEMA, - "stable_version": STABLE_VERSION, - "artifact_components": list(COMPONENT_NAMES), - "release_critical_experiments": list(RELEASE_CRITICAL_EXPERIMENTS), - "required_sdk_cells": {"polyglot": list(POLYGLOT_CELLS)}, - "evidence_policy": { - "artifact_tuple_binding": "exact", - "missing": "deny", - "stale": "deny", - "non_passing": "deny", - "runner_blocked": "deny", - "aggregate_historical_pass_rate": "never_authoritative", - }, - "human_decision": { - "required": True, - "occurs_after_evidence_gate": True, - }, - } - if contract != expected: - raise CandidateError("stable authorization contract differs from the fixed release-critical tier") - return contract - - -def load_contract(path: Path) -> dict[str, Any]: - return validate_contract(parse_json_file(path, "stable authorization contract", maximum_bytes=256 * 1024)) - - -def validate_binding(value: Any, context: str) -> dict[str, Any]: - binding = require_exact_keys(value, {"tag", "commit", "sha256"}, context) - if ( - not isinstance(binding["tag"], str) - or CANDIDATE_TAG_PATTERN.fullmatch(binding["tag"]) is None - or not isinstance(binding["commit"], str) - or COMMIT_PATTERN.fullmatch(binding["commit"]) is None - or not isinstance(binding["sha256"], str) - or SHA256_PATTERN.fullmatch(binding["sha256"]) is None - ): - raise CandidateError(f"{context} must identify one immutable RC artifact tuple") - return binding - - -def validate_artifact_tuple(value: Any) -> dict[str, Any]: - artifact_tuple = require_exact_keys(value, {"tag", "commit", "components"}, "artifact tuple") - if ( - not isinstance(artifact_tuple["tag"], str) - or CANDIDATE_TAG_PATTERN.fullmatch(artifact_tuple["tag"]) is None - or not isinstance(artifact_tuple["commit"], str) - or COMMIT_PATTERN.fullmatch(artifact_tuple["commit"]) is None - ): - raise CandidateError("artifact tuple must cite one immutable RC candidate tag and commit") - components = artifact_tuple["components"] - if not isinstance(components, dict) or set(components) != set(COMPONENT_NAMES): - raise CandidateError(f"artifact tuple components must be exactly {list(COMPONENT_NAMES)}") - for name in COMPONENT_NAMES: - component = require_exact_keys( - components[name], - {"version", "commit"}, - f"artifact tuple component {name}", - ) - if ( - not isinstance(component["version"], str) - or VERSION_PATTERN.fullmatch(component["version"]) is None - or not isinstance(component["commit"], str) - or COMMIT_PATTERN.fullmatch(component["commit"]) is None - ): - raise CandidateError(f"artifact tuple component {name} has an invalid immutable identity") - return artifact_tuple - - -def validate_generated_at(value: Any, context: str) -> None: - if not isinstance(value, str): - raise CandidateError(f"{context} generated_at must be a date-time") - try: - parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError as error: - raise CandidateError(f"{context} generated_at must be a date-time") from error - if parsed.tzinfo is None: - raise CandidateError(f"{context} generated_at must include a timezone") - - -def validate_cell(value: Any, context: str) -> dict[str, Any]: - cell = require_exact_keys(value, {"outcome", "runner_blocked", "artifact_tuple"}, context) - if cell["outcome"] not in {"pass", "fail", "error"} or not isinstance(cell["runner_blocked"], bool): - raise CandidateError(f"{context} has an invalid outcome") - validate_binding(cell["artifact_tuple"], f"{context} artifact tuple") - return cell - - -def validate_experiment_evidence( - value: Any, - experiment: str, -) -> dict[str, Any]: - expected_keys = { - "schema", - "experiment", - "outcome", - "runner_blocked", - "artifact_tuple", - "source", - } - if experiment == "polyglot": - expected_keys.add("cells") - evidence = require_exact_keys(value, expected_keys, f"{experiment} evidence") - if ( - evidence["schema"] != EXPERIMENT_EVIDENCE_SCHEMA - or evidence["experiment"] != experiment - or evidence["outcome"] not in {"pass", "fail", "error"} - or not isinstance(evidence["runner_blocked"], bool) - ): - raise CandidateError(f"{experiment} evidence has an invalid outcome contract") - validate_binding(evidence["artifact_tuple"], f"{experiment} evidence artifact tuple") - source = require_exact_keys( - evidence["source"], - {"url", "sha256", "generated_at"}, - f"{experiment} evidence source", - ) - public_source_prefixes = ( - "https://github.com/durable-workflow/", - "https://raw.githubusercontent.com/durable-workflow/", - ) - if ( - not isinstance(source["url"], str) - or not source["url"].startswith(public_source_prefixes) - or not isinstance(source["sha256"], str) - or SHA256_PATTERN.fullmatch(source["sha256"]) is None - ): - raise CandidateError(f"{experiment} evidence source must be public and digest-bound") - validate_generated_at(source["generated_at"], f"{experiment} evidence source") - if experiment == "polyglot": - cells = evidence["cells"] - if not isinstance(cells, dict) or not set(cells) <= set(POLYGLOT_CELLS): - raise CandidateError("polyglot evidence contains an unknown SDK cell") - for cell in POLYGLOT_CELLS: - if cell in cells: - validate_cell(cells[cell], f"polyglot {cell} cell") - return evidence - - -def validate_request(value: Any) -> dict[str, Any]: - request = require_exact_keys( - value, - {"$schema", "schema", "stable_version", "artifact_tuple", "evidence"}, - "stable authorization request", - ) - if ( - request["$schema"] != "./request-schema.json" - or request["schema"] != REQUEST_SCHEMA - or request["stable_version"] != STABLE_VERSION - ): - raise CandidateError("stable authorization request must select only stable 2.0.0") - validate_artifact_tuple(request["artifact_tuple"]) - evidence = require_exact_keys( - request["evidence"], - {"experiments"}, - "stable authorization request evidence", - ) - experiments = evidence["experiments"] - if not isinstance(experiments, dict) or not set(experiments) <= set(RELEASE_CRITICAL_EXPERIMENTS): - raise CandidateError("stable authorization request contains an unknown experiment") - for experiment, record in experiments.items(): - validate_experiment_evidence(record, experiment) - return request - - -def load_request(path: Path) -> dict[str, Any]: - return validate_request(parse_json_file(path, "stable authorization request", maximum_bytes=MAX_REQUEST_BYTES)) - - -def tuple_binding(artifact_tuple: dict[str, Any]) -> dict[str, str]: - return { - "tag": artifact_tuple["tag"], - "commit": artifact_tuple["commit"], - "sha256": manifest_digest(artifact_tuple), - } - - -def status_for_record( - record: dict[str, Any] | None, - expected_binding: dict[str, str], -) -> dict[str, Any]: - if record is None: - return { - "freshness": "missing", - "outcome": "missing", - "status": "missing", - "ready": False, - } - freshness = "current" if record["artifact_tuple"] == expected_binding else "stale" - if record["runner_blocked"]: - outcome = "runner-blocked" - elif record["outcome"] == "pass": - outcome = "pass" - else: - outcome = "fail" - ready = freshness == "current" and outcome == "pass" - return { - "freshness": freshness, - "outcome": outcome, - "status": outcome if freshness == "current" else "stale", - "ready": ready, - } - - -def evaluate( - contract: dict[str, Any], - request: dict[str, Any], -) -> dict[str, Any]: - validate_contract(contract) - validate_request(request) - artifact_tuple = request["artifact_tuple"] - expected_binding = tuple_binding(artifact_tuple) - evidence = request["evidence"]["experiments"] - experiments: dict[str, dict[str, Any]] = {} - for experiment in RELEASE_CRITICAL_EXPERIMENTS: - record = evidence.get(experiment) - status = status_for_record(record, expected_binding) - cells: dict[str, dict[str, Any]] = {} - if experiment == "polyglot": - recorded_cells = record.get("cells", {}) if record is not None else {} - for cell in POLYGLOT_CELLS: - cells[cell] = status_for_record(recorded_cells.get(cell), expected_binding) - status["ready"] = status["ready"] and all(cell["ready"] for cell in cells.values()) - if status["status"] == "pass" and not status["ready"]: - status["status"] = "fail" - status["source"] = record["source"] if record is not None else None - status["cells"] = cells - experiments[experiment] = status - gate = "pass" if all(status["ready"] for status in experiments.values()) else "fail" - return { - "schema": READOUT_SCHEMA, - "stable_version": STABLE_VERSION, - "artifact_tuple": { - "tag": artifact_tuple["tag"], - "commit": artifact_tuple["commit"], - "sha256": expected_binding["sha256"], - "components": artifact_tuple["components"], - }, - "contract": { - "schema": CONTRACT_SCHEMA, - "url": CONTRACT_URL, - "sha256": manifest_digest(contract), - }, - "evidence_gate": gate, - "stable_authorization": ("awaiting-human-decision" if gate == "pass" else "blocked"), - "prerelease_iteration": "allowed", - "experiments": experiments, - "historical_aggregate": { - "release_authority": "never-authoritative", - }, - } - - -def render_summary(readout: dict[str, Any]) -> str: - lines = [ - "# Stable 2.0 release-critical evidence", - "", - f"Evidence gate: **{readout['evidence_gate']}**", - "", - "| Experiment or SDK cell | Freshness | Outcome | Ready |", - "| --- | --- | --- | --- |", - ] - for experiment, status in readout["experiments"].items(): - lines.append( - f"| {experiment} | {status['freshness']} | {status['outcome']} | {'yes' if status['ready'] else 'no'} |" - ) - for cell, cell_status in status["cells"].items(): - lines.append( - f"| {experiment}/{cell} | {cell_status['freshness']} | " - f"{cell_status['outcome']} | {'yes' if cell_status['ready'] else 'no'} |" - ) - lines.extend( - [ - "", - "Historical aggregate pass percentages are not release authority.", - f"Prerelease iteration: {readout['prerelease_iteration']}.", - "", - ] - ) - return "\n".join(lines) - - -def verified_readout( - contract: dict[str, Any], - request: dict[str, Any], - path: Path, -) -> dict[str, Any]: - value = parse_json_file(path, "stable authorization readout", maximum_bytes=MAX_EVIDENCE_BYTES) - expected = evaluate(contract, request) - if value != expected or path.read_bytes() != canonical_json(expected): - raise CandidateError("stable authorization readout differs from the request and fixed contract") - return expected - - -def require_ready(readout: dict[str, Any]) -> None: - if readout["evidence_gate"] != "pass": - failures = [name for name, status in readout["experiments"].items() if not status["ready"]] - raise CandidateError("stable authorization is blocked by release-critical evidence: " + ", ".join(failures)) - if readout["stable_authorization"] != "awaiting-human-decision": - raise CandidateError("passing evidence must still await an explicit human decision") - - -def verify_artifact_tuple_candidate( - client: PublicClient, - request: dict[str, Any], -) -> None: - artifact_tuple = request["artifact_tuple"] - resolved = resolve_tag(client, CONTROL_REPOSITORY, artifact_tuple["tag"]) - if resolved != artifact_tuple["commit"]: - raise CandidateError("artifact tuple tag does not resolve to its requested immutable commit") - candidate = read_public_record( - client, - artifact_tuple["tag"], - artifact_tuple["commit"], - "release-candidate.json", - ) - if ( - not isinstance(candidate, dict) - or candidate.get("schema") != "durable-workflow.release-candidate/v1" - or candidate.get("channel") != "rc" - or candidate.get("components") != artifact_tuple["components"] - ): - raise CandidateError("artifact tuple differs from its immutable RC candidate record") - - -def verify_evidence_sources( - client: PublicClient, - request: dict[str, Any], -) -> None: - for experiment, record in request["evidence"]["experiments"].items(): - source = record["source"] - raw = client.bytes(source["url"]) - if len(raw) > MAX_EVIDENCE_BYTES: - raise CandidateError(f"{experiment} public evidence exceeds the 2 MiB limit") - if hashlib.sha256(raw).hexdigest() != source["sha256"]: - raise CandidateError(f"{experiment} public evidence digest differs") - expected = {key: value for key, value in record.items() if key != "source"} - if raw != canonical_json(expected): - raise CandidateError(f"{experiment} public evidence differs from the authorization request") - - -def environment_urls() -> tuple[str, str]: - activity = ( - f"https://github.com/{CONTROL_REPOSITORY}/deployments/activity_log" - f"?environments_filter={AUTHORIZATION_ENVIRONMENT}" - ) - api = f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{AUTHORIZATION_ENVIRONMENT}" - return activity, api - - -def protected_environment_evidence(client: PublicClient) -> dict[str, Any]: - activity_url, _api_url = environment_urls() - encoded = urllib.parse.quote(AUTHORIZATION_ENVIRONMENT, safe="") - headers = {"X-GitHub-Api-Version": API_VERSION} - environment = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}", - headers=headers, - accept="application/vnd.github+json", - ) - rules = environment.get("protection_rules") if isinstance(environment, dict) else None - reviewer_rules = [ - rule - for rule in rules or [] - if isinstance(rule, dict) and rule.get("type") == "required_reviewers" and rule.get("reviewers") - ] - rule_ids = sorted(rule["id"] for rule in reviewer_rules if type(rule.get("id")) is int and rule["id"] > 0) - reviewer_user_ids = sorted( - identity["id"] - for rule in reviewer_rules - for reviewer in rule.get("reviewers", []) - if isinstance(reviewer, dict) - and reviewer.get("type") == "User" - and isinstance((identity := reviewer.get("reviewer")), dict) - and type(identity.get("id")) is int - and identity["id"] > 0 - ) - branch_policy = environment.get("deployment_branch_policy") if isinstance(environment, dict) else None - if ( - not rule_ids - or reviewer_user_ids != [PRODUCT_OWNER_REVIEWER_ID] - or [rule.get("prevent_self_review") for rule in reviewer_rules] != [True] - or type(environment.get("id") if isinstance(environment, dict) else None) is not int - or environment["id"] < 1 - or environment.get("html_url") != activity_url - or branch_policy != {"custom_branch_policies": True, "protected_branches": False} - ): - raise CandidateError("stable authorization environment must require independent product-owner review") - policies = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/{encoded}/" - "deployment-branch-policies?per_page=100", - headers=headers, - accept="application/vnd.github+json", - ) - custom = policies.get("branch_policies") if isinstance(policies, dict) else None - if ( - not isinstance(custom, list) - or policies.get("total_count") != 1 - or len(custom) != 1 - or not isinstance(custom[0], dict) - or type(custom[0].get("id")) is not int - or custom[0]["id"] < 1 - or custom[0].get("name") != "main" - or custom[0].get("type", "branch") != "branch" - ): - raise CandidateError("stable authorization environment must allow only main") - return { - "custom_branch_policies": [{"id": custom[0]["id"], "name": "main"}], - "deployment_branch_policy": branch_policy, - "environment_id": environment["id"], - "environment_url": activity_url, - "prevent_self_review": True, - "required_reviewer_rule_ids": rule_ids, - "required_reviewer_user_ids": reviewer_user_ids, - } - - -def protected_run_evidence( - client: PublicClient, - *, - actor: str, - run_id: int, - run_attempt: int, - workflow_commit: str, - environment_protection: dict[str, Any], -) -> dict[str, Any]: - headers = {"X-GitHub-Api-Version": API_VERSION} - run = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - headers=headers, - accept="application/vnd.github+json", - ) - accepted_paths = {AUTHORIZATION_WORKFLOW, f"{AUTHORIZATION_WORKFLOW}@main"} - if ( - not isinstance(run, dict) - or run.get("actor", {}).get("login") != actor - or run.get("repository", {}).get("full_name") != CONTROL_REPOSITORY - or run.get("id") != run_id - or run.get("run_attempt") != run_attempt - or run.get("event") != "workflow_dispatch" - or run.get("path") not in accepted_paths - or run.get("head_branch") != "main" - or run.get("head_sha") != workflow_commit - or run.get("html_url") != f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id}" - ): - raise CandidateError("stable authorization workflow run evidence does not match GitHub") - history = client.json( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}/approvals", - headers=headers, - accept="application/vnd.github+json", - ) - if ( - not isinstance(history, list) - or len(history) != 1 - or not isinstance(history[0], dict) - or history[0].get("state") != "approved" - ): - raise CandidateError("stable authorization requires exactly one approved environment review") - review = history[0] - environments = review.get("environments") - if ( - not isinstance(review.get("comment"), str) - or not isinstance(environments, list) - or len(environments) != 1 - or not isinstance(environments[0], dict) - ): - raise CandidateError("stable authorization approval history is malformed") - activity_url, api_url = environment_urls() - environment = environments[0] - if ( - environment.get("id") != environment_protection["environment_id"] - or environment.get("name") != AUTHORIZATION_ENVIRONMENT - or environment.get("html_url") != activity_url - or environment.get("url") != api_url - or not isinstance(environment.get("node_id"), str) - or not environment["node_id"] - ): - raise CandidateError("stable authorization review names the wrong environment") - reviewer = user_identity(review.get("user"), "stable authorization approving reviewer") - if reviewer["id"] != PRODUCT_OWNER_REVIEWER_ID: - raise CandidateError("stable authorization review was not submitted by the product owner") - return { - "comment": review["comment"], - "environments": [ - { - "html_url": activity_url, - "id": environment["id"], - "name": AUTHORIZATION_ENVIRONMENT, - "node_id": environment["node_id"], - "url": api_url, - } - ], - "run_attempt": run_attempt, - "run_id": run_id, - "state": "approved", - "user": reviewer, - } - - -def authorization_tag(request: dict[str, Any]) -> str: - match = CANDIDATE_TAG_PATTERN.fullmatch(request["artifact_tuple"]["tag"]) - if match is None: - raise CandidateError("artifact tuple has no stable authorization identity") - return f"{AUTHORIZATION_TAG_PREFIX}{match.group('candidate')}" - - -def validate_existing_authorization( - value: Any, - request: dict[str, Any], - readout: dict[str, Any], - contract: dict[str, Any], -) -> dict[str, Any]: - authorization = require_exact_keys( - value, - { - "schema", - "channel", - "stable_version", - "artifact_tuple", - "contract", - "request_sha256", - "readout_sha256", - "evidence_gate", - "decision", - }, - "stable authorization", - ) - if ( - authorization["schema"] != AUTHORIZATION_SCHEMA - or authorization["channel"] != "stable" - or authorization["stable_version"] != STABLE_VERSION - or authorization["artifact_tuple"] != request["artifact_tuple"] - or authorization["contract"] != {"url": CONTRACT_URL, "sha256": manifest_digest(contract)} - or authorization["request_sha256"] != manifest_digest(request) - or authorization["readout_sha256"] != manifest_digest(readout) - or authorization["evidence_gate"] != "pass" - ): - raise CandidateError("existing stable authorization differs from this exact evidence decision") - decision = require_exact_keys( - authorization["decision"], - { - "status", - "type", - "actor", - "repository", - "workflow_ref", - "workflow_commit", - "run_id", - "run_attempt", - "run_url", - "environment", - "environment_protection", - "environment_approval", - }, - "stable authorization human decision", - ) - if ( - decision["status"] != "authorized" - or decision["type"] != "protected-human-review" - or decision["repository"] != CONTROL_REPOSITORY - or decision["environment"] != AUTHORIZATION_ENVIRONMENT - or decision["workflow_ref"] != AUTHORIZATION_WORKFLOW_REF - or not isinstance(decision["actor"], str) - or LOGIN_PATTERN.fullmatch(decision["actor"]) is None - or not isinstance(decision["workflow_commit"], str) - or COMMIT_PATTERN.fullmatch(decision["workflow_commit"]) is None - or type(decision["run_id"]) is not int - or decision["run_id"] < 1 - or type(decision["run_attempt"]) is not int - or decision["run_attempt"] < 1 - or decision["run_url"] != f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{decision['run_id']}" - ): - raise CandidateError("existing stable authorization lacks an explicit human decision") - protection = decision["environment_protection"] - approval = decision["environment_approval"] - if ( - not isinstance(protection, dict) - or protection.get("prevent_self_review") is not True - or protection.get("required_reviewer_user_ids") != [PRODUCT_OWNER_REVIEWER_ID] - or not isinstance(approval, dict) - or approval.get("state") != "approved" - or approval.get("run_id") != decision["run_id"] - or approval.get("run_attempt") != decision["run_attempt"] - or user_identity(approval.get("user"), "stable authorization approving reviewer")["id"] - != PRODUCT_OWNER_REVIEWER_ID - ): - raise CandidateError("existing stable authorization lacks an approved product-owner review") - return authorization - - -def existing_authorization( - repository: Path, - request: dict[str, Any], - readout: dict[str, Any], - contract: dict[str, Any], - *, - remote: str, - authoritative_authorization: Path | None = None, - authoritative_request: Path | None = None, - authoritative_readout: Path | None = None, - authoritative_contract: Path | None = None, -) -> dict[str, str] | None: - tag = authorization_tag(request) - reference = fetch_existing_record(repository, remote, tag) - if reference is None: - return None - files = { - "authorization": read_record_file(repository, reference, "stable-authorization.json"), - "request": read_record_file(repository, reference, "stable-authorization-request.json"), - "readout": read_record_file(repository, reference, "release-critical-readout.json"), - "contract": read_record_file(repository, reference, "release-critical-contract.json"), - } - if files["request"] != canonical_json(request): - raise CandidateError("existing stable authorization has a different request") - if files["readout"] != canonical_json(readout): - raise CandidateError("existing stable authorization has a different readout") - if files["contract"] != canonical_json(contract): - raise CandidateError("existing stable authorization has a different tier contract") - try: - authorization = json.loads(files["authorization"]) - except json.JSONDecodeError as error: - raise CandidateError("existing stable authorization is not valid JSON") from error - validate_existing_authorization(authorization, request, readout, contract) - outputs = ( - (authoritative_authorization, files["authorization"]), - (authoritative_request, files["request"]), - (authoritative_readout, files["readout"]), - (authoritative_contract, files["contract"]), - ) - for path, content in outputs: - if path is not None: - path.write_bytes(content) - return { - "status": "existing", - "tag": tag, - "commit": run_git(["rev-parse", f"{reference}^{{commit}}"], cwd=repository), - } - - -def check_authorization( - repository: Path, - contract_path: Path, - request_path: Path, - readout_path: Path, - *, - remote: str, -) -> dict[str, str]: - contract = load_contract(contract_path) - request = load_request(request_path) - readout = verified_readout(contract, request, readout_path) - require_ready(readout) - existing = existing_authorization( - repository, - request, - readout, - contract, - remote=remote, - ) - return existing or { - "status": "new", - "tag": authorization_tag(request), - } - - -def record_authorization( - repository: Path, - contract_path: Path, - request_path: Path, - readout_path: Path, - *, - remote: str, - authoritative_authorization: Path, - authoritative_request: Path, - authoritative_readout: Path, - authoritative_contract: Path, - client: PublicClient, - actor: str, - run_id: int, - run_attempt: int, - workflow_ref: str, - workflow_commit: str, -) -> dict[str, str]: - contract = load_contract(contract_path) - request = load_request(request_path) - readout = verified_readout(contract, request, readout_path) - require_ready(readout) - existing = existing_authorization( - repository, - request, - readout, - contract, - remote=remote, - authoritative_authorization=authoritative_authorization, - authoritative_request=authoritative_request, - authoritative_readout=authoritative_readout, - authoritative_contract=authoritative_contract, - ) - if existing is not None: - return existing - if ( - not isinstance(actor, str) - or LOGIN_PATTERN.fullmatch(actor) is None - or run_id < 1 - or run_attempt < 1 - or workflow_ref != AUTHORIZATION_WORKFLOW_REF - or COMMIT_PATTERN.fullmatch(workflow_commit) is None - ): - raise CandidateError("stable authorization workflow identity is invalid") - verify_artifact_tuple_candidate(client, request) - verify_evidence_sources(client, request) - protection = protected_environment_evidence(client) - approval = protected_run_evidence( - client, - actor=actor, - run_id=run_id, - run_attempt=run_attempt, - workflow_commit=workflow_commit, - environment_protection=protection, - ) - authorization = { - "schema": AUTHORIZATION_SCHEMA, - "channel": "stable", - "stable_version": STABLE_VERSION, - "artifact_tuple": request["artifact_tuple"], - "contract": { - "url": CONTRACT_URL, - "sha256": manifest_digest(contract), - }, - "request_sha256": manifest_digest(request), - "readout_sha256": manifest_digest(readout), - "evidence_gate": "pass", - "decision": { - "status": "authorized", - "type": "protected-human-review", - "actor": actor, - "repository": CONTROL_REPOSITORY, - "workflow_ref": workflow_ref, - "workflow_commit": workflow_commit, - "run_id": run_id, - "run_attempt": run_attempt, - "run_url": f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - "environment": AUTHORIZATION_ENVIRONMENT, - "environment_protection": protection, - "environment_approval": approval, - }, - } - validate_existing_authorization(authorization, request, readout, contract) - canonical_files = { - "stable-authorization.json": canonical_json(authorization), - "stable-authorization-request.json": canonical_json(request), - "release-critical-readout.json": canonical_json(readout), - "release-critical-contract.json": canonical_json(contract), - } - tag = authorization_tag(request) - with tempfile.NamedTemporaryFile(prefix="stable-authorization-index-", delete=False) as index: - index_path = Path(index.name) - try: - environment = os.environ.copy() - environment["GIT_INDEX_FILE"] = str(index_path) - index_path.unlink(missing_ok=True) - run_git(["read-tree", "--empty"], cwd=repository, env=environment) - for filename, content in canonical_files.items(): - blob = ( - subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=repository, - env=environment, - input=content, - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - run_git( - ["update-index", "--add", "--cacheinfo", f"100644,{blob},{filename}"], - cwd=repository, - env=environment, - ) - tree = run_git(["write-tree"], cwd=repository, env=environment) - commit_environment = environment | { - "GIT_AUTHOR_NAME": "Durable Workflow Stable Authorizer", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Stable Authorizer", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - } - commit = subprocess.run( - ["git", "commit-tree", tree], - cwd=repository, - env=commit_environment, - input=f"Record stable authorization for {request['artifact_tuple']['tag']}\n", - check=True, - text=True, - capture_output=True, - ).stdout.strip() - push = subprocess.run( - ["git", "push", remote, f"{commit}:refs/tags/{tag}"], - cwd=repository, - check=False, - text=True, - capture_output=True, - ) - if push.returncode: - recovered = existing_authorization( - repository, - request, - readout, - contract, - remote=remote, - authoritative_authorization=authoritative_authorization, - authoritative_request=authoritative_request, - authoritative_readout=authoritative_readout, - authoritative_contract=authoritative_contract, - ) - if recovered is None: - raise CandidateError(f"cannot publish immutable stable authorization: {push.stderr.strip()}") - return recovered - finally: - index_path.unlink(missing_ok=True) - authoritative_authorization.write_bytes(canonical_files["stable-authorization.json"]) - authoritative_request.write_bytes(canonical_files["stable-authorization-request.json"]) - authoritative_readout.write_bytes(canonical_files["release-critical-readout.json"]) - authoritative_contract.write_bytes(canonical_files["release-critical-contract.json"]) - return { - "status": "created", - "tag": tag, - "commit": commit, - } - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - commands = parser.add_subparsers(dest="command", required=True) - - readout = commands.add_parser("readout") - readout.add_argument("contract", type=Path) - readout.add_argument("request", type=Path) - readout.add_argument("destination", type=Path) - readout.add_argument("--verify-public-sources", action="store_true") - readout.add_argument("--github-summary", type=Path) - - ready = commands.add_parser("require-ready") - ready.add_argument("contract", type=Path) - ready.add_argument("request", type=Path) - ready.add_argument("readout", type=Path) - - check = commands.add_parser("check") - check.add_argument("contract", type=Path) - check.add_argument("request", type=Path) - check.add_argument("readout", type=Path) - check.add_argument("--repository", type=Path, default=Path.cwd()) - check.add_argument("--remote", default="origin") - check.add_argument("--github-output", type=Path) - - record = commands.add_parser("record") - record.add_argument("contract", type=Path) - record.add_argument("request", type=Path) - record.add_argument("readout", type=Path) - record.add_argument("--repository", type=Path, default=Path.cwd()) - record.add_argument("--remote", default="origin") - record.add_argument("--authoritative-authorization", required=True, type=Path) - record.add_argument("--authoritative-request", required=True, type=Path) - record.add_argument("--authoritative-readout", required=True, type=Path) - record.add_argument("--authoritative-contract", required=True, type=Path) - record.add_argument("--actor", required=True) - record.add_argument("--run-id", required=True, type=int) - record.add_argument("--run-attempt", required=True, type=int) - record.add_argument("--workflow-ref", required=True) - record.add_argument("--workflow-commit", required=True) - record.add_argument("--github-output", type=Path) - - arguments = parser.parse_args() - try: - if arguments.command == "readout": - contract = load_contract(arguments.contract) - request = load_request(arguments.request) - if arguments.verify_public_sources: - client = PublicClient(os.environ.get("GITHUB_TOKEN")) - verify_artifact_tuple_candidate(client, request) - verify_evidence_sources(client, request) - result = evaluate(contract, request) - arguments.destination.write_bytes(canonical_json(result)) - if arguments.github_summary is not None: - with arguments.github_summary.open("a", encoding="utf-8") as summary: - summary.write(render_summary(result)) - elif arguments.command == "require-ready": - contract = load_contract(arguments.contract) - request = load_request(arguments.request) - require_ready(verified_readout(contract, request, arguments.readout)) - elif arguments.command == "check": - result = check_authorization( - arguments.repository, - arguments.contract, - arguments.request, - arguments.readout, - remote=arguments.remote, - ) - write_github_output(arguments.github_output, result) - print(json.dumps(result, sort_keys=True)) - else: - result = record_authorization( - arguments.repository, - arguments.contract, - arguments.request, - arguments.readout, - remote=arguments.remote, - authoritative_authorization=arguments.authoritative_authorization, - authoritative_request=arguments.authoritative_request, - authoritative_readout=arguments.authoritative_readout, - authoritative_contract=arguments.authoritative_contract, - client=PublicClient(os.environ.get("GITHUB_TOKEN")), - actor=arguments.actor, - run_id=arguments.run_id, - run_attempt=arguments.run_attempt, - workflow_ref=arguments.workflow_ref, - workflow_commit=arguments.workflow_commit, - ) - write_github_output(arguments.github_output, result) - print(json.dumps(result, sort_keys=True)) - except CandidateError as error: - print(f"stable authorization failed: {error}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/waterline_service_conformance.py b/scripts/waterline_service_conformance.py deleted file mode 100644 index 6a73a79..0000000 --- a/scripts/waterline_service_conformance.py +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env python3 -"""Exercise the exact Waterline service image against the exact standalone server.""" - -from __future__ import annotations - -import argparse -import contextlib -import datetime as dt -import json -import os -import re -import subprocess -import time -import urllib.request -from pathlib import Path -from typing import Any - -SCHEMA = "durable-workflow.beta-conformance.waterline-service.v1" -SCENARIO = "service_image_php_sdk_standalone" -IMAGE_PATTERN = re.compile(r"^docker\.io/durableworkflow/waterline@(?Psha256:[0-9a-f]{64})$") -VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?$") - - -class ServiceConformanceError(RuntimeError): - """The published Waterline service artifact did not satisfy its runtime contract.""" - - -def now() -> str: - return dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def docker(arguments: list[str], *, timeout: int = 180, check: bool = True) -> subprocess.CompletedProcess[str]: - try: - process = subprocess.run( - ["docker", *arguments], - check=False, - capture_output=True, - text=True, - timeout=timeout, - ) - except (FileNotFoundError, subprocess.TimeoutExpired) as error: - raise ServiceConformanceError("Docker is unavailable for the Waterline service runner") from error - if check and process.returncode: - raise ServiceConformanceError(f"Docker {arguments[0]} failed for the Waterline service runner") - return process - - -def required_environment() -> dict[str, str]: - names = ( - "DW_SERVER_VERSION", - "DW_PHP_SDK_VERSION", - "DW_WATERLINE_VERSION", - "DW_WATERLINE_SERVICE_IMAGE", - "DW_WATERLINE_SERVICE_DOCKER_NETWORK", - "DW_WATERLINE_SERVICE_SERVER_URL", - "DW_WATERLINE_SERVICE_NAMESPACE", - "DW_WATERLINE_SERVICE_TOKEN", - ) - values = {name: os.environ.get(name, "") for name in names} - if any(not value for value in values.values()): - raise ServiceConformanceError("Waterline service runner environment is incomplete") - for name in ("DW_SERVER_VERSION", "DW_PHP_SDK_VERSION", "DW_WATERLINE_VERSION"): - if VERSION_PATTERN.fullmatch(values[name]) is None: - raise ServiceConformanceError(f"{name} is not an exact release version") - if IMAGE_PATTERN.fullmatch(values["DW_WATERLINE_SERVICE_IMAGE"]) is None: - raise ServiceConformanceError("DW_WATERLINE_SERVICE_IMAGE is not digest-pinned") - return values - - -def wait_for_url(url: str, *, timeout: int = 120) -> bytes: - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - try: - with urllib.request.urlopen(url, timeout=3) as response: - if response.status < 500: - return response.read(1024 * 1024) - except Exception: - time.sleep(2) - raise ServiceConformanceError("Waterline service did not become ready before its deadline") - - -def installed_sdk_version(container: str) -> str: - inspection = ( - "$installed=require '/app/vendor/composer/installed.php';" - "$version=$installed['versions']['durable-workflow/sdk']['pretty_version']??'';" - "fwrite(STDOUT,(string)$version);" - ) - result = docker(["exec", container, "php", "-r", inspection], timeout=30) - version = result.stdout.strip().removeprefix("v") - if VERSION_PATTERN.fullmatch(version) is None: - raise ServiceConformanceError("Waterline service image does not expose an exact installed PHP SDK version") - return version - - -def distribution_identity(image: str, version: str) -> dict[str, Any]: - match = IMAGE_PATTERN.fullmatch(image) - if match is None: - raise ServiceConformanceError("Waterline service image is not digest-pinned") - return { - "kind": "oci", - "locator": f"oci:docker.io/durableworkflow/waterline@{version}", - "artifacts": [{"name": "manifest", "sha256": match.group("digest").removeprefix("sha256:")}], - } - - -def write_result( - result_dir: Path, - environment: dict[str, str], - started_at: str, - *, - status: str, - runner_blocked: bool, - identity: dict[str, Any] | None, - summary: str | None = None, -) -> None: - findings = [] - if summary: - findings.append( - { - "type": "waterline_service_execution_failure", - "owning_contract": "signals-and-queries", - "summary": summary[:2048], - } - ) - result = { - "schema": SCHEMA, - "started_at": started_at, - "finished_at": now(), - "outcome": "pass" if status == "pass" else "fail", - "runner_blocked": runner_blocked, - "artifact_versions": { - "server": environment["DW_SERVER_VERSION"], - "waterline": environment["DW_WATERLINE_VERSION"], - }, - "executed_distribution_identities": ({"waterline-service": identity} if identity is not None else {}), - "local_product_source_checkout_used": False, - "scenario_results": { - SCENARIO: { - "scenario_id": SCENARIO, - "status": status, - } - }, - "findings": findings, - } - result_dir.mkdir(parents=True, exist_ok=True) - (result_dir / "waterline-service-conformance-result.json").write_text( - json.dumps(result, indent=2, sort_keys=True) + "\n", - encoding="utf-8", - ) - - -def run(result_dir: Path) -> int: - started_at = now() - environment: dict[str, str] = {} - identity = None - container = f"dw-waterline-service-{os.getpid()}-{time.time_ns()}" - try: - environment = required_environment() - image = environment["DW_WATERLINE_SERVICE_IMAGE"] - docker(["pull", image], timeout=300) - inspection = docker(["image", "inspect", "--format", "{{json .RepoDigests}}", image], timeout=30) - digest = IMAGE_PATTERN.fullmatch(image).group("digest") # type: ignore[union-attr] - if digest not in inspection.stdout: - raise ServiceConformanceError("pulled Waterline service image does not retain the candidate digest") - identity = distribution_identity(image, environment["DW_WATERLINE_VERSION"]) - docker( - [ - "run", - "-d", - "--rm", - "--name", - container, - "--network", - environment["DW_WATERLINE_SERVICE_DOCKER_NETWORK"], - "-p", - "127.0.0.1::8080", - "-e", - f"WATERLINE_SERVER_ENDPOINT={environment['DW_WATERLINE_SERVICE_SERVER_URL']}", - "-e", - f"WATERLINE_SERVER_TOKEN={environment['DW_WATERLINE_SERVICE_TOKEN']}", - "-e", - f"WATERLINE_NAMESPACE={environment['DW_WATERLINE_SERVICE_NAMESPACE']}", - "-e", - "WATERLINE_ACCESS_MODE=read_only", - "-e", - "WATERLINE_ALLOW_UNAUTHENTICATED=true", - image, - ], - timeout=60, - ) - port = docker(["port", container, "8080/tcp"], timeout=30).stdout.strip().rsplit(":", 1)[-1] - if not port.isdigit(): - raise ServiceConformanceError("Waterline service image did not publish its HTTP port") - base_url = f"http://127.0.0.1:{port}" - wait_for_url(f"{base_url}/up") - sdk_version = installed_sdk_version(container) - if sdk_version != environment["DW_PHP_SDK_VERSION"]: - raise ServiceConformanceError("Waterline service image contains a mismatched PHP SDK version") - payload = json.loads(wait_for_url(f"{base_url}/waterline/api/flows/running")) - if not isinstance(payload, dict) or not isinstance(payload.get("data"), list): - raise ServiceConformanceError("Waterline service did not return the standalone workflow list shape") - write_result(result_dir, environment, started_at, status="pass", runner_blocked=False, identity=identity) - return 0 - except (ServiceConformanceError, json.JSONDecodeError) as error: - if environment: - write_result( - result_dir, - environment, - started_at, - status="runner_blocked" if identity is None else "fail", - runner_blocked=identity is None, - identity=identity, - summary=str(error), - ) - return 1 - finally: - with contextlib.suppress(ServiceConformanceError): - docker(["rm", "--force", container], timeout=30, check=False) - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--result-dir", type=Path, required=True) - arguments = parser.parse_args() - return run(arguments.result_dir) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/waterline_train.py b/scripts/waterline_train.py deleted file mode 100644 index d04f509..0000000 --- a/scripts/waterline_train.py +++ /dev/null @@ -1,817 +0,0 @@ -#!/usr/bin/env python3 -"""Qualify a sequential Waterline successor from immutable public evidence.""" - -from __future__ import annotations - -import argparse -import datetime as dt -import hashlib -import json -import os -import re -import subprocess -import sys -import tempfile -import urllib.parse -from collections.abc import Mapping, Sequence -from pathlib import Path -from typing import Any - -if __package__ in {None, ""}: - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from scripts.beta_candidate import ( - COMPONENTS, - WATERLINE_SERVICE, - CandidateError, - PublicClient, - require_tag_commit, - resolve_github_tag, - verify_composer, - verify_oci, -) -from scripts.release_plan import validate_recorded_plan - -ROOT = Path(__file__).resolve().parents[1] -CONTRACT_PATH = ROOT / "waterline-train" / "contract.json" -CONTRACT_SCHEMA = "durable-workflow.waterline-release-train/v1" -COMPLETION_SCHEMA = "durable-workflow.waterline-release-completion/v3" -CONTROL_REPOSITORY = "durable-workflow/.github" -DOCS_REPOSITORY = "durable-workflow/durable-workflow.github.io" -DOCS_AUDIT_URL = "https://durable-workflow.com/docs-page-release-audit.json" -SUPPORTED_DOCS_AUDIT_SCHEMA_VERSIONS = frozenset((6, 7, 8)) -QUICKSTART_CONTRACT_URL = "https://durable-workflow.com/quickstart-execution-contract.json" -QUICKSTART_EVIDENCE_URL = re.compile( - r"^https://durable-workflow\.com/platform-conformance/evidence/" - r"[a-z0-9][a-z0-9._-]+\.json$" -) -PLAN_TAG = re.compile(r"^release-plan/[a-z0-9][a-z0-9._-]{0,55}$") -COMMIT = re.compile(r"^[0-9a-f]{40}$") -SHA256 = re.compile(r"^[0-9a-f]{64}$") -PRERELEASE = re.compile(r"^2\.0\.0-(?Pbeta|rc)\.(?P[1-9][0-9]*)$") -VERSION = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?$") -QUICKSTART_SCENARIOS = ( - "php_user_local_server_completion", - "python_user_local_server_completion", - "rust_user_local_server_completion", - "operator_local_server_observation", - "laravel_user_embedded_completion", -) -COMPLETION_REQUIRES = ( - "immutable_successor_plan_identity", - "source_bound_github_release", - "packagist_package", - "container_image", - "deployed_docs_artifact_tuple", - "exact_current_composer_laravel_boot", - "retained_five_scenario_quickstart", -) - - -class TrainError(RuntimeError): - """The Waterline/PHP release train is incomplete or inconsistent.""" - - -def canonical_json(value: Any) -> bytes: - return (json.dumps(value, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - - -def read_json(path: Path) -> dict[str, Any]: - try: - value = json.loads(path.read_text(encoding="utf-8")) - except (OSError, json.JSONDecodeError) as error: - raise TrainError(f"cannot read JSON from {path}: {error}") from error - if not isinstance(value, dict): - raise TrainError(f"{path} must contain a JSON object") - return value - - -def decode_json(raw: bytes, label: str, *, maximum: int = 1024 * 1024) -> dict[str, Any]: - if len(raw) > maximum: - raise TrainError(f"{label} exceeds the {maximum}-byte limit") - try: - value = json.loads(raw) - except (json.JSONDecodeError, UnicodeDecodeError) as error: - raise TrainError(f"{label} is not valid JSON") from error - if not isinstance(value, dict): - raise TrainError(f"{label} must contain a JSON object") - return value - - -def validate_contract(contract: Mapping[str, Any]) -> None: - expected = { - "$schema", - "schema", - "composer_tuple", - "dependency_policy", - "completion_requires", - "quickstart_scenarios", - } - if set(contract) != expected or contract.get("schema") != CONTRACT_SCHEMA: - raise TrainError("Waterline release-train contract has an invalid shape") - if contract.get("composer_tuple") != { - "waterline": "durable-workflow/waterline", - "workflow": "durable-workflow/workflow", - "sdk-php": "durable-workflow/sdk", - }: - raise TrainError("release train must qualify the exact three-package Composer tuple") - if contract.get("dependency_policy") != { - "waterline_to_sdk": "exact", - "conflicting_sdk_prerelease": "sequential_waterline_successor_required", - "historical_prereleases": "immutable", - "cross_prerelease_compatibility_shim": "forbidden", - }: - raise TrainError("release train must preserve exact immutable prerelease dependencies") - if tuple(contract.get("completion_requires", ())) != COMPLETION_REQUIRES: - raise TrainError("release train must require immutable fresh public completion evidence") - if tuple(contract.get("quickstart_scenarios", ())) != QUICKSTART_SCENARIOS: - raise TrainError("release train must require the five public quickstart scenarios") - - -def exact_sdk_requirement(waterline_manifest: Mapping[str, Any]) -> str: - require = waterline_manifest.get("require") - requirement = require.get("durable-workflow/sdk") if isinstance(require, dict) else None - if not isinstance(requirement, str) or PRERELEASE.fullmatch(requirement) is None: - raise TrainError("Waterline must declare one exact PHP SDK prerelease") - return requirement - - -def exact_workflow_requirement(waterline_manifest: Mapping[str, Any]) -> str: - require = waterline_manifest.get("require-dev") - requirement = require.get("durable-workflow/workflow") if isinstance(require, dict) else None - if not isinstance(requirement, str) or PRERELEASE.fullmatch(requirement) is None: - raise TrainError("Waterline must declare one exact Workflow prerelease") - return requirement - - -def next_prerelease(version: str) -> str: - match = PRERELEASE.fullmatch(version) - if match is None: - raise TrainError(f"Waterline version is not a supported prerelease: {version}") - return f"2.0.0-{match['channel']}.{int(match['number']) + 1}" - - -def previous_prerelease(version: str) -> str: - match = PRERELEASE.fullmatch(version) - if match is None or int(match["number"]) <= 1: - raise TrainError(f"Waterline version has no supported sequential predecessor: {version}") - return f"2.0.0-{match['channel']}.{int(match['number']) - 1}" - - -def advances(previous: str, successor: str) -> bool: - before = PRERELEASE.fullmatch(previous) - after = PRERELEASE.fullmatch(successor) - return bool( - before - and after - and before.group("channel") == after.group("channel") - and int(after.group("number")) > int(before.group("number")) - ) - - -def compatibility_decision( - sdk_version: str, - waterline_version: str, - waterline_manifest: Mapping[str, Any], -) -> dict[str, Any]: - if PRERELEASE.fullmatch(sdk_version) is None: - raise TrainError(f"PHP SDK version is not a supported prerelease: {sdk_version}") - required_sdk = exact_sdk_requirement(waterline_manifest) - if required_sdk == sdk_version: - return { - "action": "qualify_exact_current_tuple", - "sdk_version": sdk_version, - "waterline_version": waterline_version, - } - return { - "action": "route_sequential_waterline_successor", - "sdk_version": sdk_version, - "waterline_version": waterline_version, - "waterline_required_sdk": required_sdk, - "required_successor_version": next_prerelease(waterline_version), - } - - -def _parse_time(value: object, label: str) -> dt.datetime: - if not isinstance(value, str): - raise TrainError(f"{label} must be an RFC 3339 timestamp") - try: - parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError as error: - raise TrainError(f"{label} must be an RFC 3339 timestamp") from error - if parsed.tzinfo is None: - raise TrainError(f"{label} must include a timezone") - return parsed - - -def release_identity(release: Mapping[str, Any], *, label: str) -> tuple[int, str]: - release_id = release.get("id") - url = release.get("html_url") - if not isinstance(release_id, int) or isinstance(release_id, bool) or release_id < 1: - raise TrainError(f"{label} has no immutable release id") - if not isinstance(url, str) or not url.startswith("https://github.com/"): - raise TrainError(f"{label} has no canonical public URL") - return release_id, url - - -def plan_artifact_tuple(plan: Mapping[str, Any]) -> dict[str, str]: - components = plan.get("components") - if not isinstance(components, dict) or set(components) != set(COMPONENTS): - raise TrainError("successor plan must bind the complete release artifact tuple") - result: dict[str, str] = {} - for name, identity in components.items(): - version = identity.get("version") if isinstance(identity, dict) else None - commit = identity.get("commit") if isinstance(identity, dict) else None - if not isinstance(version, str) or not isinstance(commit, str) or COMMIT.fullmatch(commit) is None: - raise TrainError(f"successor plan has an invalid {name} identity") - result[name] = version - for name in ("waterline", "workflow", "sdk-php"): - if PRERELEASE.fullmatch(result[name]) is None: - raise TrainError(f"successor plan must bind an exact prerelease {name} version") - return result - - -def quickstart_contract_tuple(contract: Mapping[str, Any]) -> dict[str, str]: - artifacts = contract.get("artifacts") - if not isinstance(artifacts, dict) or set(artifacts) != set(COMPONENTS): - raise TrainError("deployed quickstart contract does not bind the complete artifact tuple") - - result: dict[str, str] = {} - for name in COMPONENTS: - artifact = artifacts[name] - version = artifact.get("version") if isinstance(artifact, dict) else None - if not isinstance(version, str) or VERSION.fullmatch(version) is None: - raise TrainError(f"deployed quickstart contract has an invalid {name} version") - result[name] = version - return result - - -def validate_successor_source( - plan: Mapping[str, Any], - manifest: Mapping[str, Any], - predecessor_manifest: Mapping[str, Any], -) -> dict[str, Any]: - versions = plan_artifact_tuple(plan) - if plan.get("channel") != "rc": - raise TrainError("current Waterline train completion requires an immutable release-candidate plan") - if manifest.get("name") != "durable-workflow/waterline": - raise TrainError("planned Waterline source has the wrong Composer package identity") - if exact_sdk_requirement(manifest) != versions["sdk-php"]: - raise TrainError("planned Waterline source does not pin the exact planned PHP SDK") - if exact_workflow_requirement(manifest) != versions["workflow"]: - raise TrainError("planned Waterline source does not pin the exact planned Workflow package") - declared_waterline = manifest.get("extra", {}).get("durable-workflow", {}).get("product-train") - if declared_waterline != versions["waterline"]: - raise TrainError("planned Waterline source does not declare the successor version") - - predecessor = previous_prerelease(versions["waterline"]) - predecessor_sdk = exact_sdk_requirement(predecessor_manifest) - if predecessor_manifest.get("name") != "durable-workflow/waterline": - raise TrainError("predecessor Waterline source has the wrong Composer package identity") - if not advances(predecessor_sdk, versions["sdk-php"]): - raise TrainError("successor does not repair an independently advanced PHP SDK prerelease") - return { - "outcome": "verified", - "kind": "sdk-advance-with-sequential-waterline-successor", - "predecessor_waterline": predecessor, - "predecessor_sdk_requirement": predecessor_sdk, - "successor_waterline": versions["waterline"], - "successor_sdk_requirement": versions["sdk-php"], - } - - -def github_release(client: PublicClient, repository: str, tag: str, expected_commit: str) -> dict[str, Any]: - source = resolve_github_tag(client, repository, tag) - require_tag_commit(source, expected_commit) - encoded = urllib.parse.quote(tag, safe="") - release = client.json(f"https://api.github.com/repos/{repository}/releases/tags/{encoded}") - if release.get("draft") or release.get("tag_name") != tag: - raise TrainError(f"GitHub release {repository}@{tag} is absent or still a draft") - published_at = release.get("published_at") - _parse_time(published_at, f"GitHub release {repository}@{tag}") - release_id, release_url = release_identity(release, label=f"GitHub release {repository}@{tag}") - return { - "outcome": "verified", - "repository": repository, - "version": tag, - "source": source, - "release_id": release_id, - "url": release_url, - "published_at": published_at, - } - - -def source_json( - client: PublicClient, - repository: str, - commit: str, - path: str, - label: str, -) -> tuple[dict[str, Any], str]: - if COMMIT.fullmatch(commit) is None: - raise TrainError(f"{label} source commit is invalid") - url = f"https://raw.githubusercontent.com/{repository}/{commit}/{path}" - raw = client.bytes(url) - return decode_json(raw, label), hashlib.sha256(raw).hexdigest() - - -def immutable_plan(client: PublicClient, plan_tag: str) -> tuple[dict[str, Any], dict[str, Any]]: - if PLAN_TAG.fullmatch(plan_tag) is None: - raise TrainError("plan tag must be an exact release-plan identity") - source = resolve_github_tag(client, CONTROL_REPOSITORY, plan_tag) - plan, raw_sha256 = source_json( - client, - CONTROL_REPOSITORY, - source["commit"], - "release-plan.json", - "immutable successor plan", - ) - try: - validate_recorded_plan(plan) - except CandidateError as error: - raise TrainError(f"immutable successor plan is invalid: {error}") from error - if plan_tag != f"release-plan/{plan['plan']}": - raise TrainError("plan tag does not match the immutable successor plan identity") - - encoded = urllib.parse.quote(plan_tag, safe="") - release = client.json(f"https://api.github.com/repos/{CONTROL_REPOSITORY}/releases/tags/{encoded}") - assets = [asset for asset in release.get("assets", []) if asset.get("name") == "release-plan.json"] - if release.get("draft") or release.get("tag_name") != plan_tag or len(assets) != 1: - raise TrainError("immutable successor plan has no exact GitHub release mirror") - release_id, _release_url = release_identity(release, label="immutable successor plan release") - asset_id = assets[0].get("id") - asset_url = assets[0].get("browser_download_url") - if not isinstance(asset_id, int) or isinstance(asset_id, bool) or asset_id < 1: - raise TrainError("immutable successor plan mirror has no release asset id") - if not isinstance(asset_url, str) or not asset_url.startswith("https://github.com/"): - raise TrainError("immutable successor plan mirror has no canonical public URL") - mirror_raw = client.bytes(asset_url) - if hashlib.sha256(mirror_raw).hexdigest() != raw_sha256 or decode_json(mirror_raw, "release plan mirror") != plan: - raise TrainError("release plan mirror differs from immutable Git authority") - return plan, { - "outcome": "verified", - "tag": plan_tag, - "record_commit": source["commit"], - "sha256": raw_sha256, - "release_id": release_id, - "release_asset_id": asset_id, - "release_asset_url": asset_url, - } - - -def verify_public_artifacts( - client: PublicClient, - plan: Mapping[str, Any], -) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: - versions = plan_artifact_tuple(plan) - components = plan["components"] - releases = { - name: github_release( - client, - COMPONENTS[name].repository, - versions[name], - components[name]["commit"], - ) - for name in ("waterline", "workflow", "sdk-php") - } - if _parse_time(releases["waterline"]["published_at"], "Waterline publication") <= _parse_time( - releases["sdk-php"]["published_at"], "PHP SDK publication" - ): - raise TrainError("Waterline successor publication must follow the PHP SDK prerelease") - - with tempfile.TemporaryDirectory(prefix="waterline-train-artifacts-") as temporary: - directory = Path(temporary) - distributions = { - name: verify_composer( - client, - COMPONENTS[name], - versions[name], - components[name]["commit"], - directory, - ) - for name in ("waterline", "workflow", "sdk-php") - } - waterline_image = verify_oci( - client, - WATERLINE_SERVICE, - versions["waterline"], - components["waterline"]["commit"], - directory, - ) - return releases, distributions, waterline_image - - -def install_laravel_boot_probe(root: Path) -> None: - provider = root / "app" / "Providers" / "ExactCurrentQualificationServiceProvider.php" - provider.write_text( - """app->bound(WorkflowClientInterface::class)) { - throw new RuntimeException( - 'The exact-current PHP SDK Laravel client contract is unavailable.' - ); - } - } -} -""", - encoding="utf-8", - ) - - providers = root / "bootstrap" / "providers.php" - try: - source = providers.read_text(encoding="utf-8") - except OSError as error: - raise TrainError("clean Laravel application has no provider manifest") from error - short_marker = " AppServiceProvider::class,\n" - qualified_marker = " App\\Providers\\AppServiceProvider::class,\n" - if short_marker in source: - source = source.replace( - "use App\\Providers\\AppServiceProvider;\n", - "use App\\Providers\\AppServiceProvider;\n" - "use App\\Providers\\ExactCurrentQualificationServiceProvider;\n", - 1, - ).replace( - short_marker, - short_marker + " ExactCurrentQualificationServiceProvider::class,\n", - 1, - ) - elif qualified_marker in source: - source = source.replace( - qualified_marker, - qualified_marker - + " App\\Providers\\ExactCurrentQualificationServiceProvider::class,\n", - 1, - ) - else: - raise TrainError("clean Laravel provider manifest has an unsupported shape") - providers.write_text(source, encoding="utf-8") - - -def solve_composer_tuple( - versions: Mapping[str, str], - *, - runner: Any = subprocess.run, - probe_installer: Any = install_laravel_boot_probe, -) -> dict[str, Any]: - composer_tuple = {name: versions[name] for name in ("waterline", "workflow", "sdk-php")} - manifest = { - "name": "durable-workflow/exact-current-qualification", - "minimum-stability": "RC", - "prefer-stable": True, - "require": { - "durable-workflow/waterline": composer_tuple["waterline"], - "durable-workflow/workflow": composer_tuple["workflow"], - "durable-workflow/sdk": composer_tuple["sdk-php"], - }, - } - with tempfile.TemporaryDirectory(prefix="waterline-train-composer-") as temporary: - root = Path(temporary) / "laravel" - try: - create = runner( - [ - "composer", - "create-project", - "laravel/laravel", - str(root), - "^13.0", - "--no-install", - "--no-scripts", - "--no-interaction", - "--no-progress", - "--prefer-dist", - ], - check=False, - capture_output=True, - text=True, - timeout=300, - ) - except (FileNotFoundError, subprocess.TimeoutExpired) as error: - raise TrainError("Composer is unavailable for clean Laravel qualification") from error - if create.returncode != 0: - raise TrainError("clean Laravel application could not be created for exact-current qualification") - - try: - install = runner( - [ - "composer", - "require", - "--working-dir", - str(root), - "--with-all-dependencies", - "--no-interaction", - "--no-progress", - "--prefer-dist", - f"durable-workflow/waterline:{composer_tuple['waterline']}@RC", - f"durable-workflow/workflow:{composer_tuple['workflow']}@RC", - f"durable-workflow/sdk:{composer_tuple['sdk-php']}@RC", - ], - check=False, - capture_output=True, - text=True, - timeout=600, - ) - except (FileNotFoundError, subprocess.TimeoutExpired) as error: - raise TrainError("Composer is unavailable for exact-current Laravel installation") from error - install_output = f"{install.stdout}\n{install.stderr}"[-64 * 1024 :] - if install.returncode != 0: - raise TrainError( - "exact-current Waterline, Workflow, and PHP SDK packages are not installable in Laravel" - ) - - probe_installer(root) - try: - discovery = runner( - ["php", "artisan", "package:discover", "--ansi", "--no-interaction"], - cwd=root, - check=False, - capture_output=True, - text=True, - timeout=300, - ) - except (FileNotFoundError, subprocess.TimeoutExpired) as error: - raise TrainError("Laravel is unavailable for exact-current package discovery") from error - discovery_output = f"{discovery.stdout}\n{discovery.stderr}"[-64 * 1024 :] - if discovery.returncode != 0: - raise TrainError("exact-current Composer graph does not boot through Laravel package discovery") - - return { - "outcome": "pass", - "artifact_tuple": composer_tuple, - "manifest_sha256": hashlib.sha256(canonical_json(manifest)).hexdigest(), - "install_output_sha256": hashlib.sha256(install_output.encode()).hexdigest(), - "package_discovery_output_sha256": hashlib.sha256(discovery_output.encode()).hexdigest(), - "laravel_boot": "pass", - } - - -def verify_docs_documents( - audit: Mapping[str, Any], - published_versions: Mapping[str, Any], - contract: Mapping[str, Any], - quickstart_evidence: Mapping[str, Any], - expected_versions: Mapping[str, str], -) -> dict[str, str]: - if ( - audit.get("schema") != "durable-workflow.docs.page-release-audit" - or audit.get("schema_version") not in SUPPORTED_DOCS_AUDIT_SCHEMA_VERSIONS - ): - raise TrainError("deployed docs release audit has an unsupported schema") - if audit.get("artifact_versions") != expected_versions: - raise TrainError("deployed docs do not name the exact immutable successor tuple") - compatibility = audit.get("artifact_compatibility_evidence") - if ( - not isinstance(compatibility, dict) - or compatibility.get("outcome") != "pass" - or compatibility.get("qualified_artifact_versions") != expected_versions - ): - raise TrainError("deployed docs install pins do not name the exact qualified successor tuple") - if published_versions.get("artifacts") != expected_versions: - raise TrainError("deployed docs revision does not bind the exact published artifact tuple") - if contract.get("schema") != "durable-workflow.docs.v2.quickstart-execution-contract": - raise TrainError("deployed quickstart contract has an unsupported schema") - contract_tuple = quickstart_contract_tuple(contract) - if contract_tuple != expected_versions: - raise TrainError("deployed quickstart contract does not name the exact execution tuple") - scenarios = contract.get("scenarios") - observed_scenarios = ( - tuple(item.get("id") for item in scenarios if isinstance(item, dict)) - if isinstance(scenarios, list) - else () - ) - if observed_scenarios != QUICKSTART_SCENARIOS: - raise TrainError("deployed quickstart contract does not require all five scenarios in order") - - qualification = audit.get("quickstart_qualification") - if not isinstance(qualification, dict) or qualification.get("outcome") != "pass": - raise TrainError("deployed docs lack passing five-scenario exact-current quickstart evidence") - if ( - qualification.get("role") != "five_scenario_exact_current" - or qualification.get("artifact_versions") != expected_versions - or tuple(qualification.get("required_scenarios", ())) != QUICKSTART_SCENARIOS - ): - raise TrainError("deployed quickstart qualification is stale or incomplete") - if audit.get("schema_version") >= 8 and ( - qualification.get("contract_artifact_versions") != contract_tuple - or qualification.get("execution_artifact_versions") != contract_tuple - ): - raise TrainError("deployed quickstart qualification does not bind contract and execution tuples") - evidence_identity = qualification.get("evidence") - if not isinstance(evidence_identity, dict): - raise TrainError("deployed quickstart qualification lacks retained evidence identity") - if ( - quickstart_evidence.get("schema") != "durable-workflow.v2.platform-conformance.run-evidence" - or quickstart_evidence.get("schema_version") != 1 - or quickstart_evidence.get("id") != evidence_identity.get("id") - or quickstart_evidence.get("experiment") != "quickstart" - or quickstart_evidence.get("evidence_kind") != "executed_run" - or quickstart_evidence.get("artifact_tuple") != contract_tuple - or quickstart_evidence.get("outcome") != "pass" - or quickstart_evidence.get("runner_blocked") is not False - ): - raise TrainError("retained quickstart evidence does not prove the exact current five-scenario run") - - evidence_qualification = quickstart_evidence.get("qualification") - scenario_results = ( - evidence_qualification.get("scenario_results") - if isinstance(evidence_qualification, dict) - else None - ) - expected_scenario_results = [ - {"id": scenario, "outcome": "pass"} for scenario in QUICKSTART_SCENARIOS - ] - if scenario_results != expected_scenario_results: - raise TrainError("retained quickstart evidence does not prove all five scenarios passed") - - exact_composer_graph = evidence_qualification.get("exact_composer_graph") - expected_composer_tuple = { - name: contract_tuple[name] for name in ("sdk-php", "waterline", "workflow") - } - expected_composer_fields = { - "outcome", - "artifact_tuple", - "manifest_sha256", - "install_output_sha256", - "package_discovery", - "package_discovery_output_sha256", - "laravel_boot", - } - if ( - not isinstance(exact_composer_graph, dict) - or set(exact_composer_graph) != expected_composer_fields - or exact_composer_graph.get("outcome") != "pass" - or exact_composer_graph.get("artifact_tuple") != expected_composer_tuple - or exact_composer_graph.get("package_discovery") != "pass" - or exact_composer_graph.get("laravel_boot") != "pass" - or any( - not isinstance(exact_composer_graph.get(field), str) - or SHA256.fullmatch(exact_composer_graph[field]) is None - for field in ( - "manifest_sha256", - "install_output_sha256", - "package_discovery_output_sha256", - ) - ) - ): - raise TrainError( - "retained quickstart evidence does not prove the exact Composer install, " - "package discovery, and Laravel boot" - ) - return contract_tuple - - -def verify_deployed_docs(client: PublicClient, expected_versions: Mapping[str, str]) -> dict[str, Any]: - audit_raw = client.bytes(DOCS_AUDIT_URL) - audit = decode_json(audit_raw, "deployed docs release audit") - revision = audit.get("docs_revision") - if not isinstance(revision, str) or COMMIT.fullmatch(revision) is None: - raise TrainError("deployed docs release audit has no immutable source revision") - versions_url = ( - f"https://raw.githubusercontent.com/{DOCS_REPOSITORY}/{revision}/" - "scripts/published-artifact-versions.json" - ) - versions_raw = client.bytes(versions_url) - published_versions = decode_json(versions_raw, "deployed docs artifact tuple source") - contract_raw = client.bytes(QUICKSTART_CONTRACT_URL) - contract = decode_json(contract_raw, "deployed quickstart contract") - - qualification = audit.get("quickstart_qualification") - evidence_identity = qualification.get("evidence") if isinstance(qualification, dict) else None - evidence_url = evidence_identity.get("url") if isinstance(evidence_identity, dict) else None - if not isinstance(evidence_url, str) or QUICKSTART_EVIDENCE_URL.fullmatch(evidence_url) is None: - raise TrainError("deployed quickstart qualification has no retained public evidence URL") - evidence_raw = client.bytes(evidence_url) - evidence = decode_json(evidence_raw, "retained quickstart evidence") - contract_tuple = verify_docs_documents( - audit, - published_versions, - contract, - evidence, - expected_versions, - ) - return { - "outcome": "pass", - "docs_revision": revision, - "audit_url": DOCS_AUDIT_URL, - "audit_sha256": hashlib.sha256(audit_raw).hexdigest(), - "artifact_tuple_source_url": versions_url, - "artifact_tuple_source_sha256": hashlib.sha256(versions_raw).hexdigest(), - "quickstart_contract_url": QUICKSTART_CONTRACT_URL, - "quickstart_contract_sha256": hashlib.sha256(contract_raw).hexdigest(), - "quickstart_evidence_url": evidence_url, - "quickstart_evidence_sha256": hashlib.sha256(evidence_raw).hexdigest(), - "quickstart_evidence_id": evidence["id"], - "quickstart_contract_artifact_tuple": contract_tuple, - "quickstart_execution_artifact_tuple": evidence["artifact_tuple"], - "artifact_tuple": dict(expected_versions), - } - - -def qualify_public_completion(client: PublicClient, plan_tag: str) -> dict[str, Any]: - plan, plan_authority = immutable_plan(client, plan_tag) - versions = plan_artifact_tuple(plan) - waterline_identity = plan["components"]["waterline"] - manifest, manifest_sha256 = source_json( - client, - COMPONENTS["waterline"].repository, - waterline_identity["commit"], - "composer.json", - "planned Waterline composer manifest", - ) - predecessor_version = previous_prerelease(versions["waterline"]) - predecessor_source = resolve_github_tag(client, COMPONENTS["waterline"].repository, predecessor_version) - predecessor_manifest, predecessor_manifest_sha256 = source_json( - client, - COMPONENTS["waterline"].repository, - predecessor_source["commit"], - "composer.json", - "predecessor Waterline composer manifest", - ) - transition = validate_successor_source(plan, manifest, predecessor_manifest) - transition.update( - { - "successor_composer_sha256": manifest_sha256, - "predecessor_source_commit": predecessor_source["commit"], - "predecessor_composer_sha256": predecessor_manifest_sha256, - } - ) - - try: - releases, distributions, waterline_image = verify_public_artifacts(client, plan) - except CandidateError as error: - raise TrainError(f"public successor artifacts are incomplete: {error}") from error - composer_resolution = solve_composer_tuple(versions) - docs = verify_deployed_docs(client, versions) - return { - "schema": COMPLETION_SCHEMA, - "outcome": "pass", - "plan_authority": plan_authority, - "artifact_tuple": versions, - "transition": transition, - "composer_resolution": composer_resolution, - "public_artifacts": { - "waterline": { - "github_release": releases["waterline"], - "packagist": distributions["waterline"], - "container_image": waterline_image, - }, - "workflow": { - "github_release": releases["workflow"], - "packagist": distributions["workflow"], - }, - "sdk-php": { - "github_release": releases["sdk-php"], - "packagist": distributions["sdk-php"], - }, - }, - "deployed_docs": docs, - "quickstart": { - "outcome": "pass", - "evidence_id": docs["quickstart_evidence_id"], - "evidence_url": docs["quickstart_evidence_url"], - "evidence_sha256": docs["quickstart_evidence_sha256"], - "artifact_tuple": versions, - "contract_artifact_tuple": docs["quickstart_contract_artifact_tuple"], - "execution_artifact_tuple": docs["quickstart_execution_artifact_tuple"], - "scenarios": list(QUICKSTART_SCENARIOS), - }, - } - - -def main(arguments: Sequence[str] | None = None) -> int: - parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers(dest="command", required=True) - subparsers.add_parser("validate-contract") - qualify = subparsers.add_parser("qualify-public") - qualify.add_argument("--plan-tag", required=True) - qualify.add_argument("--evidence", required=True, type=Path) - args = parser.parse_args(arguments) - - validate_contract(read_json(CONTRACT_PATH)) - if args.command == "qualify-public": - client = PublicClient(os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")) - evidence = qualify_public_completion(client, args.plan_tag) - args.evidence.write_bytes(canonical_json(evidence)) - print( - "Verified immutable public Waterline successor " - f"{evidence['artifact_tuple']['waterline']} with PHP SDK {evidence['artifact_tuple']['sdk-php']}" - ) - return 0 - - -if __name__ == "__main__": - try: - raise SystemExit(main()) - except (CandidateError, TrainError) as error: - print(f"Waterline release train incomplete: {error}") - raise SystemExit(1) from error diff --git a/stable-authorization/README.md b/stable-authorization/README.md deleted file mode 100644 index 0daadd9..0000000 --- a/stable-authorization/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Protected stable 2.0 authorization - -[`contract.json`](contract.json) is the public machine-readable release -authority for the stable 2.0 evidence gate. It fixes the release-critical -experiment set, the PHP, Python, and Rust polyglot cells, the seven artifact -components, and every fail-closed evidence rule. A historical aggregate pass -percentage is never part of the stable release claim. - -An authorization request conforming to -[`request-schema.json`](request-schema.json) selects one immutable -`release-candidate/rc/*` tag and its exact Workflow, Waterline, server, CLI, -PHP SDK, Python SDK, and Rust SDK identities. Every supplied experiment record -also cites that tuple by tag, commit, and canonical SHA-256. Its public source -is digest-bound and must contain the same canonical evidence fields except for -the source locator itself. - -The evaluator always creates a -[`readout-schema.json`](readout-schema.json) document. Missing tier records or -polyglot cells remain visible as missing; tuple mismatches are stale; execution -results are pass, fail, or runner-blocked. Incomplete evidence leaves stable -authorization blocked without preventing another prerelease or a later -readout. - -The `Stable authorization` workflow verifies the candidate tag and every -public evidence source before enforcing readiness. Only a passing evidence job -can start the `stable-authorization` environment job. That environment must -allow only `main`, require the product owner, and prevent self-review, so the -approval is an explicit human release decision made after the evidence gate. - -The resulting `stable-authorization/2.0.0/*` tag contains the exact contract, -request, readout, and authorization record. A GitHub Release with the same tag -mirrors all four files. Repeating the same request compares or repairs that -immutable authority; a changed tuple, evidence set, or readout fails closed. -The authorization record permits a later stable publication operation but -does not itself publish or retag any component. diff --git a/stable-authorization/authorization-schema.json b/stable-authorization/authorization-schema.json deleted file mode 100644 index 3cbbb0c..0000000 --- a/stable-authorization/authorization-schema.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/stable-authorization/authorization-schema.json", - "title": "Durable Workflow stable authorization", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "channel", - "stable_version", - "artifact_tuple", - "contract", - "request_sha256", - "readout_sha256", - "evidence_gate", - "decision" - ], - "properties": { - "schema": { - "const": "durable-workflow.stable-authorization/v1" - }, - "channel": { - "const": "stable" - }, - "stable_version": { - "const": "2.0.0" - }, - "artifact_tuple": { - "$ref": "#/$defs/artifactTuple" - }, - "contract": { - "type": "object", - "additionalProperties": false, - "required": ["url", "sha256"], - "properties": { - "url": { - "const": "https://raw.githubusercontent.com/durable-workflow/.github/main/stable-authorization/contract.json" - }, - "sha256": { - "$ref": "#/$defs/sha256" - } - } - }, - "request_sha256": { - "$ref": "#/$defs/sha256" - }, - "readout_sha256": { - "$ref": "#/$defs/sha256" - }, - "evidence_gate": { - "const": "pass" - }, - "decision": { - "type": "object", - "additionalProperties": false, - "required": [ - "status", - "type", - "actor", - "repository", - "workflow_ref", - "workflow_commit", - "run_id", - "run_attempt", - "run_url", - "environment", - "environment_protection", - "environment_approval" - ], - "properties": { - "status": { - "const": "authorized" - }, - "type": { - "const": "protected-human-review" - }, - "actor": { - "type": "string", - "pattern": "^[A-Za-z0-9-]{1,39}$" - }, - "repository": { - "const": "durable-workflow/.github" - }, - "workflow_ref": { - "const": "durable-workflow/.github/.github/workflows/stable-authorization.yml@refs/heads/main" - }, - "workflow_commit": { - "$ref": "#/$defs/commit" - }, - "run_id": { - "type": "integer", - "minimum": 1 - }, - "run_attempt": { - "type": "integer", - "minimum": 1 - }, - "run_url": { - "type": "string", - "pattern": "^https://github.com/durable-workflow/\\.github/actions/runs/[1-9][0-9]*$" - }, - "environment": { - "const": "stable-authorization" - }, - "environment_protection": { - "type": "object", - "required": ["prevent_self_review", "required_reviewer_user_ids"], - "properties": { - "prevent_self_review": { - "const": true - }, - "required_reviewer_user_ids": { - "const": [1130888] - } - } - }, - "environment_approval": { - "type": "object", - "required": ["state", "user"], - "properties": { - "state": { - "const": "approved" - }, - "user": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "const": 1130888 - } - } - } - } - } - } - } - }, - "$defs": { - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "component": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string" - }, - "commit": { - "$ref": "#/$defs/commit" - } - } - }, - "artifactTuple": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "components"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-candidate/rc/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "commit": { - "$ref": "#/$defs/commit" - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "properties": { - "workflow": { "$ref": "#/$defs/component" }, - "waterline": { "$ref": "#/$defs/component" }, - "server": { "$ref": "#/$defs/component" }, - "cli": { "$ref": "#/$defs/component" }, - "sdk-php": { "$ref": "#/$defs/component" }, - "sdk-python": { "$ref": "#/$defs/component" }, - "sdk-rust": { "$ref": "#/$defs/component" } - } - } - } - } - } -} diff --git a/stable-authorization/contract-schema.json b/stable-authorization/contract-schema.json deleted file mode 100644 index 2a21355..0000000 --- a/stable-authorization/contract-schema.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/stable-authorization/contract-schema.json", - "title": "Durable Workflow stable authorization contract", - "type": "object", - "additionalProperties": false, - "required": [ - "$schema", - "schema", - "stable_version", - "artifact_components", - "release_critical_experiments", - "required_sdk_cells", - "evidence_policy", - "human_decision" - ], - "properties": { - "$schema": { - "const": "./contract-schema.json" - }, - "schema": { - "const": "durable-workflow.stable-authorization.contract/v1" - }, - "stable_version": { - "const": "2.0.0" - }, - "artifact_components": { - "const": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ] - }, - "release_critical_experiments": { - "const": [ - "activities", - "cloud", - "heartbeats", - "namespaces", - "polyglot", - "replay", - "signals-queries", - "timers", - "worker-versioning", - "workflow-lifecycle", - "workflow-updates", - "python" - ] - }, - "required_sdk_cells": { - "const": { - "polyglot": [ - "php", - "python", - "rust" - ] - } - }, - "evidence_policy": { - "const": { - "artifact_tuple_binding": "exact", - "missing": "deny", - "stale": "deny", - "non_passing": "deny", - "runner_blocked": "deny", - "aggregate_historical_pass_rate": "never_authoritative" - } - }, - "human_decision": { - "const": { - "required": true, - "occurs_after_evidence_gate": true - } - } - } -} diff --git a/stable-authorization/contract.json b/stable-authorization/contract.json deleted file mode 100644 index 8e53136..0000000 --- a/stable-authorization/contract.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "$schema": "./contract-schema.json", - "schema": "durable-workflow.stable-authorization.contract/v1", - "stable_version": "2.0.0", - "artifact_components": [ - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust" - ], - "release_critical_experiments": [ - "activities", - "cloud", - "heartbeats", - "namespaces", - "polyglot", - "replay", - "signals-queries", - "timers", - "worker-versioning", - "workflow-lifecycle", - "workflow-updates", - "python" - ], - "required_sdk_cells": { - "polyglot": [ - "php", - "python", - "rust" - ] - }, - "evidence_policy": { - "artifact_tuple_binding": "exact", - "missing": "deny", - "stale": "deny", - "non_passing": "deny", - "runner_blocked": "deny", - "aggregate_historical_pass_rate": "never_authoritative" - }, - "human_decision": { - "required": true, - "occurs_after_evidence_gate": true - } -} diff --git a/stable-authorization/readout-schema.json b/stable-authorization/readout-schema.json deleted file mode 100644 index 6f9d570..0000000 --- a/stable-authorization/readout-schema.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/stable-authorization/readout-schema.json", - "title": "Durable Workflow stable release-critical evidence readout", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "stable_version", - "artifact_tuple", - "contract", - "evidence_gate", - "stable_authorization", - "prerelease_iteration", - "experiments", - "historical_aggregate" - ], - "properties": { - "schema": { - "const": "durable-workflow.stable-authorization-readout/v1" - }, - "stable_version": { - "const": "2.0.0" - }, - "artifact_tuple": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "sha256", "components"], - "properties": { - "tag": { "type": "string" }, - "commit": { "$ref": "#/$defs/commit" }, - "sha256": { "$ref": "#/$defs/sha256" }, - "components": { - "type": "object", - "minProperties": 7, - "maxProperties": 7, - "additionalProperties": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { "type": "string" }, - "commit": { "$ref": "#/$defs/commit" } - } - } - } - } - }, - "contract": { - "type": "object", - "additionalProperties": false, - "required": ["schema", "url", "sha256"], - "properties": { - "schema": { - "const": "durable-workflow.stable-authorization.contract/v1" - }, - "url": { - "const": "https://raw.githubusercontent.com/durable-workflow/.github/main/stable-authorization/contract.json" - }, - "sha256": { "$ref": "#/$defs/sha256" } - } - }, - "evidence_gate": { - "enum": ["pass", "fail"] - }, - "stable_authorization": { - "enum": ["awaiting-human-decision", "blocked"] - }, - "prerelease_iteration": { - "const": "allowed" - }, - "experiments": { - "type": "object", - "additionalProperties": false, - "required": [ - "activities", - "cloud", - "heartbeats", - "namespaces", - "polyglot", - "replay", - "signals-queries", - "timers", - "worker-versioning", - "workflow-lifecycle", - "workflow-updates", - "python" - ], - "properties": { - "activities": { "$ref": "#/$defs/status" }, - "cloud": { "$ref": "#/$defs/status" }, - "heartbeats": { "$ref": "#/$defs/status" }, - "namespaces": { "$ref": "#/$defs/status" }, - "polyglot": { "$ref": "#/$defs/polyglotStatus" }, - "replay": { "$ref": "#/$defs/status" }, - "signals-queries": { "$ref": "#/$defs/status" }, - "timers": { "$ref": "#/$defs/status" }, - "worker-versioning": { "$ref": "#/$defs/status" }, - "workflow-lifecycle": { "$ref": "#/$defs/status" }, - "workflow-updates": { "$ref": "#/$defs/status" }, - "python": { "$ref": "#/$defs/status" } - } - }, - "historical_aggregate": { - "type": "object", - "additionalProperties": false, - "required": ["release_authority"], - "properties": { - "release_authority": { - "const": "never-authoritative" - } - } - } - }, - "$defs": { - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "status": { - "type": "object", - "additionalProperties": false, - "required": ["freshness", "outcome", "status", "ready", "source", "cells"], - "properties": { - "freshness": { - "enum": ["current", "stale", "missing"] - }, - "outcome": { - "enum": ["pass", "fail", "runner-blocked", "missing"] - }, - "status": { - "enum": ["pass", "fail", "runner-blocked", "stale", "missing"] - }, - "ready": { - "type": "boolean" - }, - "source": { - "type": ["object", "null"] - }, - "cells": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/cellStatus" - } - } - } - }, - "polyglotStatus": { - "allOf": [ - { "$ref": "#/$defs/status" }, - { - "type": "object", - "properties": { - "cells": { - "type": "object", - "additionalProperties": false, - "required": ["php", "python", "rust"], - "properties": { - "php": { "$ref": "#/$defs/cellStatus" }, - "python": { "$ref": "#/$defs/cellStatus" }, - "rust": { "$ref": "#/$defs/cellStatus" } - } - } - } - } - ] - }, - "cellStatus": { - "type": "object", - "additionalProperties": false, - "required": ["freshness", "outcome", "status", "ready"], - "properties": { - "freshness": { - "enum": ["current", "stale", "missing"] - }, - "outcome": { - "enum": ["pass", "fail", "runner-blocked", "missing"] - }, - "status": { - "enum": ["pass", "fail", "runner-blocked", "stale", "missing"] - }, - "ready": { - "type": "boolean" - } - } - } - } -} diff --git a/stable-authorization/request-schema.json b/stable-authorization/request-schema.json deleted file mode 100644 index 0bfc737..0000000 --- a/stable-authorization/request-schema.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/stable-authorization/request-schema.json", - "title": "Durable Workflow stable authorization request", - "type": "object", - "additionalProperties": false, - "required": ["$schema", "schema", "stable_version", "artifact_tuple", "evidence"], - "properties": { - "$schema": { - "const": "./request-schema.json" - }, - "schema": { - "const": "durable-workflow.stable-authorization-request/v1" - }, - "stable_version": { - "const": "2.0.0" - }, - "artifact_tuple": { - "$ref": "#/$defs/artifactTuple" - }, - "evidence": { - "type": "object", - "additionalProperties": false, - "required": ["experiments"], - "properties": { - "experiments": { - "type": "object", - "additionalProperties": false, - "properties": { - "activities": { "$ref": "#/$defs/experimentEvidence" }, - "cloud": { "$ref": "#/$defs/experimentEvidence" }, - "heartbeats": { "$ref": "#/$defs/experimentEvidence" }, - "namespaces": { "$ref": "#/$defs/experimentEvidence" }, - "polyglot": { "$ref": "#/$defs/experimentEvidence" }, - "replay": { "$ref": "#/$defs/experimentEvidence" }, - "signals-queries": { "$ref": "#/$defs/experimentEvidence" }, - "timers": { "$ref": "#/$defs/experimentEvidence" }, - "worker-versioning": { "$ref": "#/$defs/experimentEvidence" }, - "workflow-lifecycle": { "$ref": "#/$defs/experimentEvidence" }, - "workflow-updates": { "$ref": "#/$defs/experimentEvidence" }, - "python": { "$ref": "#/$defs/experimentEvidence" } - } - } - } - } - }, - "$defs": { - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - }, - "sha256": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "component": { - "type": "object", - "additionalProperties": false, - "required": ["version", "commit"], - "properties": { - "version": { - "type": "string", - "pattern": "^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$" - }, - "commit": { - "$ref": "#/$defs/commit" - } - } - }, - "components": { - "type": "object", - "additionalProperties": false, - "required": ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python", "sdk-rust"], - "properties": { - "workflow": { "$ref": "#/$defs/component" }, - "waterline": { "$ref": "#/$defs/component" }, - "server": { "$ref": "#/$defs/component" }, - "cli": { "$ref": "#/$defs/component" }, - "sdk-php": { "$ref": "#/$defs/component" }, - "sdk-python": { "$ref": "#/$defs/component" }, - "sdk-rust": { "$ref": "#/$defs/component" } - } - }, - "artifactTuple": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "components"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-candidate/rc/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "commit": { - "$ref": "#/$defs/commit" - }, - "components": { - "$ref": "#/$defs/components" - } - } - }, - "artifactTupleBinding": { - "type": "object", - "additionalProperties": false, - "required": ["tag", "commit", "sha256"], - "properties": { - "tag": { - "type": "string", - "pattern": "^release-candidate/rc/[a-z0-9][a-z0-9._-]{0,55}$" - }, - "commit": { - "$ref": "#/$defs/commit" - }, - "sha256": { - "$ref": "#/$defs/sha256" - } - } - }, - "source": { - "type": "object", - "additionalProperties": false, - "required": ["url", "sha256", "generated_at"], - "properties": { - "url": { - "type": "string", - "pattern": "^https://(?:github\\.com|raw\\.githubusercontent\\.com)/durable-workflow/" - }, - "sha256": { - "$ref": "#/$defs/sha256" - }, - "generated_at": { - "type": "string", - "format": "date-time" - } - } - }, - "sdkCell": { - "type": "object", - "additionalProperties": false, - "required": ["outcome", "runner_blocked", "artifact_tuple"], - "properties": { - "outcome": { - "enum": ["pass", "fail", "error"] - }, - "runner_blocked": { - "type": "boolean" - }, - "artifact_tuple": { - "$ref": "#/$defs/artifactTupleBinding" - } - } - }, - "sdkCells": { - "type": "object", - "additionalProperties": false, - "properties": { - "php": { "$ref": "#/$defs/sdkCell" }, - "python": { "$ref": "#/$defs/sdkCell" }, - "rust": { "$ref": "#/$defs/sdkCell" } - } - }, - "experimentEvidence": { - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "experiment", - "outcome", - "runner_blocked", - "artifact_tuple", - "source" - ], - "properties": { - "schema": { - "const": "durable-workflow.release-critical-experiment-evidence/v1" - }, - "experiment": { - "enum": [ - "activities", - "cloud", - "heartbeats", - "namespaces", - "polyglot", - "replay", - "signals-queries", - "timers", - "worker-versioning", - "workflow-lifecycle", - "workflow-updates", - "python" - ] - }, - "outcome": { - "enum": ["pass", "fail", "error"] - }, - "runner_blocked": { - "type": "boolean" - }, - "artifact_tuple": { - "$ref": "#/$defs/artifactTupleBinding" - }, - "source": { - "$ref": "#/$defs/source" - }, - "cells": { - "$ref": "#/$defs/sdkCells" - } - } - } - } -} diff --git a/tests/fixtures/beta-conformance/activities-cross-language-php-sdk.json b/tests/fixtures/beta-conformance/activities-cross-language-php-sdk.json deleted file mode 100644 index c566d6e..0000000 --- a/tests/fixtures/beta-conformance/activities-cross-language-php-sdk.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "candidate": "rc-current-2-0-20260801", - "historical_rejection": { - "classification": "infrastructure_failure", - "failure_fingerprint": "bdd94127104739178d2048007f8dcd93e84cfd915c81e49a585be0aacd4b82af", - "reason": "published runner result retains artifact versions outside its required distributions: sdk-php" - }, - "native_result": { - "artifact_versions": { - "cli": "2.0.0-rc.12", - "sdk-php": "2.0.0-rc.6", - "sdk-python": "2.0.0-rc.8", - "server": "2.0.0-rc.13", - "waterline": "2.0.0-rc.9", - "workflow": "2.0.0-rc.12" - }, - "executed_distribution_identities": { - "cli": { - "artifacts": [ - { - "name": "SHA256SUMS", - "sha256": "0000000000000000000000000000000000000000000000000000000000000015" - } - ], - "kind": "github-release", - "locator": "github-release:durable-workflow/cli@2.0.0-rc.12" - }, - "sdk-php": { - "artifacts": [ - { - "name": "durable-workflow/sdk", - "sha256": "0000000000000000000000000000000000000000000000000000000000000005" - } - ], - "kind": "composer", - "locator": "composer:durable-workflow/sdk@2.0.0-rc.6" - }, - "sdk-python": { - "artifacts": [ - { - "name": "durable_workflow.whl", - "sha256": "0000000000000000000000000000000000000000000000000000000000000029" - } - ], - "kind": "pypi", - "locator": "pypi:durable-workflow@2.0.0-rc.8" - }, - "server": { - "artifacts": [ - { - "name": "manifest", - "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - } - ], - "kind": "oci", - "locator": "oci:docker.io/durableworkflow/server@2.0.0-rc.13" - }, - "waterline": { - "artifacts": [ - { - "name": "durable-workflow/waterline", - "sha256": "0000000000000000000000000000000000000000000000000000000000000002" - } - ], - "kind": "composer", - "locator": "composer:durable-workflow/waterline@2.0.0-rc.9" - }, - "workflow": { - "artifacts": [ - { - "name": "durable-workflow/workflow", - "sha256": "0000000000000000000000000000000000000000000000000000000000000001" - } - ], - "kind": "composer", - "locator": "composer:durable-workflow/workflow@2.0.0-rc.12" - } - }, - "findings": [], - "local_product_source_checkout_used": false, - "outcome": "pass", - "scenario_results": [ - { - "scenario_id": "published_artifact_install_only", - "status": "pass" - } - ], - "schema": "durable-workflow.v2.activity-runtime.result" - }, - "runner": "activities-cross-language", - "schema": "durable-workflow.beta-conformance.retained-result-regression/v1" -} diff --git a/tests/fixtures/github/cross-referenced-events.json b/tests/fixtures/github/cross-referenced-events.json deleted file mode 100644 index 921ac28..0000000 --- a/tests/fixtures/github/cross-referenced-events.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "data": { - "repository": { - "issue": { - "timelineItems": { - "nodes": [ - { - "__typename": "CrossReferencedEvent", - "actor": { - "login": "durable-workflow-ops" - }, - "id": "CRE_kwDOA1b2c84AAZ9x", - "referencedAt": "2026-07-24T09:00:00Z", - "source": { - "__typename": "PullRequest", - "number": 70, - "repository": { - "nameWithOwner": "durable-workflow/workflow" - }, - "url": "https://github.com/durable-workflow/workflow/pull/70" - }, - "willCloseTarget": true - }, - { - "__typename": "CrossReferencedEvent", - "actor": { - "login": "external-contributor" - }, - "id": "CRE_kwDOA1b2c84AAaB7", - "referencedAt": "2026-07-24T09:05:00Z", - "source": { - "__typename": "PullRequest", - "number": 71, - "repository": { - "nameWithOwner": "durable-workflow/server" - }, - "url": "https://github.com/durable-workflow/server/pull/71" - }, - "willCloseTarget": true - } - ], - "pageInfo": { - "hasNextPage": false, - "endCursor": "Y3Vyc29yOnYyOpHOAAaB7" - } - } - } - } - } -} diff --git a/tests/fixtures/github/pipeline-completion-comment.json b/tests/fixtures/github/pipeline-completion-comment.json deleted file mode 100644 index 2e80653..0000000 --- a/tests/fixtures/github/pipeline-completion-comment.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "body": "Completed in [`aaaaaaaaaaaa`](https://github.com/durable-workflow/.github/commit/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) on `main`.\n\nRequired public qualification Source Qualification (source-qualification.yml) passed in run [30200000001](https://github.com/durable-workflow/.github/actions/runs/30200000001).\n\nRequired cross-repository target qualification passed:\n- `durable-workflow.github.io:main` at [`bbbbbbbbbbbb`](https://github.com/durable-workflow/durable-workflow.github.io/commit/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) ([qualification](https://github.com/durable-workflow/durable-workflow.github.io/actions/runs/30200000002))\n- `workflow:v2` at [`cccccccccccc`](https://github.com/durable-workflow/workflow/commit/cccccccccccccccccccccccccccccccccccccccc) ([qualification](https://github.com/durable-workflow/workflow/actions/runs/30200000003))\n\n\n\n", - "completion_source": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "targets": [ - { - "branch": "main", - "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "qualification_run": 30200000001, - "required_check_run": 30200000001, - "repository": ".github", - "workflow_name": "Source Qualification", - "workflow_path": "source-qualification.yml" - }, - { - "branch": "main", - "commit": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "qualification_run": 30200000002, - "required_check_run": 30200000102, - "repository": "durable-workflow.github.io", - "workflow_name": null, - "workflow_path": "qualification.yml" - }, - { - "branch": "v2", - "commit": "cccccccccccccccccccccccccccccccccccccccc", - "qualification_run": 30200000003, - "required_check_run": 30200000103, - "repository": "workflow", - "workflow_name": null, - "workflow_path": "php.yml" - } - ] -} diff --git a/tests/test_beta_authorization.py b/tests/test_beta_authorization.py deleted file mode 100644 index 68a68b1..0000000 --- a/tests/test_beta_authorization.py +++ /dev/null @@ -1,1093 +0,0 @@ -from __future__ import annotations - -import copy -import json -import subprocess -import sys -import tempfile -import unittest -import urllib.parse -from pathlib import Path -from unittest import mock - -from jsonschema import Draft202012Validator -from referencing import Registry, Resource - -from scripts.beta_authorization import ( - AUTHORIZATION_SCHEMA, - AUTHORIZATION_WORKFLOW_REF, - EVIDENCE_SCHEMA, - REQUEST_SCHEMA, - CandidateError, - authority_issue_and_decision, - build_evidence, - check_authorization, - expected_qualification_commits, - protected_environment_evidence, - protected_run_evidence, - public_backlog_evidence, - record_authorization, - validate_qualification_evidence, - validate_request, - verify_candidate_evidence, - verify_conformance_evidence, - verify_continuity_evidence, - verify_qualified_heads_stable, -) -from scripts.beta_candidate import COMPONENTS, canonical_json, manifest_digest -from scripts.release_plan import verify_beta_authorization -from tests.verification_fixture import candidate_verification - -ROOT = Path(__file__).resolve().parents[1] - - -def authorization() -> dict[str, object]: - return { - "schema": AUTHORIZATION_SCHEMA, - "channel": "beta", - "candidate": "first-beta", - "components": { - name: { - "version": f"2.0.0-beta.{index + 1}" if name in {"workflow", "waterline"} else f"1.4.{index}", - "commit": f"{index + 1:040x}", - } - for index, name in enumerate(COMPONENTS) - }, - } - - -def request() -> dict[str, object]: - return { - "schema": REQUEST_SCHEMA, - "authorization": authorization(), - "evidence": { - "candidate": { - "tag": "beta-candidate/qualified-alpha", - "commit": "a" * 40, - }, - "conformance": { - "tag": "beta-conformance/qualified-alpha/123.1", - "commit": "d" * 40, - }, - "continuity": { - "complete": { - "tag": "beta-continuity/continuity-alpha/complete", - "commit": "e" * 40, - }, - "no_op": { - "tag": "beta-continuity/continuity-alpha/no-op-confirmed", - "commit": "f" * 40, - }, - }, - "decision": {"issue": 3, "comment": 91}, - }, - } - - -def candidate_manifest() -> dict[str, object]: - intended = authorization()["components"] - return { - "schema": "durable-workflow.beta-candidate/v2", - "candidate": "qualified-alpha", - "components": { - name: { - "version": f"2.0.0-alpha.{index + 1}" if name in {"workflow", "waterline"} else f"1.3.{index}", - "commit": intended[name]["commit"], - } - for index, name in enumerate(COMPONENTS) - }, - } - - -def qualification() -> dict[str, object]: - intended = authorization()["components"] - policy = json.loads((ROOT / "qualification" / "policy.json").read_bytes()) - return { - "schema": "durable-workflow.github-target-qualification/v1", - "targets": { - name: { - "action_releases": [], - "branch": target["branch"], - "commit": intended[name]["commit"] if name in intended else f"{index + 20:040x}", - "protected_checks": [workflow["required_check"] for workflow in target["workflows"]], - "successful_check_runs": { - workflow["required_check"]: index + 10 for workflow in target["workflows"] - }, - "workflow_trust": { - f".github/workflows/{workflow['path']}": { - "containers": [], - "external_actions": [], - "local_actions": [], - "privileged_jobs": [], - } - for workflow in target["workflows"] - }, - "workflows": [ - { - "path": f".github/workflows/{workflow['path']}", - "required_check": workflow["required_check"], - "workflow_id": index + 100, - } - for workflow in target["workflows"] - ], - } - for index, (name, target) in enumerate(policy["targets"].items()) - if target.get("public_audit", True) is not False - }, - } - - -def github_user(login: str, identifier: int) -> dict[str, object]: - return { - "login": login, - "id": identifier, - "node_id": f"U_{identifier}", - "url": f"https://api.github.com/users/{login}", - "html_url": f"https://github.com/{login}", - } - - -def environment_protection() -> dict[str, object]: - return { - "custom_branch_policies": [{"id": 23, "name": "main"}], - "deployment_branch_policy": {"custom_branch_policies": True, "protected_branches": False}, - "environment_id": 17, - "environment_url": ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=beta-authorization" - ), - "prevent_self_review": False, - "required_reviewer_rule_ids": [19], - "required_reviewer_user_ids": [1130888], - } - - -def environment_approval() -> dict[str, object]: - return { - "comment": "Approved for beta", - "environments": [ - { - "html_url": ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=beta-authorization" - ), - "id": 17, - "name": "beta-authorization", - "node_id": "ENV_17", - "url": "https://api.github.com/repos/durable-workflow/.github/environments/beta-authorization", - } - ], - "run_attempt": 1, - "run_id": 456, - "state": "approved", - "user": github_user("release-reviewer", 1130888), - } - - -def recorded_evidence(value: dict[str, object]) -> dict[str, object]: - return { - "schema": EVIDENCE_SCHEMA, - "authorization_sha256": manifest_digest(value["authorization"]), - "request_sha256": manifest_digest(value), - "decision": { - "repository": "durable-workflow/.github", - "issue": 3, - "issue_url": "https://github.com/durable-workflow/.github/issues/3", - "comment": 91, - "comment_url": "https://github.com/durable-workflow/.github/issues/3#issuecomment-91", - "author": github_user("release-operator", 7), - "body_sha256": "1" * 64, - }, - "candidate": { - "tag": "beta-candidate/qualified-alpha", - "commit": "a" * 40, - "manifest_sha256": "2" * 64, - "verification_sha256": "3" * 64, - }, - "qualification": { - "path": "target-qualification-evidence.json", - "sha256": manifest_digest(qualification()), - }, - "conformance": { - "tag": "beta-conformance/qualified-alpha/123.1", - "commit": "d" * 40, - "release": "https://github.com/durable-workflow/.github/releases/tag/conformance", - "run": { - "repository": "durable-workflow/.github", - "run_id": 123, - "run_attempt": 1, - "evidence_tag": "beta-conformance/qualified-alpha/123.1", - }, - }, - "continuity": { - "complete": value["evidence"]["continuity"]["complete"], - "no_op": value["evidence"]["continuity"]["no_op"], - "plan": {"tag": "release-plan/continuity-alpha", "commit": "4" * 40, "sha256": "5" * 64}, - }, - "backlog": { - "repositories": [ - f"durable-workflow/{name}" - for name in json.loads((ROOT / "issue-authority" / "policy.json").read_bytes())["repositories"] - ], - "allowed_authorization_gate": { - "repository": "durable-workflow/.github", - "number": 3, - "url": "https://github.com/durable-workflow/.github/issues/3", - }, - "unresolved_p0_p1": [], - }, - "github_authority": { - "actor": "release-operator", - "repository": "durable-workflow/.github", - "workflow_ref": AUTHORIZATION_WORKFLOW_REF, - "workflow_commit": "6" * 40, - "run_id": 456, - "run_attempt": 1, - "run_url": "https://github.com/durable-workflow/.github/actions/runs/456", - "environment": "beta-authorization", - "environment_protection": environment_protection(), - "environment_approval": environment_approval(), - }, - } - - -class RouteClient: - def __init__(self, json_routes: dict[str, object], byte_routes: dict[str, bytes] | None = None) -> None: - self.json_routes = json_routes - self.byte_routes = byte_routes or {} - self.requested: list[str] = [] - - def json(self, url: str, **_kwargs: object) -> object: - self.requested.append(url) - if url not in self.json_routes: - raise AssertionError(f"unexpected JSON URL: {url}") - return copy.deepcopy(self.json_routes[url]) - - def bytes(self, url: str, **_kwargs: object) -> bytes: - self.requested.append(url) - if url not in self.byte_routes: - raise AssertionError(f"unexpected bytes URL: {url}") - return self.byte_routes[url] - - -class BetaAuthorizationContractTest(unittest.TestCase): - def test_request_and_record_schemas_accept_the_canonical_contract(self) -> None: - record_schema = json.loads((ROOT / "beta-authorization" / "record-schema.json").read_bytes()) - request_schema = json.loads((ROOT / "beta-authorization" / "request-schema.json").read_bytes()) - record_resource = Resource.from_contents(record_schema) - registry = Registry().with_resource(record_schema["$id"], record_resource) - - Draft202012Validator(record_schema).validate(authorization()) - Draft202012Validator(request_schema, registry=registry).validate(request()) - Draft202012Validator( - json.loads((ROOT / "beta-authorization" / "evidence-schema.json").read_bytes()) - ).validate(recorded_evidence(request())) - - def test_validation_rejects_stable_channel_and_incomplete_evidence(self) -> None: - stable = request() - stable["authorization"]["channel"] = "stable" - with self.assertRaisesRegex(CandidateError, "only the beta channel"): - validate_request(stable) - - incomplete = request() - incomplete["evidence"].pop("conformance") - with self.assertRaisesRegex(CandidateError, "keys must be exactly"): - validate_request(incomplete) - - def test_fresh_qualification_pins_exactly_the_seven_intended_sources(self) -> None: - value = request() - self.assertEqual( - { - name: identity["commit"] - for name, identity in value["authorization"]["components"].items() - }, - expected_qualification_commits(value), - ) - - def test_workflow_is_protected_and_mirrors_both_git_records(self) -> None: - source = (ROOT / ".github" / "workflows" / "beta-authorization.yml").read_text(encoding="utf-8") - self.assertIn("environment: beta-authorization", source) - self.assertIn("contents: write", source) - self.assertIn("checks: read", source) - self.assertIn("issues: read", source) - self.assertIn("python scripts/beta_authorization.py record", source) - self.assertIn("--verify-tag --prerelease", source) - self.assertIn("beta-authorization-evidence.json", source) - self.assertIn("qualification_policy.py audit", source) - self.assertIn("target-qualification-evidence.json", source) - self.assertNotIn("stable-authorization", source) - - @mock.patch("scripts.release_plan.read_public_record") - def test_record_is_consumed_by_the_existing_beta_release_plan_contract(self, read_record: mock.Mock) -> None: - value = authorization() - plan = { - "plan": value["candidate"], - "components": value["components"], - "beta_authorization": {"tag": "beta-authorization/first-beta", "commit": "7" * 40}, - } - read_record.return_value = value - verify_beta_authorization(mock.Mock(), plan) - - changed = copy.deepcopy(value) - changed["components"]["server"]["version"] = "9.9.9" - read_record.return_value = changed - with self.assertRaisesRegex(CandidateError, "same candidate and seven-component tuple"): - verify_beta_authorization(mock.Mock(), plan) - - def test_cli_entry_points_are_directly_executable(self) -> None: - for command in ("validate", "expected-commits", "check", "record"): - process = subprocess.run( - [sys.executable, "scripts/beta_authorization.py", command, "--help"], - cwd=ROOT, - text=True, - capture_output=True, - check=False, - ) - self.assertEqual(0, process.returncode, process.stderr) - - -class ProtectedGitHubAuthorityTest(unittest.TestCase): - def test_environment_and_approval_bind_main_dispatch_and_both_identities(self) -> None: - activity = ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=beta-authorization" - ) - environment_url = "https://api.github.com/repos/durable-workflow/.github/environments/beta-authorization" - routes = { - environment_url: { - "id": 17, - "html_url": activity, - "protection_rules": [ - { - "id": 19, - "type": "required_reviewers", - "prevent_self_review": False, - "reviewers": [ - {"type": "User", "reviewer": {"id": 1130888}} - ], - } - ], - "deployment_branch_policy": {"custom_branch_policies": True, "protected_branches": False}, - }, - f"{environment_url}/deployment-branch-policies?per_page=100": { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "main", "type": "branch"}], - }, - "https://api.github.com/repos/durable-workflow/.github/actions/runs/456": { - "actor": {"login": "release-operator"}, - "event": "workflow_dispatch", - "head_branch": "main", - "head_sha": "6" * 40, - "html_url": "https://github.com/durable-workflow/.github/actions/runs/456", - "id": 456, - "path": ".github/workflows/beta-authorization.yml@main", - "repository": {"full_name": "durable-workflow/.github"}, - "run_attempt": 1, - }, - "https://api.github.com/repos/durable-workflow/.github/actions/runs/456/approvals": [ - { - "comment": "Approved for beta", - "environments": environment_approval()["environments"], - "state": "approved", - "user": github_user("release-reviewer", 1130888), - } - ], - } - client = RouteClient(routes) - protection = protected_environment_evidence(client) - approval = protected_run_evidence( - client, - actor="release-operator", - run_id=456, - run_attempt=1, - workflow_commit="6" * 40, - environment_protection=protection, - ) - self.assertEqual("release-reviewer", approval["user"]["login"]) - self.assertEqual([19], protection["required_reviewer_rule_ids"]) - self.assertEqual([1130888], protection["required_reviewer_user_ids"]) - - def test_decision_comment_must_be_by_dispatcher_and_bind_exact_authorization(self) -> None: - value = request() - digest = manifest_digest(value["authorization"]) - routes = { - "https://api.github.com/repos/durable-workflow/.github/issues/3": { - "number": 3, - "state": "open", - "html_url": "https://github.com/durable-workflow/.github/issues/3", - "labels": [{"name": name} for name in ( - "authority:github", - "beta:blocker", - "completion:evidence-required", - "kind:release-blocker", - "priority:P0", - )], - "body": "", - "milestone": {"title": "2.0 beta"}, - }, - "https://api.github.com/repos/durable-workflow/.github/issues/comments/91": { - "id": 91, - "issue_url": "https://api.github.com/repos/durable-workflow/.github/issues/3", - "html_url": "https://github.com/durable-workflow/.github/issues/3#issuecomment-91", - "author_association": "MEMBER", - "user": github_user("release-operator", 7), - "body": ( - "I authorize the beta release.\n\n" - f"" - ), - }, - } - evidence = authority_issue_and_decision(RouteClient(routes), value, actor="release-operator") - self.assertEqual("release-operator", evidence["author"]["login"]) - - changed = copy.deepcopy(value) - changed["authorization"]["components"]["server"]["commit"] = "9" * 40 - with self.assertRaisesRegex(CandidateError, "does not authorize this exact"): - authority_issue_and_decision(RouteClient(routes), changed, actor="release-operator") - - def test_backlog_allows_only_the_authorization_gate(self) -> None: - repositories = json.loads((ROOT / "issue-authority" / "policy.json").read_bytes())["repositories"] - routes: dict[str, object] = {} - for name in repositories: - repository = f"durable-workflow/{name}" - for priority in ("priority:P0", "priority:P1"): - query = urllib.parse.urlencode({"state": "open", "labels": priority, "per_page": 100, "page": 1}) - routes[f"https://api.github.com/repos/{repository}/issues?{query}"] = [] - p0_query = urllib.parse.urlencode( - {"state": "open", "labels": "priority:P0", "per_page": 100, "page": 1} - ) - routes[f"https://api.github.com/repos/durable-workflow/.github/issues?{p0_query}"] = [ - {"number": 3, "html_url": "https://github.com/durable-workflow/.github/issues/3"} - ] - evidence = public_backlog_evidence(RouteClient(routes)) - self.assertEqual([], evidence["unresolved_p0_p1"]) - - routes[f"https://api.github.com/repos/durable-workflow/server/issues?{p0_query}"] = [ - {"number": 27, "html_url": "https://github.com/durable-workflow/server/issues/27"} - ] - with self.assertRaisesRegex(CandidateError, "server#27"): - public_backlog_evidence(RouteClient(routes)) - - -class PublicEvidenceBindingTest(unittest.TestCase): - @mock.patch("scripts.beta_authorization.resolve_tag") - @mock.patch("scripts.beta_authorization.read_public_record") - def test_candidate_artifacts_must_bind_intended_source_commits( - self, - read_record: mock.Mock, - resolve: mock.Mock, - ) -> None: - value = request() - manifest = candidate_manifest() - verification = candidate_verification(manifest) - resolve.return_value = value["evidence"]["candidate"]["commit"] - read_record.side_effect = [manifest, verification] - release_url = "https://api.github.com/repos/durable-workflow/.github/releases/tags/beta-candidate%2Fqualified-alpha" - client = RouteClient( - { - release_url: { - "assets": [ - {"name": "candidate.json", "browser_download_url": "https://assets/candidate"}, - {"name": "verification.json", "browser_download_url": "https://assets/verification"}, - ] - } - }, - { - "https://assets/candidate": canonical_json(manifest), - "https://assets/verification": canonical_json(verification), - }, - ) - _manifest, evidence = verify_candidate_evidence(client, value) - self.assertEqual(value["evidence"]["candidate"]["commit"], evidence["commit"]) - - changed = copy.deepcopy(value) - changed["authorization"]["components"]["server"]["commit"] = "9" * 40 - read_record.side_effect = [manifest, verification] - with self.assertRaisesRegex(CandidateError, "source commits differ"): - verify_candidate_evidence(client, changed) - - def test_qualification_must_prove_every_intended_commit( - self, - ) -> None: - value = request() - proof = qualification() - self.assertEqual(proof, validate_qualification_evidence(proof, value)) - - proof["targets"]["server"]["successful_check_runs"] = {} - with self.assertRaisesRegex(CandidateError, "server source commit"): - validate_qualification_evidence(proof, value) - - def test_qualified_branch_heads_are_rechecked_before_publication(self) -> None: - value = request() - routes = { - ( - f"https://api.github.com/repos/{COMPONENTS[name].repository}/branches/" - f"{'v2' if name in {'workflow', 'waterline'} else 'main'}" - ): {"commit": {"sha": identity["commit"]}} - for name, identity in value["authorization"]["components"].items() - } - verify_qualified_heads_stable(RouteClient(routes), value, qualification()) - - routes["https://api.github.com/repos/durable-workflow/server/branches/main"] = { - "commit": {"sha": "9" * 40} - } - with self.assertRaisesRegex(CandidateError, "server source changed"): - verify_qualified_heads_stable(RouteClient(routes), value, qualification()) - - @mock.patch("scripts.beta_authorization.validate_conformance_release") - @mock.patch("scripts.beta_authorization.resolve_tag") - def test_conformance_must_be_the_cited_retained_passing_release( - self, - resolve: mock.Mock, - validate_release: mock.Mock, - ) -> None: - value = request() - reference = value["evidence"]["conformance"] - resolve.return_value = reference["commit"] - release_url = ( - "https://api.github.com/repos/durable-workflow/.github/releases/tags/" - "beta-conformance%2Fqualified-alpha%2F123.1" - ) - release = {"tag_name": reference["tag"]} - validate_release.return_value = { - "tag": reference["tag"], - "release": "https://github.com/durable-workflow/.github/releases/tag/conformance", - "run": { - "repository": "durable-workflow/.github", - "run_id": 123, - "run_attempt": 1, - "evidence_tag": reference["tag"], - }, - } - evidence = verify_conformance_evidence(RouteClient({release_url: release}), value, candidate_manifest()) - self.assertEqual(reference["commit"], evidence["commit"]) - - validate_release.return_value = None - with self.assertRaisesRegex(CandidateError, "not retained passing"): - verify_conformance_evidence(RouteClient({release_url: release}), value, candidate_manifest()) - - @mock.patch("scripts.beta_authorization.exact_completion_authority") - @mock.patch("scripts.beta_authorization.load_config", return_value={"drill": "continuity"}) - @mock.patch("scripts.beta_authorization.validate_plan") - @mock.patch("scripts.beta_authorization.read_public_record") - @mock.patch("scripts.beta_authorization.resolve_tag") - def test_continuity_requires_exact_complete_and_no_op_refs( - self, - resolve: mock.Mock, - read_record: mock.Mock, - _validate: mock.Mock, - _config: mock.Mock, - completion: mock.Mock, - ) -> None: - value = request() - plan = {"plan": "continuity-alpha"} - resolve.side_effect = [ - value["evidence"]["continuity"]["complete"]["commit"], - value["evidence"]["continuity"]["no_op"]["commit"], - ] - read_record.return_value = plan - completion.return_value = { - "plan_record": {"tag": "release-plan/continuity-alpha", "commit": "1" * 40, "sha256": "2" * 64} - } - result = verify_continuity_evidence(mock.Mock(), value) - self.assertEqual("release-plan/continuity-alpha", result["plan"]["tag"]) - completion.assert_called_once() - - @mock.patch("scripts.beta_authorization.verify_qualified_heads_stable") - @mock.patch("scripts.beta_authorization.verify_requested_refs_stable") - @mock.patch("scripts.beta_authorization.public_backlog_evidence") - @mock.patch("scripts.beta_authorization.verify_continuity_evidence") - @mock.patch("scripts.beta_authorization.verify_conformance_evidence") - @mock.patch("scripts.beta_authorization.verify_candidate_evidence") - @mock.patch("scripts.beta_authorization.authority_issue_and_decision") - @mock.patch("scripts.beta_authorization.protected_run_evidence") - @mock.patch("scripts.beta_authorization.protected_environment_evidence") - def test_first_publication_checks_every_authority_before_returning_evidence( - self, - protection: mock.Mock, - approval: mock.Mock, - decision: mock.Mock, - candidate: mock.Mock, - conformance: mock.Mock, - continuity: mock.Mock, - backlog: mock.Mock, - stable: mock.Mock, - qualified_heads: mock.Mock, - ) -> None: - value = request() - protection.return_value = environment_protection() - approval.return_value = environment_approval() - decision.return_value = recorded_evidence(value)["decision"] - candidate.return_value = (candidate_manifest(), recorded_evidence(value)["candidate"]) - conformance.return_value = recorded_evidence(value)["conformance"] - continuity.return_value = recorded_evidence(value)["continuity"] - backlog.return_value = recorded_evidence(value)["backlog"] - evidence = build_evidence( - mock.Mock(), - value, - qualification(), - actor="release-operator", - run_id=456, - run_attempt=1, - workflow_ref=AUTHORIZATION_WORKFLOW_REF, - workflow_commit="6" * 40, - ) - self.assertEqual(EVIDENCE_SCHEMA, evidence["schema"]) - stable.assert_called_once() - qualified_heads.assert_called_once() - self.assertEqual("release-reviewer", evidence["github_authority"]["environment_approval"]["user"]["login"]) - - -class ImmutableAuthorizationRecordTest(unittest.TestCase): - def setUp(self) -> None: - self.temporary = tempfile.TemporaryDirectory() - self.root = Path(self.temporary.name) - self.repository = self.root / "repository" - self.remote = self.root / "remote.git" - subprocess.run(["git", "init", "--bare", str(self.remote)], check=True, capture_output=True) - subprocess.run(["git", "init", "-b", "main", str(self.repository)], check=True, capture_output=True) - subprocess.run(["git", "config", "user.name", "Test"], cwd=self.repository, check=True) - subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=self.repository, check=True) - (self.repository / "README.md").write_text("test\n", encoding="utf-8") - historical_policy = self.repository / "qualification" / "policy.json" - historical_policy.parent.mkdir() - historical_policy.write_bytes((ROOT / "qualification" / "policy.json").read_bytes()) - subprocess.run( - ["git", "add", "README.md", "qualification/policy.json"], - cwd=self.repository, - check=True, - ) - subprocess.run(["git", "commit", "-m", "Initial"], cwd=self.repository, check=True, capture_output=True) - self.workflow_commit = subprocess.run( - ["git", "rev-parse", "HEAD"], - cwd=self.repository, - check=True, - capture_output=True, - text=True, - ).stdout.strip() - subprocess.run(["git", "remote", "add", "origin", str(self.remote)], cwd=self.repository, check=True) - subprocess.run(["git", "push", "origin", "main"], cwd=self.repository, check=True, capture_output=True) - self.request = request() - self.request_path = self.root / "request.json" - self.request_path.write_bytes(canonical_json(self.request)) - self.authoritative = self.root / "authorization.json" - self.evidence = self.root / "evidence.json" - self.qualification_path = self.root / "qualification.json" - self.qualification_path.write_bytes(canonical_json(qualification())) - self.authoritative_qualification = self.root / "authoritative-qualification.json" - - def tearDown(self) -> None: - self.temporary.cleanup() - - def record(self) -> dict[str, str]: - return record_authorization( - self.repository, - self.request_path, - qualification_path=self.qualification_path, - remote="origin", - authoritative_authorization=self.authoritative, - authoritative_evidence=self.evidence, - authoritative_qualification=self.authoritative_qualification, - client=mock.Mock(), - actor="release-operator", - run_id=456, - run_attempt=1, - workflow_ref=AUTHORIZATION_WORKFLOW_REF, - workflow_commit=self.workflow_commit, - ) - - def recorded_evidence( - self, - qualification_evidence: dict[str, object] | None = None, - ) -> dict[str, object]: - evidence = recorded_evidence(self.request) - evidence["github_authority"]["workflow_commit"] = self.workflow_commit - evidence["qualification"]["sha256"] = manifest_digest( - qualification_evidence if qualification_evidence is not None else qualification() - ) - return evidence - - def replace_remote_record( - self, - evidence: dict[str, object], - qualification_evidence: dict[str, object], - ) -> None: - for filename, value in ( - ("beta-authorization.json", self.request["authorization"]), - ("beta-authorization-evidence.json", evidence), - ("target-qualification-evidence.json", qualification_evidence), - ): - (self.repository / filename).write_bytes(canonical_json(value)) - subprocess.run( - [ - "git", - "add", - "beta-authorization.json", - "beta-authorization-evidence.json", - "target-qualification-evidence.json", - ], - cwd=self.repository, - check=True, - ) - subprocess.run( - ["git", "commit", "-m", "Replace retained record"], - cwd=self.repository, - check=True, - capture_output=True, - ) - subprocess.run( - ["git", "push", "--force", "origin", "HEAD:refs/tags/beta-authorization/first-beta"], - cwd=self.repository, - check=True, - capture_output=True, - ) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_identical_rerun_compares_without_revalidating_or_changing_record(self, build: mock.Mock) -> None: - self.assertEqual( - "new", - check_authorization(self.repository, self.request_path, remote="origin")["status"], - ) - build.return_value = self.recorded_evidence() - created = self.record() - self.assertEqual("created", created["status"]) - commit = created["commit"] - - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - self.qualification_path.unlink() - existing = self.record() - self.assertEqual("existing", existing["status"]) - self.assertEqual(commit, existing["commit"]) - self.assertEqual(canonical_json(self.request["authorization"]), self.authoritative.read_bytes()) - self.assertEqual( - "existing", - check_authorization(self.repository, self.request_path, remote="origin")["status"], - ) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_identical_rerun_recovers_record_after_current_qualification_policy_changes( - self, - build: mock.Mock, - ) -> None: - original_qualification = qualification() - original_evidence = self.recorded_evidence(original_qualification) - build.return_value = original_evidence - created = self.record() - self.assertEqual("created", created["status"]) - - self.authoritative.unlink() - self.evidence.unlink() - self.authoritative_qualification.unlink() - self.qualification_path.unlink() - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - - policy_path = ROOT / "qualification" / "policy.json" - changed_policy = json.loads(policy_path.read_bytes()) - changed_policy["targets"]["server"]["workflows"][0]["required_check"] = ( - "Future server qualification" - ) - changed_policy_bytes = canonical_json(changed_policy) - read_bytes = Path.read_bytes - - def read_with_changed_policy(path: Path) -> bytes: - if path == policy_path: - return changed_policy_bytes - return read_bytes(path) - - with mock.patch.object(Path, "read_bytes", read_with_changed_policy): - self.assertEqual( - "existing", - check_authorization(self.repository, self.request_path, remote="origin")["status"], - ) - existing = self.record() - - self.assertEqual("existing", existing["status"]) - self.assertEqual(created["commit"], existing["commit"]) - self.assertEqual(canonical_json(self.request["authorization"]), self.authoritative.read_bytes()) - self.assertEqual(canonical_json(original_evidence), self.evidence.read_bytes()) - self.assertEqual(canonical_json(original_qualification), self.authoritative_qualification.read_bytes()) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_recovery_rejects_retained_checks_that_disagree_with_recorded_workflow( - self, - build: mock.Mock, - ) -> None: - retained_qualification = qualification() - retained_evidence = self.recorded_evidence(retained_qualification) - build.return_value = retained_evidence - self.record() - - retained_qualification["targets"]["server"]["protected_checks"] = ["Unrelated check"] - retained_qualification["targets"]["server"]["successful_check_runs"] = { - "Unrelated check": 999 - } - retained_evidence["qualification"]["sha256"] = manifest_digest(retained_qualification) - self.replace_remote_record(retained_evidence, retained_qualification) - - self.authoritative.unlink() - self.evidence.unlink() - self.authoritative_qualification.unlink() - self.qualification_path.unlink() - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - - with self.assertRaisesRegex(CandidateError, "does not prove intended server source commit"): - self.record() - - build.assert_called_once() - self.assertFalse(self.authoritative.exists()) - self.assertFalse(self.evidence.exists()) - self.assertFalse(self.authoritative_qualification.exists()) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_recovery_rejects_malformed_retained_non_component_target( - self, - build: mock.Mock, - ) -> None: - retained_qualification = qualification() - retained_evidence = self.recorded_evidence(retained_qualification) - build.return_value = retained_evidence - self.record() - - retained_qualification["targets"]["documentation"] = {"malformed": True} - retained_evidence["qualification"]["sha256"] = manifest_digest(retained_qualification) - self.replace_remote_record(retained_evidence, retained_qualification) - - self.authoritative.unlink() - self.evidence.unlink() - self.authoritative_qualification.unlink() - self.qualification_path.unlink() - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - - with self.assertRaisesRegex( - CandidateError, - "qualification evidence for documentation has an invalid protected target record", - ): - self.record() - - build.assert_called_once() - self.assertFalse(self.authoritative.exists()) - self.assertFalse(self.evidence.exists()) - self.assertFalse(self.authoritative_qualification.exists()) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_recovery_rejects_malformed_retained_action_release( - self, - build: mock.Mock, - ) -> None: - retained_qualification = qualification() - retained_qualification["targets"]["server"]["action_releases"] = [ - { - "action": "actions/checkout", - "commit": "d23441a48e516b6c34aea4fa41551a30e30af803", - "reference": "d23441a48e516b6c34aea4fa41551a30e30af803", - "repository": "actions/checkout", - "runtime": "node24", - "version": "v6", - "workflows": [".github/workflows/phpunit-feature.yml"], - } - ] - retained_evidence = self.recorded_evidence(retained_qualification) - self.qualification_path.write_bytes(canonical_json(retained_qualification)) - build.return_value = retained_evidence - self.record() - - retained_qualification["targets"]["server"]["action_releases"] = ["malformed"] - retained_evidence["qualification"]["sha256"] = manifest_digest(retained_qualification) - self.replace_remote_record(retained_evidence, retained_qualification) - - self.authoritative.unlink() - self.evidence.unlink() - self.authoritative_qualification.unlink() - self.qualification_path.unlink() - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - - with self.assertRaisesRegex(CandidateError, "does not prove intended server source commit"): - self.record() - - build.assert_called_once() - self.assertFalse(self.authoritative.exists()) - self.assertFalse(self.evidence.exists()) - self.assertFalse(self.authoritative_qualification.exists()) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_recovery_rejects_deleted_historical_target(self, build: mock.Mock) -> None: - retained_qualification = qualification() - retained_evidence = self.recorded_evidence(retained_qualification) - build.return_value = retained_evidence - self.record() - - retained_qualification["targets"].pop("documentation") - retained_evidence["qualification"]["sha256"] = manifest_digest(retained_qualification) - self.replace_remote_record(retained_evidence, retained_qualification) - - self.authoritative.unlink() - self.evidence.unlink() - self.authoritative_qualification.unlink() - self.qualification_path.unlink() - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - - with self.assertRaisesRegex(CandidateError, "invalid authority shape"): - self.record() - - build.assert_called_once() - self.assertFalse(self.authoritative.exists()) - self.assertFalse(self.evidence.exists()) - self.assertFalse(self.authoritative_qualification.exists()) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_recovery_rejects_coherent_historical_target_replacement(self, build: mock.Mock) -> None: - retained_qualification = qualification() - retained_evidence = self.recorded_evidence(retained_qualification) - build.return_value = retained_evidence - self.record() - - documentation = retained_qualification["targets"]["documentation"] - documentation["branch"] = "v2" - documentation["protected_checks"] = ["Replacement documentation check"] - documentation["successful_check_runs"] = {"Replacement documentation check": 999} - documentation["workflows"] = [ - { - "path": ".github/workflows/replacement.yml", - "required_check": "Replacement documentation check", - "workflow_id": 998, - } - ] - retained_evidence["qualification"]["sha256"] = manifest_digest(retained_qualification) - self.replace_remote_record(retained_evidence, retained_qualification) - - self.authoritative.unlink() - self.evidence.unlink() - self.authoritative_qualification.unlink() - self.qualification_path.unlink() - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - - with self.assertRaisesRegex( - CandidateError, - "qualification evidence for documentation has an invalid protected target record", - ): - self.record() - - build.assert_called_once() - self.assertFalse(self.authoritative.exists()) - self.assertFalse(self.evidence.exists()) - self.assertFalse(self.authoritative_qualification.exists()) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_recovery_rejects_action_release_outside_historical_policy(self, build: mock.Mock) -> None: - retained_qualification = qualification() - retained_qualification["targets"]["server"]["action_releases"] = [ - { - "action": "actions/checkout", - "commit": "d23441a48e516b6c34aea4fa41551a30e30af803", - "reference": "d23441a48e516b6c34aea4fa41551a30e30af803", - "repository": "actions/checkout", - "runtime": "node24", - "version": "v6", - "workflows": [".github/workflows/phpunit-feature.yml"], - } - ] - retained_evidence = self.recorded_evidence(retained_qualification) - self.qualification_path.write_bytes(canonical_json(retained_qualification)) - build.return_value = retained_evidence - self.record() - - action_release = retained_qualification["targets"]["server"]["action_releases"][0] - action_release["reference"] = "v999" - action_release["runtime"] = "node999" - retained_evidence["qualification"]["sha256"] = manifest_digest(retained_qualification) - self.replace_remote_record(retained_evidence, retained_qualification) - - self.authoritative.unlink() - self.evidence.unlink() - self.authoritative_qualification.unlink() - self.qualification_path.unlink() - build.side_effect = AssertionError("existing immutable authorization must not be rebuilt") - - with self.assertRaisesRegex(CandidateError, "does not prove intended server source commit"): - self.record() - - build.assert_called_once() - self.assertFalse(self.authoritative.exists()) - self.assertFalse(self.evidence.exists()) - self.assertFalse(self.authoritative_qualification.exists()) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_new_authorization_remains_bound_to_current_qualification_policy( - self, - build: mock.Mock, - ) -> None: - policy_path = ROOT / "qualification" / "policy.json" - changed_policy = json.loads(policy_path.read_bytes()) - changed_policy["targets"]["server"]["workflows"][0]["required_check"] = ( - "Future server qualification" - ) - changed_policy_bytes = canonical_json(changed_policy) - read_bytes = Path.read_bytes - - def read_with_changed_policy(path: Path) -> bytes: - if path == policy_path: - return changed_policy_bytes - return read_bytes(path) - - with ( - mock.patch.object(Path, "read_bytes", read_with_changed_policy), - self.assertRaisesRegex(CandidateError, "does not prove intended server source commit"), - ): - self.record() - - build.assert_not_called() - refs = subprocess.run( - ["git", "ls-remote", "--tags", str(self.remote)], - text=True, - capture_output=True, - check=True, - ).stdout - self.assertNotIn("beta-authorization/", refs) - - @mock.patch("scripts.beta_authorization.build_evidence") - def test_changed_identity_is_rejected_and_first_validation_failure_does_not_publish(self, build: mock.Mock) -> None: - build.side_effect = CandidateError("missing conformance evidence") - with self.assertRaisesRegex(CandidateError, "missing conformance"): - self.record() - refs = subprocess.run( - ["git", "ls-remote", "--tags", str(self.remote)], - text=True, - capture_output=True, - check=True, - ).stdout - self.assertNotIn("beta-authorization/", refs) - - build.side_effect = None - build.return_value = self.recorded_evidence() - self.record() - changed = copy.deepcopy(self.request) - changed["authorization"]["components"]["server"]["commit"] = "9" * 40 - self.request_path.write_bytes(canonical_json(changed)) - with self.assertRaisesRegex(CandidateError, "immutable and differs"): - self.record() - - def test_occupied_conflicting_tag_fails_closed(self) -> None: - subprocess.run( - ["git", "push", "origin", "HEAD:refs/tags/beta-authorization/first-beta"], - cwd=self.repository, - check=True, - capture_output=True, - ) - with self.assertRaisesRegex(CandidateError, "missing beta-authorization.json"): - self.record() - - def test_new_authorization_rejects_missing_fresh_qualification_before_publication(self) -> None: - self.qualification_path.unlink() - with self.assertRaisesRegex(CandidateError, "requires fresh target qualification"): - self.record() - refs = subprocess.run( - ["git", "ls-remote", "--tags", str(self.remote)], - text=True, - capture_output=True, - check=True, - ).stdout - self.assertNotIn("beta-authorization/", refs) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_beta_candidate.py b/tests/test_beta_candidate.py deleted file mode 100644 index 9f23676..0000000 --- a/tests/test_beta_candidate.py +++ /dev/null @@ -1,1015 +0,0 @@ -from __future__ import annotations - -import base64 -import copy -import hashlib -import http.client -import io -import json -import shutil -import subprocess -import tarfile -import tempfile -import unittest -import urllib.error -import zipfile -from pathlib import Path -from unittest import mock - -from jsonschema import Draft202012Validator -from jsonschema.exceptions import ValidationError - -from scripts.beta_candidate import ( - CLI_ASSETS, - COMPONENTS, - LEGACY_VERIFICATION_SCHEMA, - SCHEMA, - CandidateError, - PublicClient, - PublicInfrastructureError, - canonical_cli_embedded_identity, - canonical_json, - canonical_pypi_version, - check_candidate_compatibility, - inspect_cli_phar_identity, - load_verification, - manifest_digest, - parse_checksums, - record_candidate, - revalidate_verification, - validate_manifest, - validate_recorded_verification, - validate_verification, - verify_composer, - verify_github_release, - verify_pypi, - verify_python_archive_identity, -) -from tests.verification_fixture import ( - candidate_verification, - legacy_beta_one_candidate_manifest, - legacy_candidate_manifest, - legacy_candidate_verification, - legacy_completed_candidate_manifests, -) - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] - - -def write_build_info_phar(path: Path, version: str, commit: str) -> None: - generated = f"""addFromString('src/GeneratedBuildInfo.php', base64_decode($argv[2], true));" - ) - subprocess.run( - [ - "php", - "-d", - "phar.readonly=0", - "-r", - script, - "--", - str(path), - base64.b64encode(generated).decode(), - ], - check=True, - text=True, - capture_output=True, - ) - - -def http_error(status: int, body: bytes = b"error", **headers: str) -> urllib.error.HTTPError: - return urllib.error.HTTPError( - "https://api.github.com/repos/durable-workflow/.github/releases", - status, - "request failed", - headers, - io.BytesIO(body), - ) - - -def manifest() -> dict[str, object]: - return { - "schema": SCHEMA, - "candidate": "beta-test-1", - "components": { - name: {"version": f"1.2.{index}", "commit": f"{index + 1:040x}"} for index, name in enumerate(COMPONENTS) - }, - } - - -def verification(candidate: dict[str, object]) -> dict[str, object]: - return candidate_verification(candidate, verified_at="2026-07-16T00:00:00Z") - - -class ManifestTest(unittest.TestCase): - def test_python_registry_normalizes_every_supported_prerelease_channel(self) -> None: - self.assertEqual("2.0.0a7", canonical_pypi_version("2.0.0-alpha.7")) - self.assertEqual("2.0.0b21", canonical_pypi_version("2.0.0-beta.21")) - self.assertEqual("2.0.0rc5", canonical_pypi_version("2.0.0-rc.5")) - self.assertEqual("2.0.0", canonical_pypi_version("2.0.0")) - - def test_python_release_lookup_uses_pep_440_rc_identity(self) -> None: - component = COMPONENTS["sdk-python"] - commit = "a" * 40 - - class FixtureClient: - requested_url = "" - - def json(self, url: str) -> dict[str, object]: - self.requested_url = url - return { - "info": { - "version": "2.0.0rc5", - "project_urls": {"Repository": "https://github.com/durable-workflow/sdk-python"}, - }, - "urls": [], - } - - client = FixtureClient() - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex( - CandidateError, - "wheel and source archive", - ), - ): - verify_pypi( - client, - component, - "2.0.0-rc.5", - commit, - Path(temporary), - ) - self.assertEqual( - "https://pypi.org/pypi/durable-workflow/2.0.0rc5/json", - client.requested_url, - ) - - def test_python_evidence_accepts_only_exact_semver_or_pep_440_registry_urls(self) -> None: - candidate = manifest() - candidate["components"]["sdk-python"]["version"] = "2.0.0-rc.5" - result = verification(candidate) - registry = result["components"]["sdk-python"]["distribution"] - semver_url = "https://pypi.org/pypi/durable-workflow/2.0.0-rc.5/json" - pep_440_url = "https://pypi.org/pypi/durable-workflow/2.0.0rc5/json" - - self.assertEqual(semver_url, registry["registry"]) - validate_verification(result, candidate) - - registry["registry"] = pep_440_url - validate_verification(result, candidate) - - registry["registry"] = "https://pypi.org/pypi/durable-workflow/2.0.0rc05/json" - with self.assertRaisesRegex(CandidateError, "planned PyPI package identity"): - validate_verification(result, candidate) - - def test_waterline_alpha_139_expands_minified_metadata_and_checks_effective_provenance(self) -> None: - component = COMPONENTS["waterline"] - commit = "a" * 40 - dist_url = "https://api.github.com/repos/durable-workflow/waterline/zipball/commit" - payload = { - "minified": "composer/2.0", - "packages": { - component.package: [ - { - "name": component.package, - "version": "2.0.0-alpha.140", - "source": { - "type": "git", - "url": "https://github.com/durable-workflow/waterline", - "reference": commit, - }, - "dist": {"type": "zip", "url": dist_url, "reference": commit}, - }, - {"version": "2.0.0-alpha.139"}, - ] - }, - } - client = mock.Mock() - client.json.return_value = payload - client.download.return_value = {"url": dist_url, "size": 1, "sha256": "b" * 64} - - with tempfile.TemporaryDirectory() as temporary: - result = verify_composer(client, component, "2.0.0-alpha.139", commit, Path(temporary)) - - self.assertEqual(commit, result["source_reference"]) - self.assertEqual(commit, result["dist_reference"]) - client.download.assert_called_once() - - payload["packages"][component.package][1]["source"] = { - "type": "git", - "url": "https://github.com/durable-workflow/waterline", - "reference": "c" * 40, - } - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(CandidateError, "source identity.*does not match"), - ): - verify_composer(client, component, "2.0.0-alpha.139", commit, Path(temporary)) - - payload["packages"][component.package][1]["source"] = "__unset" - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(CandidateError, "source identity.*does not match"), - ): - verify_composer(client, component, "2.0.0-alpha.139", commit, Path(temporary)) - - del payload["packages"][component.package][1]["source"] - payload["packages"][component.package][0]["source"]["reference"] = "c" * 40 - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(CandidateError, "source identity.*does not match"), - ): - verify_composer(client, component, "2.0.0-alpha.139", commit, Path(temporary)) - - def test_composer_verification_does_not_inherit_unmarked_or_unsupported_metadata(self) -> None: - component = COMPONENTS["workflow"] - commit = "a" * 40 - first = { - "version": "2.0.0-alpha.2", - "source": {"reference": commit}, - "dist": {"url": "https://example.test/package.zip", "reference": commit}, - } - client = mock.Mock() - - cases = ( - (None, "source identity.*does not match"), - ("composer/3.0", "unsupported minified format"), - ) - for marker, error in cases: - with self.subTest(marker=marker): - payload = {"packages": {component.package: [first, {"version": "2.0.0-alpha.1"}]}} - if marker is not None: - payload["minified"] = marker - client.json.return_value = payload - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(CandidateError, error), - ): - verify_composer(client, component, "2.0.0-alpha.1", commit, Path(temporary)) - - def test_composer_verification_rejects_invalid_compact_identity_and_order(self) -> None: - component = COMPONENTS["waterline"] - commit = "a" * 40 - dist_url = "https://example.test/package.zip" - first = { - "version": "2.0.0-alpha.139", - "source": {"reference": commit}, - "dist": {"url": dist_url, "reference": commit}, - } - client = mock.Mock() - - cases = ( - ([first, {"version": "2.0.0-alpha.140"}], "strictly descending"), - ([first, {"version": "2.0.0-alpha.138"}, {"source": {"reference": commit}}], "declare a version"), - ) - for versions, error in cases: - with self.subTest(error=error): - client.json.return_value = { - "minified": "composer/2.0", - "packages": {component.package: versions}, - } - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(CandidateError, error), - ): - verify_composer(client, component, "2.0.0-alpha.139", commit, Path(temporary)) - - def test_composer_verification_rejects_ambiguous_exact_version_before_provenance(self) -> None: - component = COMPONENTS["workflow"] - commit = "a" * 40 - client = mock.Mock() - client.json.return_value = { - "packages": { - component.package: [ - { - "version": "2.0.0-alpha.1", - "source": {"reference": commit}, - "dist": {"url": "https://example.test/package.zip", "reference": commit}, - }, - { - "version": "v2.0.0-alpha.1", - "source": {"reference": "b" * 40}, - "dist": {"url": "https://example.test/drifted.zip", "reference": "b" * 40}, - }, - ] - } - } - - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(CandidateError, "multiple records"), - ): - verify_composer(client, component, "2.0.0-alpha.1", commit, Path(temporary)) - - client.download.assert_not_called() - - def test_verification_schema_and_runtime_reject_unknown_or_missing_nested_evidence(self) -> None: - candidate = manifest() - result = verification(candidate) - schema = json.loads((REPOSITORY_ROOT / "candidates" / "verification-schema.json").read_bytes()) - Draft202012Validator.check_schema(schema) - Draft202012Validator(schema).validate(result) - validate_verification(result, candidate) - - for path in ("top-level", "nested"): - with self.subTest(path=path): - tampered = copy.deepcopy(result) - if path == "top-level": - tampered["injected"] = "same-user-process" - else: - tampered["components"]["workflow"]["distribution"]["injected"] = True - with self.assertRaisesRegex(CandidateError, "keys must be exactly"): - validate_verification(tampered, candidate) - - missing = copy.deepcopy(result) - del missing["components"]["server"]["distribution"]["configs"] - with self.assertRaisesRegex(CandidateError, "keys must be exactly"): - validate_verification(missing, candidate) - - def test_waterline_verification_requires_both_matching_distributions(self) -> None: - candidate = manifest() - result = verification(candidate) - schema = json.loads((REPOSITORY_ROOT / "candidates" / "verification-schema.json").read_bytes()) - - missing = copy.deepcopy(result) - del missing["components"]["waterline"]["distributions"]["service"] - with self.assertRaises(ValidationError): - Draft202012Validator(schema).validate(missing) - with self.assertRaisesRegex(CandidateError, "keys must be exactly"): - validate_verification(missing, candidate) - - mismatches = { - "version": lambda value: value["components"]["waterline"]["distributions"]["service"].update( - {"image": "docker.io/durableworkflow/waterline:9.9.9"} - ), - "source": lambda value: value["components"]["waterline"]["distributions"]["embedded"].update( - {"source_reference": "f" * 40} - ), - "labels": lambda value: value["components"]["waterline"]["distributions"]["service"]["configs"][0][ - "labels" - ].update({"org.opencontainers.image.revision": "f" * 40}), - } - for mismatch, mutate in mismatches.items(): - with self.subTest(mismatch=mismatch): - tampered = copy.deepcopy(result) - mutate(tampered) - with self.assertRaises(CandidateError): - validate_verification(tampered, candidate) - - partial = copy.deepcopy(result) - partial["components"]["waterline"]["distributions"]["service"]["configs"].pop() - with self.assertRaises(ValidationError): - Draft202012Validator(schema).validate(partial) - with self.assertRaises(CandidateError): - validate_verification(partial, candidate) - - def test_historical_pre_service_verification_remains_readable_but_cannot_verify_a_new_candidate(self) -> None: - historical_candidate = legacy_candidate_manifest() - historical = legacy_candidate_verification(historical_candidate) - - self.assertEqual(LEGACY_VERIFICATION_SCHEMA, historical["schema"]) - validate_recorded_verification(historical, historical_candidate) - - candidate = manifest() - with self.assertRaisesRegex(CandidateError, "does not prove this exact candidate manifest"): - validate_recorded_verification(legacy_candidate_verification(candidate), candidate) - with self.assertRaisesRegex(CandidateError, "does not prove this exact candidate manifest"): - validate_verification(legacy_candidate_verification(candidate), candidate) - - unrecorded = copy.deepcopy(historical_candidate) - unrecorded["candidate"] = "unrecorded-pre-service-candidate" - with self.assertRaisesRegex(CandidateError, "not an exact recorded historical contract"): - validate_recorded_verification(legacy_candidate_verification(unrecorded), unrecorded) - - missing = copy.deepcopy(historical) - del missing["components"]["waterline"]["distribution"] - with self.assertRaisesRegex(CandidateError, "not successful"): - validate_recorded_verification(missing, historical_candidate) - - def test_exact_tagged_and_completed_pre_service_candidates_remain_readable(self) -> None: - candidates = [ - ( - "5e083e07e6abecbb0547466812f866a3650039b210dbe1d486ca98528479cd29", - legacy_beta_one_candidate_manifest(), - ), - *zip( - ( - "dd5e8d3bb248c2b1b292b5badf29376cc5c5b6fa73dedfb343e33987a2b6d7a2", - "43243594ba34ff220365d9c514e6a54b93789788676ca8b1d678b679afa6c1c5", - "2fbeda4e3368edf7cda7bcc749359d4bcdf7fcccca289e32316782613a84b4a6", - ), - legacy_completed_candidate_manifests(), - strict=True, - ), - ] - - for expected_digest, candidate in candidates: - with self.subTest(candidate=candidate["candidate"]): - self.assertEqual(expected_digest, manifest_digest(candidate)) - validate_recorded_verification(legacy_candidate_verification(candidate), candidate) - - unrecorded = copy.deepcopy(candidate) - unrecorded["candidate"] += "-replacement" - with self.assertRaisesRegex(CandidateError, "not an exact recorded historical contract"): - validate_recorded_verification(legacy_candidate_verification(unrecorded), unrecorded) - - def test_fresh_writer_independently_rejects_shape_valid_fabricated_distribution_evidence(self) -> None: - candidate = manifest() - trusted = verification(candidate) - tampered = copy.deepcopy(trusted) - tampered["components"]["workflow"]["distribution"]["dist"]["sha256"] = "f" * 64 - validate_verification(tampered, candidate) - - sources = {COMPONENTS[name].repository: result["source"] for name, result in trusted["components"].items()} - - def composer_verifier( - _client: object, component: object, _version: str, _commit: str, _directory: Path - ) -> dict[str, object]: - name = next(name for name, expected in COMPONENTS.items() if expected == component) - return trusted["components"][name]["distribution"] - - with ( - mock.patch( - "scripts.beta_candidate.resolve_github_tag", side_effect=lambda _client, repo, _version: sources[repo] - ), - mock.patch.dict("scripts.beta_candidate.VERIFIERS", {"composer": composer_verifier}), - self.assertRaisesRegex(CandidateError, "differs from the isolated verification handoff"), - ): - revalidate_verification(tampered, candidate, mock.Mock()) - - def test_fresh_writer_rejects_a_fabricated_cli_identity_suffix(self) -> None: - candidate = manifest() - tampered = verification(candidate) - identity = tampered["components"]["cli"]["distribution"]["package_source"] - identity["embedded_phar_identity"] = f"{identity['embedded_phar_identity']} verifier-controlled" - - with self.assertRaisesRegex(CandidateError, "package_source does not bind the planned CLI source"): - revalidate_verification(tampered, candidate, mock.Mock()) - - def test_non_executing_phar_inspection_binds_the_full_embedded_source_commit(self) -> None: - version = "0.1.94" - commit = "3" * 40 - with tempfile.TemporaryDirectory() as temporary: - phar = Path(temporary) / "dw.phar" - write_build_info_phar(phar, version, commit) - - self.assertEqual( - canonical_cli_embedded_identity(version, commit), - inspect_cli_phar_identity(phar, version, commit), - ) - with self.assertRaisesRegex(CandidateError, "does not embed planned source commit"): - inspect_cli_phar_identity(phar, version, "f" * 40) - - def test_verification_loader_rejects_oversized_handoff_before_parsing(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "verification.json" - path.write_bytes(b" " * (256 * 1024 + 1)) - with self.assertRaisesRegex(CandidateError, "exceeds the 256 KiB limit"): - load_verification(path, manifest()) - - def test_public_client_preserves_explicit_github_api_version(self) -> None: - client = PublicClient("fixture-token") - with mock.patch("scripts.beta_candidate.urllib.request.urlopen", return_value=object()) as open_url: - client.request( - "https://api.github.com/repos/durable-workflow/.github/actions/runs/1/approvals", - headers={"X-GitHub-Api-Version": "2026-03-10"}, - ) - request = open_url.call_args.args[0] - self.assertEqual("2026-03-10", request.get_header("X-github-api-version")) - self.assertEqual("Bearer fixture-token", request.get_header("Authorization")) - - def test_public_client_retries_github_service_and_body_read_interruptions(self) -> None: - class InterruptedResponse(io.BytesIO): - def read(self, _size: int = -1) -> bytes: - raise http.client.IncompleteRead(b"partial") - - sleeps: list[float] = [] - client = PublicClient(max_attempts=4, retry_base_seconds=1, sleep=sleeps.append) - responses = [ - http_error(503, b"service unavailable", **{"Retry-After": "3"}), - InterruptedResponse(), - io.BytesIO(b'{"tag_name":"release-plan/current"}'), - ] - - with mock.patch("scripts.beta_candidate.urllib.request.urlopen", side_effect=responses) as open_url: - result = client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - - self.assertEqual({"tag_name": "release-plan/current"}, result) - self.assertEqual([3, 2], sleeps) - self.assertEqual(3, open_url.call_count) - - def test_public_client_honors_explicit_rate_limit_guidance(self) -> None: - sleeps: list[float] = [] - client = PublicClient( - max_attempts=3, - retry_base_seconds=1, - sleep=sleeps.append, - now=lambda: 100, - ) - responses = [ - http_error( - 403, - b"API rate limit exceeded", - **{"X-RateLimit-Remaining": "0", "X-RateLimit-Reset": "112"}, - ), - http_error(429, **{"Retry-After": "20"}), - io.BytesIO(b"[]"), - ] - - with mock.patch("scripts.beta_candidate.urllib.request.urlopen", side_effect=responses): - self.assertEqual( - [], - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100"), - ) - - self.assertEqual([12, 20], sleeps) - - def test_public_client_never_retries_authentication_with_rate_limit_guidance(self) -> None: - sleeps: list[float] = [] - client = PublicClient(max_attempts=3, retry_base_seconds=1, sleep=sleeps.append) - error = http_error( - 401, - b"Bad credentials: API rate limit exceeded", - **{"Retry-After": "20", "X-RateLimit-Remaining": "0"}, - ) - - with ( - mock.patch("scripts.beta_candidate.urllib.request.urlopen", side_effect=error) as open_url, - self.assertRaisesRegex(CandidateError, r"public request failed \(401\)"), - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - - self.assertEqual([], sleeps) - self.assertEqual(1, open_url.call_count) - - def test_public_client_reports_bounded_transient_infrastructure_without_url_or_token(self) -> None: - client = PublicClient( - "fixture-secret", - max_attempts=3, - retry_base_seconds=1, - sleep=lambda _delay: None, - ) - - with ( - mock.patch( - "scripts.beta_candidate.urllib.request.urlopen", - side_effect=[http_error(503), http_error(502), http_error(503)], - ) as open_url, - self.assertRaisesRegex( - PublicInfrastructureError, - r"classification=github-read-transient, endpoint_class=releases-api, " - r"attempts=3, reason=retry-exhausted, status=503", - ) as raised, - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - - self.assertEqual(3, open_url.call_count) - self.assertNotIn("fixture-secret", str(raised.exception)) - self.assertNotIn("api.github.com", str(raised.exception)) - - def test_public_client_stops_before_retry_guidance_exceeds_the_workflow_budget(self) -> None: - clock = [0.0] - sleeps: list[float] = [] - - def sleep(delay: float) -> None: - sleeps.append(delay) - clock[0] += delay - - client = PublicClient( - max_attempts=5, - retry_base_seconds=2, - deadline_seconds=5, - sleep=sleep, - monotonic=lambda: clock[0], - ) - responses = [ - http_error(503), - http_error(503, **{"Retry-After": "10"}), - ] - - with ( - mock.patch("scripts.beta_candidate.urllib.request.urlopen", side_effect=responses) as open_url, - self.assertRaisesRegex( - PublicInfrastructureError, - r"endpoint_class=releases-api, attempts=2, reason=workflow-deadline, status=503", - ), - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - - self.assertEqual([2], sleeps) - self.assertEqual(2, open_url.call_count) - - def test_public_client_does_not_retry_deterministic_http_or_data_failures(self) -> None: - client = PublicClient(max_attempts=3, sleep=lambda _delay: self.fail("deterministic failure was retried")) - for status in (401, 403, 404): - with self.subTest(status=status): - with ( - mock.patch( - "scripts.beta_candidate.urllib.request.urlopen", - side_effect=http_error(status, b"Resource not accessible"), - ) as open_url, - self.assertRaisesRegex(CandidateError, rf"public request failed \({status}\)"), - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - self.assertEqual(1, open_url.call_count) - - with ( - mock.patch( - "scripts.beta_candidate.urllib.request.urlopen", - return_value=io.BytesIO(b"not JSON"), - ) as open_url, - self.assertRaisesRegex(CandidateError, "did not return valid JSON"), - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - self.assertEqual(1, open_url.call_count) - - def test_public_client_does_not_retry_non_github_service_failures(self) -> None: - client = PublicClient(max_attempts=3, sleep=lambda _delay: self.fail("registry failure was retried")) - error = urllib.error.HTTPError( - "https://pypi.org/pypi/durable-workflow/json", - 503, - "request failed", - {}, - io.BytesIO(b"service unavailable"), - ) - - with ( - mock.patch("scripts.beta_candidate.urllib.request.urlopen", side_effect=error) as open_url, - self.assertRaisesRegex(CandidateError, r"public request failed \(503\)"), - ): - client.json("https://pypi.org/pypi/durable-workflow/json") - - self.assertEqual(1, open_url.call_count) - - def test_manifest_is_canonical_and_stable(self) -> None: - candidate = manifest() - validate_manifest(candidate) - self.assertEqual(hashlib.sha256(canonical_json(candidate)).hexdigest(), manifest_digest(candidate)) - self.assertEqual("docker.io/durableworkflow/server", COMPONENTS["server"].package) - - def test_manifest_rejects_missing_component(self) -> None: - candidate = manifest() - del candidate["components"]["sdk-rust"] - with self.assertRaisesRegex(CandidateError, "components must be exactly"): - validate_manifest(candidate) - - def test_manifest_rejects_unowned_fields(self) -> None: - candidate = manifest() - candidate["components"]["server"]["token"] = "do-not-store" - with self.assertRaisesRegex(CandidateError, "only version and commit"): - validate_manifest(candidate) - - def test_manifest_rejects_abbreviated_commit(self) -> None: - candidate = manifest() - candidate["components"]["workflow"]["commit"] = "abc123" - with self.assertRaisesRegex(CandidateError, "full lowercase Git commit"): - validate_manifest(candidate) - - def test_checksum_parser_accepts_common_sha256_formats(self) -> None: - digest_a = "a" * 64 - digest_b = "b" * 64 - parsed = parse_checksums(f"{digest_a} first\n{digest_b} *second.exe\n".encode()) - self.assertEqual({"first": digest_a, "second.exe": digest_b}, parsed) - - def test_cli_release_rejects_same_repository_attestation_from_wrong_commit(self) -> None: - attested_commit = "a" * 40 - declared_commit = "b" * 40 - version = "1.2.3" - - class FixtureClient: - contents = {name: f"fixture {name}\n".encode() for name in CLI_ASSETS - {"SHA256SUMS"}} - checksums = "".join( - f"{hashlib.sha256(content).hexdigest()} {name}\n" for name, content in sorted(contents.items()) - ).encode() - - def json(self, _url: str) -> dict[str, object]: - return { - "id": 123, - "tag_name": version, - "draft": False, - "html_url": f"https://github.com/durable-workflow/cli/releases/tag/{version}", - "assets": [ - { - "id": index, - "name": name, - "browser_download_url": f"https://example.invalid/{name}", - } - for index, name in enumerate(sorted(CLI_ASSETS), start=1) - ], - } - - def bytes(self, _url: str) -> bytes: - return self.checksums - - def download(self, url: str, path: Path, *, expected_sha256: str) -> dict[str, object]: - content = self.contents[url.rsplit("/", 1)[-1]] - if expected_sha256 != hashlib.sha256(content).hexdigest(): - raise AssertionError("fixture download checksum mismatch") - path.write_bytes(content) - return {"url": url, "size": len(content), "sha256": expected_sha256} - - def verify_attestation(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]: - self.assertEqual("durable-workflow/cli", command[command.index("--repo") + 1]) - if "--source-digest" not in command: - return subprocess.CompletedProcess(command, 1, stdout="", stderr="workflow authority does not match") - source_digest = command[command.index("--source-digest") + 1] - return subprocess.CompletedProcess( - command, - 0 if source_digest == attested_commit else 1, - stdout="", - stderr="source digest does not match the attested build", - ) - - with ( - tempfile.TemporaryDirectory() as temporary, - mock.patch("scripts.beta_candidate.shutil.which", return_value="/usr/bin/gh"), - mock.patch("scripts.beta_candidate.subprocess.run", side_effect=verify_attestation), - self.assertRaisesRegex(CandidateError, "build attestation failed"), - ): - verify_github_release(FixtureClient(), COMPONENTS["cli"], version, declared_commit, Path(temporary)) - - def test_python_registry_archives_match_the_declared_source(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - directory = Path(temporary) - source = directory / "source-fixture.tar.gz" - sdist = directory / "package.tar.gz" - wheel = directory / "package.whl" - source_files = { - "pyproject.toml": b"[project]\nname='durable-workflow'\n", - "src/durable_workflow/__init__.py": b"VERSION = '1.0.0'\n", - } - self.write_tar(source, "source-commit", source_files) - self.write_tar( - sdist, - "durable_workflow-1.0.0", - { - **source_files, - "PKG-INFO": b"generated metadata", - "setup.cfg": b"[egg_info]\ntag_build =\n", - "src/durable_workflow.egg-info/PKG-INFO": b"generated metadata", - }, - ) - with zipfile.ZipFile(wheel, "w") as archive: - archive.writestr("durable_workflow/__init__.py", source_files["src/durable_workflow/__init__.py"]) - archive.writestr("durable_workflow-1.0.0.dist-info/METADATA", b"generated metadata") - - class FixtureClient: - def download(self, _url: str, path: Path) -> dict[str, object]: - shutil.copyfile(source, path) - return {"url": "fixture", "size": path.stat().st_size, "sha256": "a" * 64} - - component = COMPONENTS["sdk-python"] - result = verify_python_archive_identity(FixtureClient(), component, "1" * 40, sdist, [wheel], directory) - self.assertEqual(2, result["source_files_compared"]) - self.assertEqual(1, result["wheel_files_compared"]) - - @staticmethod - def write_tar(path: Path, root: str, files: dict[str, bytes]) -> None: - with tarfile.open(path, "w:gz") as archive: - for name, content in files.items(): - member = tarfile.TarInfo(f"{root}/{name}") - member.size = len(content) - archive.addfile(member, io.BytesIO(content)) - - -class RecordTest(unittest.TestCase): - def setUp(self) -> None: - self.temporary = tempfile.TemporaryDirectory() - root = Path(self.temporary.name) - self.root = root - self.repository = root / "work" - self.remote = root / "remote.git" - subprocess.run(["git", "init", "--bare", str(self.remote)], check=True, capture_output=True) - subprocess.run(["git", "init", str(self.repository)], check=True, capture_output=True) - self.manifest_path = root / "candidate.json" - self.verification_path = root / "verification.json" - self.authoritative_path = root / "authoritative.json" - self.revalidation_patcher = mock.patch("scripts.beta_candidate.revalidate_verification") - self.revalidate = self.revalidation_patcher.start() - self.revalidate.side_effect = lambda verification, _manifest, _client: verification - - def tearDown(self) -> None: - self.revalidation_patcher.stop() - self.temporary.cleanup() - - def write_request(self, candidate: dict[str, object]) -> None: - self.manifest_path.write_bytes(canonical_json(candidate)) - self.verification_path.write_bytes(canonical_json(verification(candidate))) - - def test_first_record_and_idempotent_recovery_keep_one_commit(self) -> None: - candidate = manifest() - self.write_request(candidate) - created = record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - repeated = record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - self.assertEqual("created", created["status"]) - self.assertEqual("existing", repeated["status"]) - self.assertEqual(created["commit"], repeated["commit"]) - self.assertEqual(1, self.revalidate.call_count) - record_files = subprocess.run( - ["git", "--git-dir", str(self.remote), "ls-tree", "-r", "--name-only", created["commit"]], - check=True, - text=True, - capture_output=True, - ).stdout.splitlines() - self.assertEqual(["candidate.json", "verification.json"], record_files) - self.assertEqual(canonical_json(verification(candidate)), self.authoritative_path.read_bytes()) - compatibility = check_candidate_compatibility(self.repository, self.manifest_path, remote=str(self.remote)) - self.assertEqual("existing", compatibility["status"]) - self.assertEqual(created["commit"], compatibility["commit"]) - - def test_existing_candidate_rejects_tuple_mutation(self) -> None: - candidate = manifest() - self.write_request(candidate) - record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - changed = copy.deepcopy(candidate) - changed["components"]["cli"]["version"] = "1.2.99" - changed["components"]["cli"]["commit"] = "f" * 40 - self.write_request(changed) - with self.assertRaisesRegex(CandidateError, "immutable"): - check_candidate_compatibility(self.repository, self.manifest_path, remote=str(self.remote)) - with self.assertRaisesRegex(CandidateError, "immutable"): - record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - - def test_record_rejects_incomplete_verification(self) -> None: - candidate = manifest() - result = verification(candidate) - del result["components"]["server"] - self.manifest_path.write_bytes(canonical_json(candidate)) - self.verification_path.write_bytes(canonical_json(result)) - with self.assertRaisesRegex(CandidateError, "every candidate component"): - record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - - def test_same_user_downloaded_process_tampering_fails_before_first_git_write(self) -> None: - candidate = manifest() - self.write_request(candidate) - result = json.loads(self.verification_path.read_bytes()) - result["components"]["workflow"]["distribution"]["dist"]["sha256"] = "f" * 64 - self.verification_path.write_bytes(canonical_json(result)) - self.revalidate.side_effect = CandidateError("independent public evidence differs") - - with self.assertRaisesRegex(CandidateError, "independent public evidence differs"): - record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - - refs = subprocess.run( - ["git", "--git-dir", str(self.remote), "show-ref"], - check=False, - text=True, - capture_output=True, - ) - self.assertEqual("", refs.stdout) - - def test_cli_identity_suffix_tampering_fails_before_first_git_write(self) -> None: - candidate = manifest() - self.write_request(candidate) - result = json.loads(self.verification_path.read_bytes()) - cli = result["components"]["cli"] - cli["distribution"]["package_source"]["embedded_phar_identity"] = ( - f"{canonical_cli_embedded_identity(cli['version'], cli['commit'])} fabricated-suffix" - ) - self.verification_path.write_bytes(canonical_json(result)) - - with self.assertRaisesRegex(CandidateError, "package_source does not bind the planned CLI source"): - record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - - self.revalidate.assert_not_called() - refs = subprocess.run( - ["git", "--git-dir", str(self.remote), "show-ref"], - check=False, - text=True, - capture_output=True, - ) - self.assertEqual("", refs.stdout) - - def test_canonical_cli_handoff_for_other_source_bytes_fails_before_first_git_write(self) -> None: - candidate = manifest() - self.write_request(candidate) - submitted = verification(candidate) - cli_identity = candidate["components"]["cli"] - wrong_phar = self.root / "wrong-source.phar" - write_build_info_phar(wrong_phar, cli_identity["version"], "f" * 40) - sources = {COMPONENTS[name].repository: result["source"] for name, result in submitted["components"].items()} - - def distribution_verifier( - _client: object, - component: object, - _version: str, - _commit: str, - _directory: Path, - ) -> dict[str, object]: - name = next(name for name, expected in COMPONENTS.items() if expected == component) - return submitted["components"][name]["distribution"] - - def cli_verifier( - _client: object, - _component: object, - version: str, - commit: str, - directory: Path, - ) -> dict[str, object]: - phar = directory / "dw.phar" - shutil.copyfile(wrong_phar, phar) - inspect_cli_phar_identity(phar, version, commit) - self.fail("other-source PHAR bytes unexpectedly matched the planned source") - - self.revalidate.side_effect = lambda handoff, selected, client: revalidate_verification( - handoff, selected, client - ) - verifier_replacements = {component.distribution: distribution_verifier for component in COMPONENTS.values()} - with ( - mock.patch( - "scripts.beta_candidate.resolve_github_tag", - side_effect=lambda _client, repository, _version: sources[repository], - ), - mock.patch.dict("scripts.beta_candidate.VERIFIERS", verifier_replacements), - mock.patch( - "scripts.beta_candidate.verify_composer", - return_value=submitted["components"]["waterline"]["distributions"]["embedded"], - ), - mock.patch( - "scripts.beta_candidate.verify_oci", - return_value=submitted["components"]["waterline"]["distributions"]["service"], - ), - mock.patch("scripts.beta_candidate.verify_github_release", side_effect=cli_verifier), - self.assertRaisesRegex(CandidateError, "does not embed planned source commit"), - ): - record_candidate( - self.repository, - self.manifest_path, - self.verification_path, - remote=str(self.remote), - authoritative_verification=self.authoritative_path, - client=mock.Mock(), - ) - - refs = subprocess.run( - ["git", "--git-dir", str(self.remote), "show-ref"], - check=False, - text=True, - capture_output=True, - ) - self.assertEqual("", refs.stdout) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_beta_conformance.py b/tests/test_beta_conformance.py deleted file mode 100644 index 401ee47..0000000 --- a/tests/test_beta_conformance.py +++ /dev/null @@ -1,3385 +0,0 @@ -from __future__ import annotations - -import contextlib -import json -import re -import subprocess -import sys -import tempfile -import unittest -from collections.abc import Callable -from pathlib import Path -from typing import Any -from unittest import mock - -import yaml -from jsonschema import Draft202012Validator -from jsonschema.exceptions import ValidationError -from referencing import Registry, Resource - -from scripts.beta_candidate import COMPONENTS, SCHEMA, canonical_json -from scripts.beta_conformance import ( - DISTRIBUTIONS, - EXPERIMENTS, - MAX_INFRASTRUCTURE_ATTEMPTS, - NATIVE_FAILURE_COMPONENT_LIMIT, - NATIVE_FAILURE_PROJECTION_LIMIT, - NATIVE_RESULT_LIMIT, - NATIVE_RESULT_PREFIX_LIMIT, - RUNTIME_DEPENDENCY_SELECTORS, - SYNTHETIC_CREDENTIAL_CANARY, - ConformanceError, - aggregate_results, - artifact_binding_failures, - bounded_text, - classify_attempt, - distribution_version, - experiment_result, - fetch_retention_source_metadata, - inject_distribution_identity_mismatch, - injected_failure_result, - load_contract, - native_failure_projection_error, - native_result_completeness_error, - prepare_plan, - resolve_runtime_dependencies, - restore_plan, - run_experiment, - runner_command, - runner_required_artifact_versions, - runner_runtime_environment, - sha256_bytes, - sha256_file, - summarize_native_result, - validate_contract, - validate_experiment_result, - validate_plan, - validate_retention_ref, - validate_retention_source, - write_json, -) -from tests.verification_fixture import candidate_verification as complete_candidate_verification - -ROOT = Path(__file__).resolve().parents[1] -CONTRACT_PATH = ROOT / "beta-conformance" / "contract.json" -POLYGLOT_PHP_IDENTITY_FIXTURE = ( - ROOT / "tests" / "fixtures" / "beta-conformance" / "activities-cross-language-php-sdk.json" -) - - -def beta_schema_validator(name: str) -> Draft202012Validator: - schemas = { - path.name: json.loads(path.read_bytes()) for path in sorted((ROOT / "beta-conformance").glob("*schema.json")) - } - registry = Registry().with_resources((schema["$id"], Resource.from_contents(schema)) for schema in schemas.values()) - return Draft202012Validator(schemas[name], registry=registry) - - -def candidate_manifest() -> dict[str, object]: - return { - "schema": SCHEMA, - "candidate": "portable-beta-test", - "components": { - name: {"version": f"1.2.{index}", "commit": f"{index + 1:040x}"} for index, name in enumerate(COMPONENTS) - }, - } - - -def candidate_verification(candidate: dict[str, object]) -> dict[str, object]: - return complete_candidate_verification(candidate, verified_at="2026-07-17T00:00:00Z") - - -class RunnerCommandTest(unittest.TestCase): - def test_python_runner_uses_the_current_python_interpreter(self) -> None: - self.assertEqual( - [sys.executable, "scripts/runner.py", "--result-dir", "result"], - runner_command(Path("scripts/runner.py"), Path("result")), - ) - - -def runtime_dependencies() -> dict[str, dict[str, str]]: - dependencies = {} - for index, (name, selector) in enumerate(RUNTIME_DEPENDENCY_SELECTORS.items(), start=1): - digest = f"sha256:{index:064x}" - dependencies[name] = { - "selector": selector, - "image": f"{selector.rsplit(':', 1)[0]}@{digest}", - "manifest_digest": digest, - } - return dependencies - - -def successful_diagnostic( - plan: dict[str, object], - required_distributions: list[str] | None = None, - *, - required_artifact_versions: list[str] | None = None, - runner_id: str = "fixture", - schema: str = "fixture.result/v1", - scenario_ids: list[str] | None = None, -) -> dict[str, object]: - selected = required_distributions or list(DISTRIBUTIONS) - selected_versions = required_artifact_versions or list(dict.fromkeys(DISTRIBUTIONS[name][0] for name in selected)) - selected_scenarios = scenario_ids or ["fixture"] - artifact_tuple = plan["artifact_tuple"] - distribution_identities = plan["distribution_identities"] - assert isinstance(artifact_tuple, dict) - assert isinstance(distribution_identities, dict) - empty_digest = sha256_bytes(b"") - return { - "runner": runner_id, - "attempt": 1, - "exit_code": 0, - "timed_out": False, - "native_outcome": "pass", - "runner_blocked": False, - "stdout_tail": "", - "stdout_sha256": empty_digest, - "stderr_tail": "", - "stderr_sha256": empty_digest, - "native_result_size_bytes": 128, - "native_result_sha256": "b" * 64, - "native_result_prefix_sha256": None, - "native_result_prefix_bytes": None, - "native_summary": { - "schema": schema, - "artifact_versions": {name: distribution_version(artifact_tuple, name) for name in selected_versions}, - "executed_distribution_identities": { - name: json.loads(canonical_json(distribution_identities[name])) for name in selected - }, - "scenario_statuses": [{"id": scenario_id, "status": "pass"} for scenario_id in selected_scenarios], - "failure_projection": { - "max_bytes": NATIVE_FAILURE_PROJECTION_LIMIT, - "component_max_bytes": NATIVE_FAILURE_COMPONENT_LIMIT, - "truncated": False, - "scenarios": [], - }, - "local_product_source_checkout_used": False, - }, - "findings": [], - } - - -def successful_runner_diagnostics(plan: dict[str, object], specification: dict[str, Any]) -> list[dict[str, object]]: - return [ - successful_diagnostic( - plan, - runner["required_distributions"], - required_artifact_versions=runner_required_artifact_versions(runner), - runner_id=runner["id"], - schema=runner.get("result_schema", "fixture.result/v1"), - scenario_ids=runner.get("required_scenarios", ["fixture"]), - ) - for runner in specification["runners"] - ] - - -def successful_native_result( - plan: dict[str, object], - required_distributions: list[str], - *, - required_artifact_versions: list[str] | None = None, -) -> dict[str, object]: - artifact_tuple = plan["artifact_tuple"] - distribution_identities = plan["distribution_identities"] - assert isinstance(artifact_tuple, dict) - assert isinstance(distribution_identities, dict) - selected_versions = required_artifact_versions or list( - dict.fromkeys(DISTRIBUTIONS[name][0] for name in required_distributions) - ) - return { - "schema": "fixture.result/v1", - "outcome": "pass", - "artifact_versions": {name: distribution_version(artifact_tuple, name) for name in selected_versions}, - "executed_distribution_identities": { - name: json.loads(canonical_json(distribution_identities[name])) for name in required_distributions - }, - "local_product_source_checkout_used": False, - "scenario_results": {"fixture": "pass"}, - "findings": [], - } - - -class RetentionSourceTest(unittest.TestCase): - @staticmethod - def workflow() -> dict[str, object]: - return { - "id": 314998157, - "name": "Beta conformance", - "path": ".github/workflows/beta-conformance.yml", - "state": "active", - } - - @staticmethod - def completed_run() -> dict[str, object]: - return { - "conclusion": "failure", - "display_title": "Conformance alpha-workspace-unavailable-recovery-f46818553161", - "event": "workflow_dispatch", - "head_branch": "main", - "head_repository": {"full_name": "durable-workflow/.github"}, - "head_sha": "1fd9296396bb8fc57f50362323e40ab9008bbc9f", - "id": 29775218461, - "name": "Conformance alpha-workspace-unavailable-recovery-f46818553161", - "path": ".github/workflows/beta-conformance.yml", - "repository": {"full_name": "durable-workflow/.github"}, - "run_attempt": 1, - "status": "completed", - "updated_at": "2026-07-20T20:20:06Z", - "workflow_id": 314998157, - } - - def test_completed_default_branch_run_is_bound_for_retention(self) -> None: - source = validate_retention_source( - self.completed_run(), - self.workflow(), - expected_run_id=29775218461, - expected_run_attempt=1, - ) - - self.assertEqual( - { - "source_candidate": "alpha-workspace-unavailable-recovery-f46818553161", - "source_completed_at": "2026-07-20T20:20:06Z", - "source_head_sha": "1fd9296396bb8fc57f50362323e40ab9008bbc9f", - "source_run_attempt": 1, - "source_run_id": 29775218461, - }, - source, - ) - - def test_retention_entrypoint_fetches_the_exact_attempt_and_workflow(self) -> None: - responses = [] - for document in (self.completed_run(), self.workflow()): - response = mock.MagicMock() - response.__enter__.return_value = response - response.read.return_value = json.dumps(document).encode() - responses.append(response) - - with mock.patch("scripts.beta_conformance.urllib.request.urlopen", side_effect=responses) as urlopen: - run, workflow = fetch_retention_source_metadata(29775218461, 1, "retention-token") - - self.assertEqual(self.completed_run(), run) - self.assertEqual(self.workflow(), workflow) - requests = [call.args[0] for call in urlopen.call_args_list] - self.assertEqual( - [ - "https://api.github.com/repos/durable-workflow/.github/actions/runs/29775218461/attempts/1", - "https://api.github.com/repos/durable-workflow/.github/actions/workflows/beta-conformance.yml", - ], - [request.full_url for request in requests], - ) - self.assertTrue(all(request.get_header("Authorization") == "Bearer retention-token" for request in requests)) - - def test_retention_rejects_a_different_execution_authority(self) -> None: - mutations = { - "display_title": "Unbound retention source", - "event": "pull_request", - "head_branch": "feature", - "head_repository": {"full_name": "someone/fork"}, - "head_sha": "short", - "path": ".github/workflows/another.yml", - "repository": {"full_name": "someone/fork"}, - "status": "in_progress", - } - for field, value in mutations.items(): - with self.subTest(field=field): - run = self.completed_run() - run[field] = value - with self.assertRaises(ConformanceError): - validate_retention_source(run, self.workflow(), expected_run_id=29775218461) - - with self.assertRaisesRegex(ConformanceError, "mismatched run identity"): - validate_retention_source(self.completed_run(), self.workflow(), expected_run_id=1) - with self.assertRaisesRegex(ConformanceError, "mismatched run attempt"): - validate_retention_source( - self.completed_run(), - self.workflow(), - expected_run_id=29775218461, - expected_run_attempt=2, - ) - feature_run = self.completed_run() - feature_run["path"] = ".github/workflows/beta-conformance.yml@feature" - with self.assertRaisesRegex(ConformanceError, "dispatched conformance workflow"): - validate_retention_source(feature_run, self.workflow(), expected_run_id=29775218461) - - mismatched_workflow = self.workflow() - mismatched_workflow["id"] = 1 - with self.assertRaisesRegex(ConformanceError, "dispatched conformance workflow"): - validate_retention_source( - self.completed_run(), - mismatched_workflow, - expected_run_id=29775218461, - ) - untrusted_workflow = self.workflow() - untrusted_workflow["name"] = "Another workflow" - with self.assertRaisesRegex(ConformanceError, "workflow metadata is invalid"): - validate_retention_source( - self.completed_run(), - untrusted_workflow, - expected_run_id=29775218461, - ) - - def test_workflows_separate_execution_and_retention_permissions(self) -> None: - workflows = ROOT / ".github" / "workflows" - execution = yaml.load((workflows / "beta-conformance.yml").read_text(), Loader=yaml.BaseLoader) - retention = yaml.load( - (workflows / "beta-conformance-retention.yml").read_text(), - Loader=yaml.BaseLoader, - ) - - self.assertEqual({"contents": "read"}, execution["permissions"]) - self.assertEqual({"prepare", "conformance"}, set(execution["jobs"])) - canary_input = execution["on"]["workflow_dispatch"]["inputs"]["injected_canary_failure_experiment"] - self.assertEqual("none", canary_input["default"]) - self.assertEqual("none", canary_input["options"][0]) - self.assertEqual({"none", *EXPERIMENTS}, set(canary_input["options"])) - execute_step = next(step for step in execution["jobs"]["conformance"]["steps"] if step.get("id") == "execute") - self.assertEqual( - "${{ inputs.injected_canary_failure_experiment }}", - execute_step["env"]["INJECTED_CANARY_FAILURE_EXPERIMENT"], - ) - execution_scripts = "\n".join(step.get("run", "") for step in execution["jobs"]["conformance"]["steps"]) - self.assertIn('INJECTED_CANARY_FAILURE_EXPERIMENT" = "$EXPERIMENT', execution_scripts) - self.assertIn("injection+=(--inject-product-failure)", execution_scripts) - self.assertEqual(["Beta conformance"], retention["on"]["workflow_run"]["workflows"]) - self.assertIn("workflow_dispatch", retention["on"]) - self.assertEqual( - {"actions": "read", "contents": "read"}, - retention["jobs"]["bind"]["permissions"], - ) - self.assertEqual( - {"actions": "read", "contents": "write"}, - retention["jobs"]["retain"]["permissions"], - ) - self.assertEqual("beta-conformance", retention["jobs"]["retain"]["environment"]) - retention_scripts = "\n".join(step.get("run", "") for step in retention["jobs"]["retain"]["steps"]) - self.assertIn("--source-head-sha", retention_scripts) - self.assertIn("--source-candidate", retention_scripts) - self.assertIn("evidence-ref.json evidence-ref-comparison.json", retention_scripts) - self.assertIn("for attempt in 1 2 3", retention_scripts) - - def test_workflow_reuses_the_first_attempt_plan_or_fails_before_experiments(self) -> None: - workflows = ROOT / ".github" / "workflows" - execution = yaml.load((workflows / "beta-conformance.yml").read_text(), Loader=yaml.BaseLoader) - retention = yaml.load( - (workflows / "beta-conformance-retention.yml").read_text(), - Loader=yaml.BaseLoader, - ) - prepare_steps = execution["jobs"]["prepare"]["steps"] - create = next(step for step in prepare_steps if " prepare " in f" {step.get('run', '')} ") - restore = next( - step - for step in prepare_steps - if step.get("uses") == "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" - ) - expose = next(step for step in prepare_steps if step.get("id") == "plan") - retain = next( - step - for step in prepare_steps - if step.get("uses") == "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" - ) - plan_name = "beta-conformance-plan-${{ github.run_id }}" - - self.assertEqual("${{ github.run_attempt == 1 }}", create["if"]) - self.assertEqual("${{ github.run_attempt > 1 }}", restore["if"]) - self.assertEqual(plan_name, restore["with"]["name"]) - self.assertIn("restore-plan execution-plan.json requested-candidate.json", expose["run"]) - self.assertEqual("${{ github.run_attempt == 1 }}", retain["if"]) - self.assertEqual(plan_name, retain["with"]["name"]) - self.assertNotIn("run_attempt", restore["with"]["name"]) - - conformance_restore = next( - step - for step in execution["jobs"]["conformance"]["steps"] - if step.get("uses") == "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" - ) - retention_restore = next( - step - for step in retention["jobs"]["retain"]["steps"] - if step.get("uses") == "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" - and "name" in step.get("with", {}) - ) - self.assertEqual(plan_name, conformance_restore["with"]["name"]) - self.assertEqual( - "beta-conformance-plan-${{ needs.bind.outputs.source_run_id }}", - retention_restore["with"]["name"], - ) - - def test_absent_evidence_tag_targets_the_protected_controller(self) -> None: - retention = yaml.load( - (ROOT / ".github" / "workflows" / "beta-conformance-retention.yml").read_text(), - Loader=yaml.BaseLoader, - ) - retention_scripts = "\n".join(step.get("run", "") for step in retention["jobs"]["retain"]["steps"]) - - self.assertIn('create_target=(--target "$GITHUB_SHA")', retention_scripts) - self.assertIn("create_target=(--verify-tag)", retention_scripts) - self.assertNotIn('gh release create "$EVIDENCE_TAG" --target', retention_scripts) - self.assertNotIn('--target "$SOURCE_HEAD_SHA"', retention_scripts) - self.assertNotIn('--method POST "/repos/$GITHUB_REPOSITORY/git/refs"', retention_scripts) - - def test_historical_source_and_controller_release_target_are_distinct(self) -> None: - tag = "beta-conformance/alpha-workspace-unavailable-recovery-f46818553161/29775218461.1" - source_sha = "1fd9296396bb8fc57f50362323e40ab9008bbc9f" - controller_sha = "c36b24c2a7ecca24900e4938239d3e789eaae9fd" - ref = {"ref": f"refs/tags/{tag}", "object": {"type": "commit", "sha": controller_sha}} - comparison = { - "ahead_by": 0, - "base_commit": {"sha": controller_sha}, - "behind_by": 0, - "merge_base_commit": {"sha": controller_sha}, - "status": "identical", - } - - self.assertEqual( - { - "controller_sha": controller_sha, - "evidence_ref": f"refs/tags/{tag}", - "evidence_sha": controller_sha, - "source_sha": source_sha, - }, - validate_retention_ref( - ref, - comparison, - expected_tag=tag, - source_sha=source_sha, - controller_sha=controller_sha, - ), - ) - self.assertNotEqual(source_sha, controller_sha) - - def test_existing_source_ref_is_accepted_without_moving_it(self) -> None: - tag = "beta-conformance/alpha-workspace-unavailable-recovery-f46818553161/29775218461.1" - source_sha = "1fd9296396bb8fc57f50362323e40ab9008bbc9f" - controller_sha = "c36b24c2a7ecca24900e4938239d3e789eaae9fd" - ref = {"ref": f"refs/tags/{tag}", "object": {"type": "commit", "sha": source_sha}} - comparison = { - "ahead_by": 1, - "base_commit": {"sha": source_sha}, - "behind_by": 0, - "merge_base_commit": {"sha": source_sha}, - "status": "ahead", - } - - validated = validate_retention_ref( - ref, - comparison, - expected_tag=tag, - source_sha=source_sha, - controller_sha=controller_sha, - ) - - self.assertEqual(source_sha, validated["evidence_sha"]) - - def test_release_creation_rejects_a_mismatched_or_annotated_ref(self) -> None: - tag = "beta-conformance/alpha-workspace-unavailable-recovery-f46818553161/29775218461.1" - source_sha = "1fd9296396bb8fc57f50362323e40ab9008bbc9f" - controller_sha = "c36b24c2a7ecca24900e4938239d3e789eaae9fd" - comparison = { - "ahead_by": 1, - "base_commit": {"sha": source_sha}, - "behind_by": 0, - "merge_base_commit": {"sha": source_sha}, - "status": "ahead", - } - mutations = ( - {"ref": "refs/tags/unrelated", "object": {"type": "commit", "sha": source_sha}}, - {"ref": f"refs/tags/{tag}", "object": {"type": "tag", "sha": source_sha}}, - {"ref": f"refs/tags/{tag}", "object": {"type": "commit", "sha": "a" * 40}}, - ) - for ref in mutations: - with self.subTest(ref=ref), self.assertRaises(ConformanceError): - validate_retention_ref( - ref, - comparison, - expected_tag=tag, - source_sha=source_sha, - controller_sha=controller_sha, - ) - - diverged = dict(comparison, status="diverged", behind_by=1) - ref = {"ref": f"refs/tags/{tag}", "object": {"type": "commit", "sha": source_sha}} - with self.assertRaisesRegex(ConformanceError, "outside protected controller history"): - validate_retention_ref( - ref, - diverged, - expected_tag=tag, - source_sha=source_sha, - controller_sha=controller_sha, - ) - - -class CandidateRecordFixture: - def __init__(self, component_versions: dict[str, str] | None = None) -> None: - self.temporary = tempfile.TemporaryDirectory() - self.repository = Path(self.temporary.name) - subprocess.run(["git", "init", str(self.repository)], check=True, capture_output=True) - subprocess.run( - ["git", "-C", str(self.repository), "config", "user.name", "Conformance Fixture"], - check=True, - ) - subprocess.run( - ["git", "-C", str(self.repository), "config", "user.email", "fixture@example.invalid"], - check=True, - ) - self.manifest = candidate_manifest() - for component, version in (component_versions or {}).items(): - self.manifest["components"][component]["version"] = version - (self.repository / "candidate.json").write_bytes(canonical_json(self.manifest)) - (self.repository / "verification.json").write_bytes(canonical_json(candidate_verification(self.manifest))) - subprocess.run(["git", "-C", str(self.repository), "add", "."], check=True) - subprocess.run( - ["git", "-C", str(self.repository), "commit", "-m", "Record candidate"], - check=True, - capture_output=True, - ) - self.commit = subprocess.run( - ["git", "-C", str(self.repository), "rev-parse", "HEAD"], - check=True, - capture_output=True, - text=True, - ).stdout.strip() - subprocess.run( - [ - "git", - "-C", - str(self.repository), - "tag", - f"beta-candidate/{self.manifest['candidate']}", - ], - check=True, - ) - - def close(self) -> None: - self.temporary.cleanup() - - -class ContractTest(unittest.TestCase): - def test_contract_is_portable_and_covers_the_required_beta_set(self) -> None: - contract = load_contract(CONTRACT_PATH) - self.assertEqual(set(EXPERIMENTS), set(contract["experiments"])) - self.assertEqual(RUNTIME_DEPENDENCY_SELECTORS, contract["runtime_dependencies"]) - self.assertEqual( - {"sdk-php", "sdk-python", "sdk-rust"}, - { - client - for specification in contract["experiments"].values() - for client in specification["required_clients"] - }, - ) - self.assertEqual( - set(DISTRIBUTIONS), - { - distribution - for specification in contract["experiments"].values() - for distribution in specification["required_distributions"] - }, - ) - encoded = canonical_json(contract).decode() - self.assertIsNone(re.search(r'"/(?:[^"\\]|\\.)*"', encoded)) - self.assertNotIn("../", encoded) - self.assertNotIn(":latest", encoded) - - for experiment, specification in contract["experiments"].items(): - with self.subTest(experiment=experiment): - self.assertEqual( - set(specification["required_distributions"]), - { - distribution - for runner in specification["runners"] - for distribution in runner["required_distributions"] - }, - ) - - php_runner = next( - runner for runner in contract["experiments"]["polyglot"]["runners"] if runner["id"] == "php-sdk" - ) - self.assertEqual( - { - "cache_backend": "redis", - "database_backend": "mysql", - "kind": "standalone-server", - "namespace_environment": "DW_PHP_SDK_CONFORMANCE_NAMESPACE", - "network_scope": "private", - "queue_backend": "redis", - "server_url_environment": "DW_PHP_SDK_CONFORMANCE_SERVER_URL", - "token_environment": "DW_PHP_SDK_CONFORMANCE_TOKEN", - }, - php_runner["runtime"], - ) - self.assertEqual( - ["sdk-php"], - php_runner["required_distributions"], - ) - self.assertEqual( - ["sdk-php", "server"], - runner_required_artifact_versions(php_runner), - ) - activities_runner = next( - runner - for runner in contract["experiments"]["polyglot"]["runners"] - if runner["id"] == "activities-cross-language" - ) - self.assertEqual( - ["workflow", "waterline", "server", "cli", "sdk-php", "sdk-python"], - activities_runner["required_distributions"], - ) - signals_runner = contract["experiments"]["signals-queries"]["runners"][0] - self.assertIn("waterline-service", signals_runner["required_distributions"]) - self.assertEqual( - { - "workflow", - "waterline", - "server", - "cli", - "sdk-php", - "sdk-python", - "sdk-rust", - }, - set(runner_required_artifact_versions(signals_runner)), - ) - self.assertEqual("durable-workflow.v2.signal-query-runtime.result", signals_runner["result_schema"]) - self.assertEqual( - { - "schema", - "started_at", - "finished_at", - "outcome", - "runner_blocked", - "artifactVersions", - "executed_distribution_identities", - "runtime_matrix", - "scenario_results", - "findings", - "finding_links", - }, - set(signals_runner["required_result_fields"]), - ) - self.assertEqual(19, len(signals_runner["required_scenarios"])) - self.assertIn("published_artifact_install_only", signals_runner["required_scenarios"]) - self.assertIn("waterline_operator_visibility", signals_runner["required_scenarios"]) - self.assertIn("waterline_service_operator_visibility", signals_runner["required_scenarios"]) - - def test_contract_rejects_a_multi_runner_distribution_gap(self) -> None: - contract = load_contract(CONTRACT_PATH) - contract["experiments"]["heartbeats"]["runners"][2]["required_distributions"].remove("sdk-rust") - - with self.assertRaisesRegex(ConformanceError, "do not cover"): - validate_contract(contract) - - def test_every_schema_is_parseable_draft_2020_12(self) -> None: - for path in sorted((ROOT / "beta-conformance").glob("*schema.json")): - schema = json.loads(path.read_bytes()) - self.assertEqual("https://json-schema.org/draft/2020-12/schema", schema["$schema"]) - - result_schema = json.loads((ROOT / "beta-conformance" / "result-schema.json").read_bytes()) - diagnostic = result_schema["properties"]["diagnostics"]["items"] - self.assertTrue( - { - "native_result_size_bytes", - "native_result_sha256", - "native_result_prefix_sha256", - "native_result_prefix_bytes", - }.issubset(diagnostic["required"]) - ) - self.assertEqual(4, len(diagnostic["oneOf"])) - beta_schema_validator("contract-schema.json").validate(load_contract(CONTRACT_PATH)) - - -class PlanTest(unittest.TestCase): - def setUp(self) -> None: - self.fixture = CandidateRecordFixture() - self.contract = load_contract(CONTRACT_PATH) - - def tearDown(self) -> None: - self.fixture.close() - - def test_plan_binds_record_tuple_sources_runner_and_oci_digest(self) -> None: - plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - validate_plan(plan) - beta_schema_validator("plan-schema.json").validate(plan) - self.assertEqual(self.fixture.commit, plan["candidate"]["record_commit"]) - self.assertEqual(self.fixture.commit, plan["runner"]["revision"]) - self.assertEqual( - {name: identity["commit"] for name, identity in self.fixture.manifest["components"].items()}, - plan["source_identities"], - ) - self.assertEqual( - f"docker.io/durableworkflow/server@sha256:{'a' * 64}", - plan["server_runner"]["image"], - ) - self.assertEqual( - f"docker.io/durableworkflow/waterline@sha256:{'b' * 64}", - plan["waterline_service_runner"]["image"], - ) - self.assertEqual( - sha256_bytes(canonical_json(candidate_verification(self.fixture.manifest))), - plan["candidate"]["verification_sha256"], - ) - self.assertEqual(set(DISTRIBUTIONS), set(plan["distribution_identities"])) - self.assertEqual(runtime_dependencies(), plan["runtime_dependencies"]) - - def test_plan_rejects_missing_or_mismatched_waterline_service_evidence(self) -> None: - plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - - missing = json.loads(canonical_json(plan)) - del missing["distribution_identities"]["waterline-service"] - with self.assertRaisesRegex(ConformanceError, "every required distribution"): - validate_plan(missing) - with self.assertRaises(ValidationError): - beta_schema_validator("plan-schema.json").validate(missing) - - mismatched = json.loads(canonical_json(plan)) - mismatched["waterline_service_runner"]["source_commit"] = "f" * 40 - with self.assertRaisesRegex(ConformanceError, "Waterline service runner"): - validate_plan(mismatched) - - def test_prepare_resolves_declared_runtime_selectors_to_one_manifest_digest(self) -> None: - digests = {"mysql": f"sha256:{'c' * 64}", "redis": f"sha256:{'d' * 64}"} - commands: list[list[str]] = [] - - def docker(command: list[str], **arguments: object) -> subprocess.CompletedProcess[str]: - commands.append(command) - selector = command[-1] - name = next(name for name, value in RUNTIME_DEPENDENCY_SELECTORS.items() if value == selector) - if command[1] == "image": - repository = name if name == "mysql" else f"docker.io/library/{name}" - stdout = json.dumps([f"{repository}@{digests[name]}"]) - else: - stdout = "pulled\n" - return subprocess.CompletedProcess(command, 0, stdout=stdout, stderr="") - - with mock.patch("scripts.beta_conformance.docker_runtime_command", side_effect=docker): - resolved = resolve_runtime_dependencies(self.contract) - - self.assertEqual(4, len(commands)) - for name, selector in RUNTIME_DEPENDENCY_SELECTORS.items(): - self.assertEqual(selector, resolved[name]["selector"]) - self.assertEqual(digests[name], resolved[name]["manifest_digest"]) - self.assertEqual( - f"{selector.rsplit(':', 1)[0]}@{digests[name]}", - resolved[name]["image"], - ) - - def test_restore_reuses_the_plan_without_resolving_runtime_selectors(self) -> None: - plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - - with mock.patch("scripts.beta_conformance.resolve_runtime_dependencies") as resolve: - restored = restore_plan(plan, self.fixture.manifest, self.contract, self.fixture.commit) - - self.assertIs(plan, restored) - resolve.assert_not_called() - - def test_restore_rejects_a_plan_from_another_run_identity(self) -> None: - plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - - with self.subTest("runner revision"), self.assertRaisesRegex(ConformanceError, "workflow revision"): - restore_plan(plan, self.fixture.manifest, self.contract, "f" * 40) - - with self.subTest("candidate"): - changed_manifest = json.loads(canonical_json(self.fixture.manifest)) - changed_manifest["candidate"] = "another-candidate" - with self.assertRaisesRegex(ConformanceError, "requested candidate"): - restore_plan(plan, changed_manifest, self.contract, self.fixture.commit) - - with self.subTest("contract"): - changed_contract = json.loads(canonical_json(self.contract)) - changed_contract["experiments"]["replay"]["timeout_seconds"] += 1 - with self.assertRaisesRegex(ConformanceError, "this contract"): - restore_plan(plan, self.fixture.manifest, changed_contract, self.fixture.commit) - - def test_plan_rejects_a_mutable_runtime_dependency_reference(self) -> None: - dependencies = runtime_dependencies() - dependencies["mysql"]["image"] = dependencies["mysql"]["selector"] - - with self.assertRaisesRegex(ConformanceError, "exact OCI manifest binding"): - prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - dependencies, - ) - - def test_plan_rejects_tuple_mutation_after_immutable_record(self) -> None: - changed = json.loads(canonical_json(self.fixture.manifest)) - changed["components"]["sdk-python"]["version"] = "9.9.9" - with self.assertRaisesRegex(RuntimeError, "does not contain the requested immutable tuple"): - prepare_plan( - self.fixture.repository, - changed, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - - -class StandaloneServerRuntimeTest(unittest.TestCase): - def setUp(self) -> None: - self.fixture = CandidateRecordFixture() - self.contract = load_contract(CONTRACT_PATH) - self.plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - self.temporary = tempfile.TemporaryDirectory() - self.scratch = Path(self.temporary.name) - self.runner = next( - runner for runner in self.contract["experiments"]["polyglot"]["runners"] if runner["id"] == "php-sdk" - ) - - def tearDown(self) -> None: - self.temporary.cleanup() - self.fixture.close() - - def test_declared_runtime_uses_exact_candidate_image_and_cleans_isolated_state(self) -> None: - commands: list[list[str]] = [] - - def docker(command: list[str], **arguments: object) -> subprocess.CompletedProcess[str]: - commands.append(command) - if command[1] == "port": - stdout = "127.0.0.1:49152\n" - elif command[1] == "inspect": - stdout = "healthy\n" if "Health.Status" in command[3] else "true\n" - else: - stdout = "runtime-id\n" - return subprocess.CompletedProcess(command, 0, stdout=stdout, stderr="") - - with ( - mock.patch("scripts.beta_conformance.docker_runtime_command", side_effect=docker), - mock.patch("scripts.beta_conformance.wait_for_server_ready") as wait_for_server, - runner_runtime_environment(self.plan, self.runner, self.scratch) as environment, - ): - self.assertEqual("http://127.0.0.1:49152", environment["DW_PHP_SDK_CONFORMANCE_SERVER_URL"]) - self.assertEqual("default", environment["DW_PHP_SDK_CONFORMANCE_NAMESPACE"]) - self.assertRegex(environment["DW_PHP_SDK_CONFORMANCE_TOKEN"], r"^beta-[0-9a-f]{32}$") - - wait_for_server.assert_called_once() - run_commands = [command for command in commands if command[1] == "run"] - self.assertEqual(6, len(run_commands)) - server_commands = [command for command in run_commands if self.plan["server_runner"]["image"] in command] - self.assertEqual(4, len(server_commands)) - for dependency in self.plan["runtime_dependencies"].values(): - self.assertTrue(any(dependency["image"] in command for command in run_commands)) - self.assertFalse(any(dependency["selector"] in command for command in run_commands)) - expected_version = self.plan["artifact_tuple"]["server"]["version"] - self.assertTrue(all(f"APP_VERSION={expected_version}" in command for command in server_commands)) - self.assertTrue(all("DB_CONNECTION=mysql" in command for command in server_commands)) - self.assertTrue(all("QUEUE_CONNECTION=redis" in command for command in server_commands)) - self.assertTrue(all("CACHE_STORE=redis" in command for command in server_commands)) - self.assertTrue(all("DB_CONNECTION=sqlite" not in command for command in run_commands)) - network_commands = [command for command in commands if command[1:3] == ["network", "create"]] - self.assertEqual(1, len(network_commands)) - network_name = network_commands[0][-1] - self.assertTrue(all(network_name in command for command in run_commands)) - self.assertTrue(any("127.0.0.1::8080" in command for command in run_commands)) - self.assertTrue(any(command[-1] == "server-bootstrap" for command in run_commands)) - self.assertTrue(any("queue:work" in command for command in run_commands)) - scheduler_commands = [command for command in run_commands if "schedule:evaluate" in command[-1]] - self.assertEqual(1, len(scheduler_commands)) - self.assertIn("--init", scheduler_commands[0]) - self.assertEqual(12, len([command for command in commands if command[1] == "inspect"])) - self.assertEqual(6, len([command for command in commands if command[1:3] == ["rm", "--force"]])) - self.assertEqual(1, len([command for command in commands if command[1:3] == ["network", "rm"]])) - self.assertFalse(any(command[1] == "volume" for command in commands)) - - def test_declared_runtime_rejects_a_companion_that_exits_during_the_matrix(self) -> None: - running_inspect_count = 0 - - def docker(command: list[str], **arguments: object) -> subprocess.CompletedProcess[str]: - nonlocal running_inspect_count - if command[1] == "port": - stdout = "127.0.0.1:49152\n" - elif command[1] == "inspect": - if "Health.Status" in command[3]: - stdout = "healthy\n" - else: - running_inspect_count += 1 - stdout = "false\n" if running_inspect_count == 4 else "true\n" - elif command[1] == "logs": - stdout = "queue worker stopped\n" - else: - stdout = "runtime-id\n" - return subprocess.CompletedProcess(command, 0, stdout=stdout, stderr="") - - with ( - mock.patch("scripts.beta_conformance.docker_runtime_command", side_effect=docker), - mock.patch("scripts.beta_conformance.wait_for_server_ready"), - self.assertRaisesRegex(ConformanceError, r"standalone server process .*queue.* exited"), - runner_runtime_environment(self.plan, self.runner, self.scratch), - ): - pass - - def test_waterline_service_runtime_joins_the_private_server_network(self) -> None: - service_runner = next( - runner - for runner in self.contract["experiments"]["signals-queries"]["runners"] - if runner["id"] == "waterline-service" - ) - commands: list[list[str]] = [] - - def docker(command: list[str], **arguments: object) -> subprocess.CompletedProcess[str]: - commands.append(command) - if command[1] == "port": - stdout = "127.0.0.1:49152\n" - elif command[1] == "inspect": - stdout = "healthy\n" if "Health.Status" in command[3] else "true\n" - else: - stdout = "runtime-id\n" - return subprocess.CompletedProcess(command, 0, stdout=stdout, stderr="") - - with ( - mock.patch("scripts.beta_conformance.docker_runtime_command", side_effect=docker), - mock.patch("scripts.beta_conformance.wait_for_server_ready"), - runner_runtime_environment(self.plan, service_runner, self.scratch) as environment, - ): - network = environment["DW_WATERLINE_SERVICE_DOCKER_NETWORK"] - server_container = network.removesuffix("-network") + "-http" - self.assertEqual( - f"http://{server_container}:8080", - environment["DW_WATERLINE_SERVICE_SERVER_URL"], - ) - self.assertNotIn("127.0.0.1", environment["DW_WATERLINE_SERVICE_SERVER_URL"]) - - self.assertTrue(any(command[1:3] == ["network", "create"] and network in command for command in commands)) - - -class FailureClassificationTest(unittest.TestCase): - def test_semantic_failure_is_never_retried_even_when_log_mentions_503(self) -> None: - classification, retryable = classify_attempt( - returncode=1, - timed_out=False, - native_outcome="fail", - runner_blocked=False, - native_result_rejected=False, - diagnostic_text="HTTP 503 appeared in a product assertion", - ) - self.assertEqual("product_failure", classification) - self.assertFalse(retryable) - - def test_only_classified_infrastructure_transient_is_retryable(self) -> None: - classification, retryable = classify_attempt( - returncode=1, - timed_out=False, - native_outcome=None, - runner_blocked=False, - native_result_rejected=False, - diagnostic_text="registry returned 503 Service Unavailable during pull", - ) - self.assertEqual("infrastructure_failure", classification) - self.assertTrue(retryable) - - def test_timeout_is_a_red_owning_contract_failure_not_a_retry(self) -> None: - classification, retryable = classify_attempt( - returncode=-15, - timed_out=True, - native_outcome=None, - runner_blocked=False, - native_result_rejected=False, - diagnostic_text="", - ) - self.assertEqual("product_failure", classification) - self.assertFalse(retryable) - - def test_rejected_native_result_is_never_retried_as_a_transient(self) -> None: - classification, retryable = classify_attempt( - returncode=75, - timed_out=False, - native_outcome=None, - runner_blocked=True, - native_result_rejected=True, - diagnostic_text="tls handshake timeout", - ) - self.assertEqual("infrastructure_failure", classification) - self.assertFalse(retryable) - - def test_native_artifact_version_drift_stays_red(self) -> None: - fixture = CandidateRecordFixture() - try: - contract = load_contract(CONTRACT_PATH) - plan = prepare_plan( - fixture.repository, - fixture.manifest, - contract, - fixture.commit, - runtime_dependencies(), - ) - diagnostic = successful_diagnostic(plan, ["sdk-python"]) - diagnostic["native_summary"]["artifact_versions"] = { - "sdk-python": "9.9.9", - } - failures = artifact_binding_failures(plan, ["sdk-python"], [diagnostic]) - self.assertEqual(1, len(failures)) - self.assertIn("expected exact version", failures[0]) - finally: - fixture.close() - - def test_registry_native_python_version_is_accepted_only_for_the_exact_candidate(self) -> None: - fixture = CandidateRecordFixture({"sdk-python": "2.0.0-beta.10"}) - try: - contract = load_contract(CONTRACT_PATH) - plan = prepare_plan( - fixture.repository, - fixture.manifest, - contract, - fixture.commit, - runtime_dependencies(), - ) - diagnostic = successful_diagnostic(plan, ["sdk-python"]) - summary = diagnostic["native_summary"] - summary["artifact_versions"]["sdk-python"] = "2.0.0b10" - summary["executed_distribution_identities"]["sdk-python"]["locator"] = "pypi:durable-workflow@2.0.0b10" - - self.assertEqual([], artifact_binding_failures(plan, ["sdk-python"], [diagnostic])) - - mismatched_bytes = json.loads(canonical_json(diagnostic)) - mismatched_bytes["native_summary"]["executed_distribution_identities"]["sdk-python"]["artifacts"][0][ - "sha256" - ] = "f" * 64 - failures = artifact_binding_failures(plan, ["sdk-python"], [mismatched_bytes]) - self.assertEqual(1, len(failures)) - self.assertIn("does not match the candidate digest", failures[0]) - - mismatched = json.loads(canonical_json(diagnostic)) - mismatched["native_summary"]["artifact_versions"]["sdk-python"] = "2.0.0b11" - mismatched["native_summary"]["executed_distribution_identities"]["sdk-python"]["locator"] = ( - "pypi:durable-workflow@2.0.0b11" - ) - failures = artifact_binding_failures(plan, ["sdk-python"], [mismatched]) - self.assertEqual(2, len(failures)) - self.assertTrue(any("expected exact version" in failure for failure in failures)) - self.assertTrue(any("different distribution locator" in failure for failure in failures)) - finally: - fixture.close() - - def test_same_version_with_a_different_distribution_digest_stays_red(self) -> None: - fixture = CandidateRecordFixture() - try: - contract = load_contract(CONTRACT_PATH) - plan = prepare_plan( - fixture.repository, - fixture.manifest, - contract, - fixture.commit, - runtime_dependencies(), - ) - diagnostic = successful_diagnostic(plan, ["sdk-python"]) - diagnostic["native_summary"]["executed_distribution_identities"]["sdk-python"]["artifacts"][0]["sha256"] = ( - "f" * 64 - ) - failures = artifact_binding_failures(plan, ["sdk-python"], [diagnostic]) - self.assertEqual(1, len(failures)) - self.assertIn("sdk-python executed distribution artifact", failures[0]) - finally: - fixture.close() - - def test_identity_failure_injection_changes_a_required_digest_not_the_version(self) -> None: - fixture = CandidateRecordFixture() - try: - contract = load_contract(CONTRACT_PATH) - plan = prepare_plan( - fixture.repository, - fixture.manifest, - contract, - fixture.commit, - runtime_dependencies(), - ) - diagnostic = successful_diagnostic(plan, ["sdk-python"]) - native = { - "executed_distribution_identities": diagnostic["native_summary"]["executed_distribution_identities"] - } - component, artifact_name = inject_distribution_identity_mismatch(native, plan, ["sdk-python"]) - self.assertEqual("sdk-python", component) - self.assertEqual("durable_workflow.tar.gz", artifact_name) - self.assertEqual( - plan["artifact_tuple"]["sdk-python"]["version"], - diagnostic["native_summary"]["artifact_versions"]["sdk-python"], - ) - failures = artifact_binding_failures(plan, ["sdk-python"], [diagnostic]) - self.assertEqual(1, len(failures)) - self.assertIn("does not match the candidate digest", failures[0]) - finally: - fixture.close() - - def test_missing_distribution_evidence_stays_red(self) -> None: - fixture = CandidateRecordFixture() - try: - contract = load_contract(CONTRACT_PATH) - plan = prepare_plan( - fixture.repository, - fixture.manifest, - contract, - fixture.commit, - runtime_dependencies(), - ) - diagnostic = successful_diagnostic(plan, ["cli"]) - del diagnostic["native_summary"]["executed_distribution_identities"]["cli"] - failures = artifact_binding_failures(plan, ["cli"], [diagnostic]) - self.assertEqual(1, len(failures)) - self.assertIn("executed cli distribution identity", failures[0]) - finally: - fixture.close() - - def test_injected_product_failure_has_stable_fingerprint_and_one_attempt(self) -> None: - fixture = CandidateRecordFixture() - try: - contract = load_contract(CONTRACT_PATH) - plan = prepare_plan( - fixture.repository, - fixture.manifest, - contract, - fixture.commit, - runtime_dependencies(), - ) - specification = contract["experiments"]["replay"] - first = injected_failure_result( - plan, - "replay", - "deterministic-replay", - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - ) - second = injected_failure_result( - plan, - "replay", - "deterministic-replay", - specification["required_clients"], - specification["required_distributions"], - "2026-07-18T00:00:00Z", - ) - self.assertEqual(first["failure_fingerprint"], second["failure_fingerprint"]) - self.assertEqual(1, first["retry"]["attempts"]) - self.assertEqual(MAX_INFRASTRUCTURE_ATTEMPTS, first["retry"]["maximum_infrastructure_attempts"]) - self.assertFalse(first["retry"]["semantic_failures_retryable"]) - validate_experiment_result(first, plan, contract) - finally: - fixture.close() - - -class ExperimentRetryTest(unittest.TestCase): - def setUp(self) -> None: - self.fixture = CandidateRecordFixture() - self.contract = load_contract(CONTRACT_PATH) - self.plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - self.temporary = tempfile.TemporaryDirectory() - self.root = Path(self.temporary.name) - self.artifact_root = self.root / "published-server" - self.result_dir = self.root / "result" - self.specification = self.contract["experiments"]["replay"] - self.runner = self.specification["runners"][0] - runner_path = self.artifact_root / self.runner["path"] - runner_path.parent.mkdir(parents=True) - runner_path.write_text("#!/usr/bin/env bash\n", encoding="utf-8") - - def tearDown(self) -> None: - self.temporary.cleanup() - self.fixture.close() - - def native_result(self, outcome: str) -> dict[str, object]: - return { - "schema": "fixture.result/v1", - "outcome": outcome, - "artifact_versions": { - name: self.plan["artifact_tuple"][name]["version"] - for name in self.specification["required_distributions"] - }, - "executed_distribution_identities": { - name: json.loads(canonical_json(self.plan["distribution_identities"][name])) - for name in self.specification["required_distributions"] - }, - "local_product_source_checkout_used": False, - "scenario_results": {"fixture": outcome}, - "findings": ( - [] - if outcome == "pass" - else [ - { - "type": "fixture_product_failure", - "owning_contract": self.specification["owning_contract"], - "summary": "The fixture detected a semantic failure.", - } - ] - ), - } - - def test_injected_canary_failure_asset_is_redacted_and_preserves_the_raw_digest(self) -> None: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - inject_product_failure=True, - ) - - raw_stderr = f"deterministic synthetic sanitizer canary: Authorization: Bearer {SYNTHETIC_CREDENTIAL_CANARY}" - diagnostic = result["diagnostics"][0] - asset = (self.result_dir / "experiment-result.json").read_bytes() - self.assertEqual(canonical_json(result), asset) - self.assertIn(b"[REDACTED]", asset) - self.assertEqual(sha256_bytes(raw_stderr.encode()), diagnostic["stderr_sha256"]) - self.assertNotIn(SYNTHETIC_CREDENTIAL_CANARY.encode(), asset) - validate_experiment_result(result, self.plan, self.contract) - - def test_failed_runner_asset_sanitizes_every_public_diagnostic_and_preserves_raw_identities(self) -> None: - stdout = "runner password=stdout-secret" - stderr = "Authorization: Bearer stderr-secret" - beta_token = "beta-0123456789abcdef0123456789abcdef" - credential_url = "https://runner:summary-secret@example.test/failure" - native = self.native_result("fail api_token=outcome-secret") - native["schema"] = f"fixture.{beta_token}" - first_distribution = self.specification["required_distributions"][0] - native["artifact_versions"][first_distribution] = ( - f"{native['artifact_versions'][first_distribution]} db_password=version-secret" - ) - native["findings"] = [ - { - "type": "password=type-secret", - "owning_contract": "Authorization: Bearer owner-secret", - "summary": credential_url, - } - ] - native_payload = canonical_json(native) - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text(stdout, encoding="utf-8") - stderr_path.write_text(stderr, encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(native_payload) - return 1, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - diagnostic = result["diagnostics"][0] - rendered = canonical_json(result).decode() - self.assertEqual(sha256_bytes(stdout.encode()), diagnostic["stdout_sha256"]) - self.assertEqual(sha256_bytes(stderr.encode()), diagnostic["stderr_sha256"]) - self.assertEqual(sha256_bytes(native_payload), diagnostic["native_result_sha256"]) - self.assertIn("[REDACTED]", rendered) - for fragment in ( - "stdout-secret", - "stderr-secret", - "outcome-secret", - beta_token, - "version-secret", - "type-secret", - "owner-secret", - "summary-secret", - ): - self.assertNotIn(fragment, rendered) - validate_experiment_result(result, self.plan, self.contract) - - def test_retention_rejects_sensitive_text_in_every_public_diagnostic_surface(self) -> None: - result = experiment_result( - self.plan, - "replay", - self.specification["owning_contract"], - self.specification["required_clients"], - self.specification["required_distributions"], - "2026-07-19T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, self.specification), - ) - leaks = { - "stdout": ("stdout_tail", "password=stdout-secret"), - "stderr": ("stderr_tail", "Authorization: Bearer stderr-secret"), - "native outcome": ("native_outcome", "fail beta-0123456789abcdef0123456789abcdef"), - } - for label, (field, value) in leaks.items(): - with self.subTest(surface=label): - leaking = json.loads(canonical_json(result)) - leaking["diagnostics"][0][field] = value - with self.assertRaisesRegex(ConformanceError, "unsanitized sensitive text"): - validate_experiment_result(leaking, self.plan) - - finding_leak = json.loads(canonical_json(result)) - finding_leak["diagnostics"][0]["findings"] = [ - { - "type": "password=type-secret", - "owning_contract": "Authorization: Bearer owner-secret", - "summary": "https://runner:summary-secret@example.test/failure", - } - ] - with self.assertRaisesRegex(ConformanceError, "unsanitized sensitive text"): - validate_experiment_result(finding_leak, self.plan) - - summary_leak = json.loads(canonical_json(result)) - summary_leak["diagnostics"][0]["native_summary"]["schema"] = "password=schema-secret" - with self.assertRaisesRegex(ConformanceError, "unsanitized sensitive text"): - validate_experiment_result(summary_leak, self.plan) - - def test_native_failure_projection_retains_attribution_and_sanitized_companion_evidence(self) -> None: - private_token = "beta-0123456789abcdef0123456789abcdef" - quoted_password = r"two \"quoted-password-fragment\" password-tail" - quoted_api_token = "left,right;tail" - escaped_password = r"escaped \\\"escaped-password-fragment\\\" escaped-tail" - escaped_api_token = "escaped,left;tail" - quoted_bearer = "bearer token, with; delimiters" - multiline_password = 'line-one\nmultiline-password-secret" multiline-password-tail' - multiline_authorization = "line-one\nmultiline-authorization-secret multiline-authorization-tail" - native = self.native_result("fail") - native["scenario_results"] = { - "php_sdk_lifecycle_surface": { - "scenario_id": "php_sdk_lifecycle_surface", - "status": "fail", - "observed_outputs": { - "failure_stage": "baseline_client", - "failure_classification": "server", - "failure_owner": "server", - "worker_evidence": { - "process_state": {"state": "exited", "alive": False, "exit_code": 1}, - "companion": {"access_token": private_token}, - }, - "server_evidence": { - "runtime_failure": { - "operation": "worker.run", - "status_code": 500, - "public_error_envelope": { - "message": f"Authorization: Bearer {private_token}", - "diagnostic": (f'{{"password":"{quoted_password}","api_token":"{quoted_api_token}"}}'), - "quoted_header": f'{{"Authorization":"Bearer {quoted_bearer}"}}', - "multiline_diagnostic": f'password="{multiline_password}', - "multiline_header": f"Authorization: Bearer {multiline_authorization}", - "escaped_diagnostics": [ - rf"{{\"password\":\"{escaped_password}\"}}", - rf"{{\"api_token\":\"{escaped_api_token}\"}}", - ], - }, - }, - }, - }, - "linked_findings": [ - { - "classification": "server", - "owning_surface": "server", - "summary": "The companion worker exited after a worker-protocol response.", - } - ], - } - } - - summary = summarize_native_result(native) - - self.assertIsNotNone(summary) - assert summary is not None - projection = summary["failure_projection"] - self.assertLessEqual(len(canonical_json(projection)), NATIVE_FAILURE_PROJECTION_LIMIT) - self.assertFalse(projection["truncated"]) - self.assertEqual(1, len(projection["scenarios"])) - scenario = projection["scenarios"][0] - self.assertEqual("baseline_client", scenario["failure_stage"]) - self.assertEqual("server", scenario["failure_classification"]) - self.assertEqual("server", scenario["failure_owner"]) - self.assertEqual("exited", scenario["worker_evidence"]["process_state"]["state"]) - self.assertEqual(500, scenario["server_evidence"]["runtime_failure"]["status_code"]) - rendered = canonical_json(projection).decode() - self.assertNotIn(private_token, rendered) - self.assertNotIn(quoted_password, rendered) - self.assertNotIn(quoted_api_token, rendered) - self.assertNotIn(escaped_password, rendered) - self.assertNotIn(escaped_api_token, rendered) - self.assertNotIn(quoted_bearer, rendered) - self.assertNotIn("quoted-password-fragment", rendered) - self.assertNotIn("password-tail", rendered) - self.assertNotIn("escaped-password-fragment", rendered) - self.assertNotIn("escaped-tail", rendered) - self.assertNotIn("multiline-password-secret", rendered) - self.assertNotIn("multiline-password-tail", rendered) - self.assertNotIn("multiline-authorization-secret", rendered) - self.assertNotIn("multiline-authorization-tail", rendered) - self.assertIn("[REDACTED]", rendered) - public_error_envelope = scenario["server_evidence"]["runtime_failure"]["public_error_envelope"] - self.assertEqual( - '{"password":"[REDACTED]","api_token":"[REDACTED]"}', - public_error_envelope["diagnostic"], - ) - self.assertEqual( - '{"Authorization":"Bearer [REDACTED]"}', - public_error_envelope["quoted_header"], - ) - self.assertEqual("password=[REDACTED]", public_error_envelope["multiline_diagnostic"]) - self.assertEqual("Authorization: Bearer [REDACTED]", public_error_envelope["multiline_header"]) - escaped_diagnostics = public_error_envelope["escaped_diagnostics"] - self.assertEqual( - [r"{\"password\":\"[REDACTED]\"}", r"{\"api_token\":\"[REDACTED]\"}"], - escaped_diagnostics, - ) - self.assertEqual("", native_failure_projection_error(projection)) - escaped_diagnostics[0] = rf"{{\"password\":\"{escaped_password}\"}}" - self.assertEqual( - "experiment result has unsanitized native failure evidence", - native_failure_projection_error(projection), - ) - escaped_diagnostics[1] = r"{\"api_token\":\"[REDACTED]\"}" - public_error_envelope["multiline_diagnostic"] = f'password="{multiline_password}' - self.assertEqual( - "experiment result has unsanitized native failure evidence", - native_failure_projection_error(projection), - ) - public_error_envelope["multiline_diagnostic"] = "password=[REDACTED]" - public_error_envelope["multiline_header"] = f"Authorization: Bearer {multiline_authorization}" - self.assertEqual( - "experiment result has unsanitized native failure evidence", - native_failure_projection_error(projection), - ) - escaped_diagnostics[0] = r"{\"password\":\"[REDACTED]\"}" - escaped_diagnostics[1] = rf"{{\"api_token\":\"{escaped_api_token}\"}}" - self.assertEqual( - "experiment result has unsanitized native failure evidence", - native_failure_projection_error(projection), - ) - - def test_native_failure_projection_sanitizes_and_validates_attribution_strings(self) -> None: - sensitive_values = { - "id": r'password="scenario \"id-secret\" id-tail"', - "status": 'fail api_token="status \\"status-secret\\"\nstatus-tail', - "failure_stage": r'client password="stage \"stage-secret\" stage-tail"', - "failure_classification": r'server api_token="class \"class-secret\" class-tail"', - "failure_owner": 'server password="owner\nnewline-owner-secret" newline-owner-tail', - } - sensitive_fragments = { - field: re.findall(r"[a-z]+-(?:secret|tail)", value) for field, value in sensitive_values.items() - } - native = self.native_result("fail") - native["scenario_results"] = { - "fixture": { - "scenario_id": sensitive_values["id"], - "status": sensitive_values["status"], - "observed_outputs": { - field: sensitive_values[field] - for field in ("failure_stage", "failure_classification", "failure_owner") - }, - } - } - - summary = summarize_native_result(native) - - self.assertIsNotNone(summary) - assert summary is not None - projection = summary["failure_projection"] - scenario = projection["scenarios"][0] - retained_status = summary["scenario_statuses"][0] - rendered_summary = canonical_json(summary).decode() - self.assertEqual("", native_failure_projection_error(projection)) - for field, sensitive_value in sensitive_values.items(): - with self.subTest(field=field): - self.assertNotIn(sensitive_value, scenario[field]) - self.assertIn("[REDACTED]", scenario[field]) - for fragment in sensitive_fragments[field]: - self.assertNotIn(fragment, scenario[field]) - self.assertNotIn(fragment, rendered_summary) - leaking_projection = json.loads(canonical_json(projection)) - leaking_projection["scenarios"][0][field] = sensitive_value - self.assertEqual( - "experiment result has unsanitized native failure attribution", - native_failure_projection_error(leaking_projection), - ) - self.assertEqual(scenario["id"], retained_status["id"]) - self.assertEqual(scenario["status"], retained_status["status"]) - - def test_native_summary_validation_rejects_unsanitized_scenario_statuses(self) -> None: - specification = self.contract["experiments"]["replay"] - result = experiment_result( - self.plan, - "replay", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-19T00:00:00Z", - "passed", - 1, - [successful_diagnostic(self.plan, specification["required_distributions"])], - ) - sensitive_values = { - "id": r'password="scenario \"id-secret\" id-tail"', - "status": "pass api_token=status-secret\nstatus-tail", - } - - for field, sensitive_value in sensitive_values.items(): - with self.subTest(field=field): - leaking_result = json.loads(canonical_json(result)) - leaking_result["diagnostics"][0]["native_summary"]["scenario_statuses"][0][field] = sensitive_value - with self.assertRaisesRegex(ConformanceError, "unsanitized native scenario statuses"): - validate_experiment_result(leaking_result, self.plan) - - def test_native_failure_projection_redacts_ambiguous_unquoted_scalar_suffixes(self) -> None: - sensitive_values = { - "failure_stage": "client password=two words tail", - "worker_evidence": "api_token=left,right;tail", - "server_evidence": "Authorization: Bearer [REDACTED] suffix", - } - native = self.native_result("fail") - native["scenario_results"] = { - "fixture": { - "scenario_id": "fixture", - "status": "fail", - "observed_outputs": { - "failure_stage": sensitive_values["failure_stage"], - "worker_evidence": sensitive_values["worker_evidence"], - "server_evidence": sensitive_values["server_evidence"], - }, - } - } - - summary = summarize_native_result(native) - - self.assertIsNotNone(summary) - assert summary is not None - projection = summary["failure_projection"] - scenario = projection["scenarios"][0] - self.assertEqual("client password=[REDACTED]", scenario["failure_stage"]) - self.assertEqual("api_token=[REDACTED]", scenario["worker_evidence"]) - self.assertEqual("Authorization: Bearer [REDACTED]", scenario["server_evidence"]) - self.assertEqual("", native_failure_projection_error(projection)) - - for field, sensitive_value in sensitive_values.items(): - with self.subTest(field=field): - leaking_projection = json.loads(canonical_json(projection)) - leaking_projection["scenarios"][0][field] = sensitive_value - expected_error = ( - "experiment result has unsanitized native failure attribution" - if field == "failure_stage" - else "experiment result has unsanitized native failure evidence" - ) - self.assertEqual( - expected_error, - native_failure_projection_error(leaking_projection), - ) - - def test_native_failure_projection_redacts_compound_sensitive_keys(self) -> None: - sensitive_attribution = { - "id": "case access_token=access token tail", - "status": "fail db_password=database password tail", - "failure_stage": "client x-api-token=x api token tail", - "failure_owner": "server Proxy-Authorization: Bearer proxy authorization tail", - } - sensitive_evidence = { - "snake": [ - "access_token=snake access tail", - "db_password=snake database tail", - "x_api_token=snake x api tail", - "proxy_authorization: Bearer snake proxy tail", - ], - "kebab": [ - "access-token=kebab access tail", - "db-password=kebab database tail", - "x-api-token=kebab x api tail", - "Proxy-Authorization: Bearer kebab proxy tail", - ], - "camel": [ - "accessToken=camel access tail", - "dbPassword=camel database tail", - "xApiToken=camel x api tail", - "proxyAuthorization: Bearer camel proxy tail", - ], - } - native = self.native_result("fail") - native["scenario_results"] = { - "fixture": { - "scenario_id": sensitive_attribution["id"], - "status": sensitive_attribution["status"], - "observed_outputs": { - "failure_stage": sensitive_attribution["failure_stage"], - "failure_owner": sensitive_attribution["failure_owner"], - "worker_evidence": sensitive_evidence, - }, - } - } - - summary = summarize_native_result(native) - - self.assertIsNotNone(summary) - assert summary is not None - projection = summary["failure_projection"] - scenario = projection["scenarios"][0] - self.assertEqual("case access_token=[REDACTED]", scenario["id"]) - self.assertEqual("fail db_password=[REDACTED]", scenario["status"]) - self.assertEqual("client x-api-token=[REDACTED]", scenario["failure_stage"]) - self.assertEqual( - "server Proxy-Authorization: Bearer [REDACTED]", - scenario["failure_owner"], - ) - for style, values in sensitive_evidence.items(): - with self.subTest(style=style): - self.assertEqual( - [ - values[0].split("=", 1)[0] + "=[REDACTED]", - values[1].split("=", 1)[0] + "=[REDACTED]", - values[2].split("=", 1)[0] + "=[REDACTED]", - values[3].split(":", 1)[0] + ": Bearer [REDACTED]", - ], - scenario["worker_evidence"][style], - ) - self.assertEqual("", native_failure_projection_error(projection)) - - for field, sensitive_value in sensitive_attribution.items(): - with self.subTest(field=field): - leaking_projection = json.loads(canonical_json(projection)) - leaking_projection["scenarios"][0][field] = sensitive_value - self.assertEqual( - "experiment result has unsanitized native failure attribution", - native_failure_projection_error(leaking_projection), - ) - for style, values in sensitive_evidence.items(): - for index, sensitive_value in enumerate(values): - with self.subTest(style=style, index=index): - leaking_projection = json.loads(canonical_json(projection)) - leaking_projection["scenarios"][0]["worker_evidence"][style][index] = sensitive_value - self.assertEqual( - "experiment result has unsanitized native failure evidence", - native_failure_projection_error(leaking_projection), - ) - - def portable_signals_query_result(self) -> tuple[dict[str, object], dict[str, object]]: - runner = self.contract["experiments"]["signals-queries"]["runners"][0] - native = successful_native_result( - self.plan, - runner["required_distributions"], - required_artifact_versions=runner_required_artifact_versions(runner), - ) - native.update( - { - "schema": runner["result_schema"], - "started_at": "2026-07-19T00:00:00Z", - "finished_at": "2026-07-19T00:01:00Z", - "runner_blocked": False, - "artifactVersions": native.pop("artifact_versions"), - "runtime_matrix": {}, - "scenario_results": { - scenario: {"scenario_id": scenario, "status": "pass"} for scenario in runner["required_scenarios"] - }, - "finding_links": {}, - } - ) - return native, runner - - def test_every_declared_portable_field_is_required(self) -> None: - native, runner = self.portable_signals_query_result() - required_distributions = runner["required_distributions"] - self.assertEqual("", native_result_completeness_error(native, required_distributions, runner)) - - for field in runner["required_result_fields"]: - with self.subTest(field=field): - incomplete = dict(native) - incomplete.pop(field) - self.assertIn( - field, - native_result_completeness_error(incomplete, required_distributions, runner), - ) - - def test_malformed_portable_identity_bodies_are_incomplete_evidence(self) -> None: - native, runner = self.portable_signals_query_result() - required_distributions = runner["required_distributions"] - malformed_identities = ( - None, - {"kind": "pypi", "locator": "pypi:durable-workflow@1.2.0"}, - {"kind": "pypi", "locator": "pypi:durable-workflow@1.2.0", "artifacts": []}, - { - "kind": "pypi", - "locator": "not-a-distribution-locator", - "artifacts": [{"name": "package.whl", "sha256": "a" * 64}], - }, - { - "kind": "pypi", - "locator": "pypi:durable-workflow/sdk-python@1.2.0", - "artifacts": [{"name": "package.whl", "sha256": "a" * 64}], - }, - { - "kind": "pypi", - "locator": "pypi:durable-workflow@not-a-version", - "artifacts": [{"name": "package.whl", "sha256": "a" * 64}], - }, - { - "kind": "composer", - "locator": "composer:durable-workflow/sdk-python@1.2.0", - "artifacts": [{"name": "package.whl", "sha256": "a" * 64}], - }, - { - "kind": "pypi", - "locator": "pypi:durable-workflow@1.2.0", - "artifacts": [{"name": "package.whl", "sha256": "not-a-digest"}], - }, - ) - - for identity in malformed_identities: - with self.subTest(identity=identity): - malformed = json.loads(canonical_json(native)) - malformed["executed_distribution_identities"]["sdk-python"] = identity - self.assertIn( - "malformed sdk-python distribution", - native_result_completeness_error(malformed, required_distributions, runner), - ) - - def test_passing_outcome_cannot_hide_a_non_passing_required_scenario(self) -> None: - native, runner = self.portable_signals_query_result() - required_distributions = runner["required_distributions"] - native["scenario_results"][runner["required_scenarios"][0]]["status"] = "fail" - - self.assertIn( - "passing outcome with non-passing required scenarios", - native_result_completeness_error(native, required_distributions, runner), - ) - - def test_classified_infrastructure_retry_can_recover_the_experiment(self) -> None: - attempts = 0 - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - nonlocal attempts - attempts += 1 - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - if attempts == 1: - stderr_path.write_text( - "registry returned 503 Service Unavailable during pull", - encoding="utf-8", - ) - return 75, False - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(self.native_result("pass"))) - return 0, False - - with ( - mock.patch("scripts.beta_conformance.execute_command", side_effect=execute), - mock.patch("scripts.beta_conformance.time.sleep"), - ): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - self.assertEqual(2, attempts) - self.assertEqual("pass", result["outcome"]) - self.assertEqual("passed", result["classification"]) - self.assertIsNone(result["failure_fingerprint"]) - self.assertEqual(2, result["retry"]["attempts"]) - self.assertEqual([1, 2], [diagnostic["attempt"] for diagnostic in result["diagnostics"]]) - - def test_declared_runtime_environment_reaches_the_published_runner(self) -> None: - self.runner["runtime"] = { - "cache_backend": "redis", - "database_backend": "mysql", - "kind": "standalone-server", - "namespace_environment": "DW_PHP_SDK_CONFORMANCE_NAMESPACE", - "network_scope": "private", - "queue_backend": "redis", - "server_url_environment": "DW_PHP_SDK_CONFORMANCE_SERVER_URL", - "token_environment": "DW_PHP_SDK_CONFORMANCE_TOKEN", - } - self.plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - - @contextlib.contextmanager - def runtime(*arguments: object, **options: object): - yield { - "DW_PHP_SDK_CONFORMANCE_NAMESPACE": "default", - "DW_PHP_SDK_CONFORMANCE_SERVER_URL": "http://127.0.0.1:49152", - "DW_PHP_SDK_CONFORMANCE_TOKEN": "beta-token", - } - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - environment = arguments["environment"] - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(environment, dict) - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - self.assertEqual("http://127.0.0.1:49152", environment["DW_PHP_SDK_CONFORMANCE_SERVER_URL"]) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(self.native_result("pass"))) - return 0, False - - with ( - mock.patch("scripts.beta_conformance.runner_runtime_environment", side_effect=runtime), - mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command, - ): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("pass", result["outcome"]) - - def test_unavailable_declared_runtime_is_retained_without_unclassified_retry(self) -> None: - self.runner["runtime"] = { - "cache_backend": "redis", - "database_backend": "mysql", - "kind": "standalone-server", - "namespace_environment": "DW_PHP_SDK_CONFORMANCE_NAMESPACE", - "network_scope": "private", - "queue_backend": "redis", - "server_url_environment": "DW_PHP_SDK_CONFORMANCE_SERVER_URL", - "token_environment": "DW_PHP_SDK_CONFORMANCE_TOKEN", - } - self.plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - - with ( - mock.patch( - "scripts.beta_conformance.runner_runtime_environment", - side_effect=ConformanceError("exact candidate standalone server did not become ready"), - ), - mock.patch("scripts.beta_conformance.execute_command") as execute_command, - ): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_not_called() - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertTrue(result["diagnostics"][0]["runner_blocked"]) - self.assertEqual("declared_runtime_unavailable", result["diagnostics"][0]["findings"][0]["type"]) - - def test_oversized_native_result_is_not_retried_with_transient_diagnostics(self) -> None: - oversized_size = 1 << 40 - native_path = self.result_dir / "native" / self.runner["id"] / self.runner["result"] - - def bounded_digest(path: Path) -> str: - if path == native_path: - raise AssertionError("oversized native result must not be hashed in full") - return sha256_file(path) - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("tls handshake timeout", encoding="utf-8") - native_dir = Path(command[-1]) - with (native_dir / self.runner["result"]).open("wb") as handle: - handle.write(b"{" + b"x" * (NATIVE_RESULT_PREFIX_LIMIT - 1)) - handle.truncate(oversized_size) - return 0, False - - with ( - mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command, - mock.patch( - "scripts.beta_conformance.sha256_file", - side_effect=bounded_digest, - ), - ): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - diagnostic = result["diagnostics"][0] - self.assertTrue(diagnostic["runner_blocked"]) - self.assertEqual(oversized_size, diagnostic["native_result_size_bytes"]) - self.assertIsNone(diagnostic["native_result_sha256"]) - self.assertEqual(NATIVE_RESULT_PREFIX_LIMIT, diagnostic["native_result_prefix_bytes"]) - self.assertEqual( - sha256_bytes(b"{" + b"x" * (NATIVE_RESULT_PREFIX_LIMIT - 1)), - diagnostic["native_result_prefix_sha256"], - ) - self.assertEqual("native_result_unreadable", diagnostic["findings"][0]["type"]) - validate_experiment_result(result, self.plan, self.contract) - self.assertNotIn(str(self.result_dir), diagnostic["stderr_tail"]) - - def test_oversized_native_prefix_read_failure_retains_unreadable_evidence(self) -> None: - oversized_size = 1 << 40 - native_path = self.result_dir / "native" / self.runner["id"] / self.runner["result"] - original_open = Path.open - - class PrefixReadFailure: - def __enter__(self) -> PrefixReadFailure: - self.handle = original_open(native_path, "rb") - return self - - def __exit__(self, *arguments: object) -> None: - self.handle.close() - - def fileno(self) -> int: - return self.handle.fileno() - - def read(self, size: int = -1) -> bytes: - raise OSError("simulated native evidence read failure") - - def open_with_prefix_failure(path: Path, *arguments: object, **options: object): - if path == native_path and arguments == ("rb",): - return PrefixReadFailure() - return original_open(path, *arguments, **options) - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - with (native_dir / self.runner["result"]).open("wb") as handle: - handle.truncate(oversized_size) - return 0, False - - with ( - mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command, - mock.patch.object(Path, "open", open_with_prefix_failure), - ): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - retained = json.loads((self.result_dir / "experiment-result.json").read_bytes()) - self.assertEqual(result, retained) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertEqual(1, len(result["diagnostics"])) - diagnostic = result["diagnostics"][0] - self.assertTrue(diagnostic["runner_blocked"]) - self.assertEqual(oversized_size, diagnostic["native_result_size_bytes"]) - self.assertIsNone(diagnostic["native_result_sha256"]) - self.assertIsNone(diagnostic["native_result_prefix_sha256"]) - self.assertIsNone(diagnostic["native_result_prefix_bytes"]) - self.assertIsNone(diagnostic["native_summary"]) - - def test_malformed_native_result_is_not_retried_with_transient_diagnostics(self) -> None: - malformed = b'{"outcome":' - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text( - "registry returned 503 Service Unavailable during pull", - encoding="utf-8", - ) - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(malformed) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - diagnostic = result["diagnostics"][0] - self.assertEqual(len(malformed), diagnostic["native_result_size_bytes"]) - self.assertEqual(sha256_bytes(malformed), diagnostic["native_result_sha256"]) - self.assertIsNone(diagnostic["native_result_prefix_sha256"]) - - def test_incomplete_native_result_is_not_retried_with_transient_diagnostics(self) -> None: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("tls handshake timeout", encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes( - canonical_json({"schema": "fixture.result/v1", "outcome": "pass"}) - ) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - diagnostic = result["diagnostics"][0] - self.assertEqual("pass", diagnostic["native_outcome"]) - self.assertTrue(diagnostic["runner_blocked"]) - self.assertEqual("native_result_unreadable", diagnostic["findings"][0]["type"]) - - def test_malformed_identity_is_runner_infrastructure_not_product_failure(self) -> None: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native = self.native_result("pass") - native["executed_distribution_identities"]["sdk-python"] = { - "kind": "pypi", - "locator": "pypi:durable-workflow@1.2.0", - } - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(native)) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - diagnostic = result["diagnostics"][0] - self.assertEqual("pass", diagnostic["native_outcome"]) - self.assertTrue(diagnostic["runner_blocked"]) - self.assertEqual("native_result_unreadable", diagnostic["findings"][0]["type"]) - self.assertNotIn("artifact-binding", [item["runner"] for item in result["diagnostics"]]) - - def test_malformed_identity_locator_is_runner_infrastructure_not_product_failure(self) -> None: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native = self.native_result("pass") - native["executed_distribution_identities"]["sdk-python"]["locator"] = "not-a-distribution-locator" - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(native)) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - diagnostic = result["diagnostics"][0] - self.assertEqual("pass", diagnostic["native_outcome"]) - self.assertTrue(diagnostic["runner_blocked"]) - self.assertIn("malformed sdk-python distribution identity locator", diagnostic["stderr_tail"]) - self.assertEqual("native_result_unreadable", diagnostic["findings"][0]["type"]) - self.assertNotIn("artifact-binding", [item["runner"] for item in result["diagnostics"]]) - - def test_accepted_native_result_retains_exact_full_identity(self) -> None: - native_bytes = json.dumps(self.native_result("pass"), indent=2).encode() + b"\n" - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(native_bytes) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - self.assertEqual("passed", result["classification"]) - diagnostic = result["diagnostics"][0] - self.assertEqual(len(native_bytes), diagnostic["native_result_size_bytes"]) - self.assertEqual(sha256_bytes(native_bytes), diagnostic["native_result_sha256"]) - self.assertIsNone(diagnostic["native_result_prefix_sha256"]) - - def test_missing_waterline_service_visibility_is_one_attempt_runner_infrastructure_failure(self) -> None: - native, runner = self.portable_signals_query_result() - missing_scenario = "waterline_service_operator_visibility" - native["scenario_results"].pop(missing_scenario) - runner_path = self.artifact_root / runner["path"] - runner_path.parent.mkdir(parents=True, exist_ok=True) - runner_path.write_text("#!/usr/bin/env bash\n", encoding="utf-8") - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / runner["result"]).write_bytes(canonical_json(native)) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "signals-queries", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - diagnostic = result["diagnostics"][0] - self.assertTrue(diagnostic["runner_blocked"]) - self.assertIn(missing_scenario, diagnostic["stderr_tail"]) - self.assertEqual("native_result_unreadable", diagnostic["findings"][0]["type"]) - validate_experiment_result(result, self.plan, self.contract) - - def test_missing_native_result_is_runner_infrastructure_not_product_failure(self) -> None: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertTrue(result["diagnostics"][0]["runner_blocked"]) - self.assertEqual("native_result_unreadable", result["diagnostics"][0]["findings"][0]["type"]) - - def test_semantic_failure_is_not_retried_by_the_experiment_runner(self) -> None: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("HTTP 503 appeared in a product assertion", encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(self.native_result("fail"))) - return 1, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - self.assertEqual(1, execute_command.call_count) - self.assertEqual("fail", result["outcome"]) - self.assertEqual("product_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - validate_experiment_result(result, self.plan, self.contract) - - def test_passing_native_result_with_same_version_and_different_digest_stays_red(self) -> None: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native = self.native_result("pass") - native["executed_distribution_identities"]["sdk-python"]["artifacts"][0]["sha256"] = "f" * 64 - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(native)) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("product_failure", result["classification"]) - binding = result["diagnostics"][-1] - self.assertEqual("artifact-binding", binding["runner"]) - self.assertEqual("deterministic-replay", binding["findings"][0]["owning_contract"]) - self.assertIn("sdk-python executed distribution artifact", binding["findings"][0]["summary"]) - - def test_passing_native_result_with_different_valid_locator_stays_red(self) -> None: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native = self.native_result("pass") - native["executed_distribution_identities"]["sdk-python"]["locator"] = "pypi:durable-workflow@9.9.9" - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(native)) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command: - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - ) - - execute_command.assert_called_once() - self.assertEqual("product_failure", result["classification"]) - binding = result["diagnostics"][-1] - self.assertEqual("artifact-binding", binding["runner"]) - self.assertEqual( - "pypi:durable-workflow@9.9.9", - result["diagnostics"][0]["native_summary"]["executed_distribution_identities"]["sdk-python"]["locator"], - ) - self.assertIn("different distribution locator", binding["findings"][0]["summary"]) - - def test_injected_identity_failure_exercises_binding_and_is_not_retried(self) -> None: - native_path = self.result_dir / "native" / self.runner["id"] / self.runner["result"] - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - (native_dir / self.runner["result"]).write_bytes(canonical_json(self.native_result("pass"))) - return 0, False - - def write_distinct_native(path: Path, value: object) -> None: - if path == native_path: - path.write_bytes(json.dumps(value, separators=(",", ":")).encode() + b"\n") - else: - write_json(path, value) - - with ( - mock.patch("scripts.beta_conformance.execute_command", side_effect=execute) as execute_command, - mock.patch("scripts.beta_conformance.write_json", side_effect=write_distinct_native), - ): - result = run_experiment( - self.plan, - self.contract, - "replay", - self.artifact_root, - self.result_dir, - inject_identity_failure=True, - ) - - execute_command.assert_called_once() - self.assertEqual("product_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertFalse(result["retry"]["semantic_failures_retryable"]) - native = result["diagnostics"][0] - self.assertEqual( - "injected_distribution_identity_mismatch", - native["findings"][0]["type"], - ) - self.assertEqual( - self.plan["artifact_tuple"]["cli"]["version"], - native["native_summary"]["artifact_versions"]["cli"], - ) - literal_native = native_path.read_bytes() - self.assertEqual(len(literal_native), native["native_result_size_bytes"]) - self.assertEqual(sha256_bytes(literal_native), native["native_result_sha256"]) - binding = result["diagnostics"][-1] - self.assertEqual("artifact-binding", binding["runner"]) - self.assertIn("does not match the candidate digest", binding["findings"][0]["summary"]) - - -class MultiRunnerExperimentTest(unittest.TestCase): - def setUp(self) -> None: - self.fixture = CandidateRecordFixture() - self.contract = load_contract(CONTRACT_PATH) - self.plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - self.temporary = tempfile.TemporaryDirectory() - self.root = Path(self.temporary.name) - self.artifact_root = self.root / "published-server" - self.result_dir = self.root / "result" - self.specification = self.contract["experiments"]["heartbeats"] - for runner in self.specification["runners"]: - runner_path = self.artifact_root / runner["path"] - runner_path.parent.mkdir(parents=True, exist_ok=True) - runner_path.write_text("#!/usr/bin/env bash\n", encoding="utf-8") - - def tearDown(self) -> None: - self.temporary.cleanup() - self.fixture.close() - - def execute_shards( - self, - mutate: Callable[[str, dict[str, object]], None] | None = None, - ) -> tuple[list[str], dict[str, Any]]: - executed: list[str] = [] - runners = {runner["id"]: runner for runner in self.specification["runners"]} - - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - runner_id = native_dir.name - runner = runners[runner_id] - native = successful_native_result(self.plan, runner["required_distributions"]) - if callable(mutate): - mutate(runner_id, native) - (native_dir / runner["result"]).write_bytes(canonical_json(native)) - executed.append(runner_id) - return 0, False - - with mock.patch("scripts.beta_conformance.execute_command", side_effect=execute): - result = run_experiment( - self.plan, - self.contract, - "heartbeats", - self.artifact_root, - self.result_dir, - ) - return executed, result - - def test_polyglot_python_shard_is_complete_without_the_peer_php_identity(self) -> None: - specification = self.contract["experiments"]["polyglot"] - runner = specification["runners"][0] - native = successful_native_result(self.plan, runner["required_distributions"]) - - self.assertEqual( - "", - native_result_completeness_error( - native, - runner["required_distributions"], - runner, - ), - ) - self.assertIn( - "every required artifact version", - native_result_completeness_error( - native, - specification["required_distributions"], - runner, - ), - ) - - def test_declared_runtime_version_is_separate_from_executed_identity_assignment(self) -> None: - specification = self.contract["experiments"]["polyglot"] - runner = next(item for item in specification["runners"] if item["id"] == "php-sdk") - native = successful_native_result( - self.plan, - runner["required_distributions"], - required_artifact_versions=runner_required_artifact_versions(runner), - ) - - self.assertEqual({"sdk-php", "server"}, set(native["artifact_versions"])) - self.assertEqual({"sdk-php"}, set(native["executed_distribution_identities"])) - self.assertEqual( - "", - native_result_completeness_error( - native, - runner["required_distributions"], - runner, - ), - ) - - missing_runtime_version = json.loads(canonical_json(native)) - missing_runtime_version["artifact_versions"].pop("server") - self.assertIn( - "every required artifact version", - native_result_completeness_error( - missing_runtime_version, - runner["required_distributions"], - runner, - ), - ) - - peer_version = json.loads(canonical_json(native)) - peer_version["artifact_versions"]["sdk-python"] = self.plan["artifact_tuple"]["sdk-python"]["version"] - self.assertIn( - "outside its required distributions: sdk-python", - native_result_completeness_error( - peer_version, - runner["required_distributions"], - runner, - ), - ) - - runtime_identity = json.loads(canonical_json(native)) - runtime_identity["executed_distribution_identities"]["server"] = json.loads( - canonical_json(self.plan["distribution_identities"]["server"]) - ) - self.assertIn( - "distribution identities outside its required distributions: server", - native_result_completeness_error( - runtime_identity, - runner["required_distributions"], - runner, - ), - ) - - def test_valid_partial_php_python_and_rust_shards_form_a_passing_aggregate(self) -> None: - executed, result = self.execute_shards() - - self.assertEqual(["php", "python", "rust"], executed) - self.assertEqual("pass", result["outcome"]) - self.assertEqual("passed", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertEqual( - [set(runner["required_distributions"]) for runner in self.specification["runners"]], - [ - set(diagnostic["native_summary"]["executed_distribution_identities"]) - for diagnostic in result["diagnostics"] - ], - ) - - def test_exact_peer_only_claim_fails_closed_at_the_reporting_shard(self) -> None: - def add_python_claim(runner_id: str, native: dict[str, object]) -> None: - if runner_id == "php": - versions = native["artifact_versions"] - identities = native["executed_distribution_identities"] - artifact_tuple = self.plan["artifact_tuple"] - distribution_identities = self.plan["distribution_identities"] - assert isinstance(versions, dict) - assert isinstance(identities, dict) - assert isinstance(artifact_tuple, dict) - assert isinstance(distribution_identities, dict) - versions["sdk-python"] = artifact_tuple["sdk-python"]["version"] - identities["sdk-python"] = json.loads(canonical_json(distribution_identities["sdk-python"])) - - executed, result = self.execute_shards(add_python_claim) - - self.assertEqual(["php"], executed) - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertTrue(result["diagnostics"][0]["runner_blocked"]) - self.assertIn( - "artifact versions outside its required distributions: sdk-python", - result["diagnostics"][0]["stderr_tail"], - ) - with self.assertRaisesRegex(ConformanceError, "outside its exact assignment"): - validate_experiment_result(result, self.plan, self.contract) - - def test_exact_peer_only_identity_is_rejected_without_an_extra_version(self) -> None: - runner = self.specification["runners"][0] - native = successful_native_result(self.plan, runner["required_distributions"]) - distribution_identities = self.plan["distribution_identities"] - assert isinstance(distribution_identities, dict) - identities = native["executed_distribution_identities"] - assert isinstance(identities, dict) - identities["sdk-python"] = json.loads(canonical_json(distribution_identities["sdk-python"])) - - self.assertIn( - "distribution identities outside its required distributions: sdk-python", - native_result_completeness_error( - native, - runner["required_distributions"], - runner, - ), - ) - - def test_missing_consumed_identity_fails_closed_after_one_attempt(self) -> None: - def remove_php_identity(runner_id: str, native: dict[str, object]) -> None: - if runner_id == "php": - identities = native["executed_distribution_identities"] - assert isinstance(identities, dict) - identities.pop("sdk-php") - - executed, result = self.execute_shards(remove_php_identity) - - self.assertEqual(["php"], executed) - self.assertEqual("fail", result["outcome"]) - self.assertEqual("infrastructure_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertTrue(result["diagnostics"][0]["runner_blocked"]) - self.assertIn("every required distribution identity", result["diagnostics"][0]["stderr_tail"]) - validate_experiment_result(result, self.plan, self.contract) - - def test_mismatched_consumed_identity_runs_all_shards_then_fails_the_aggregate(self) -> None: - def mismatch_php_identity(runner_id: str, native: dict[str, object]) -> None: - if runner_id == "php": - identities = native["executed_distribution_identities"] - assert isinstance(identities, dict) - artifacts = identities["sdk-php"]["artifacts"] - artifacts[0]["sha256"] = "f" * 64 - - executed, result = self.execute_shards(mismatch_php_identity) - - self.assertEqual(["php", "python", "rust"], executed) - self.assertEqual("fail", result["outcome"]) - self.assertEqual("product_failure", result["classification"]) - self.assertEqual(1, result["retry"]["attempts"]) - self.assertEqual("artifact-binding", result["diagnostics"][-1]["runner"]) - self.assertIn("does not match the candidate digest", result["diagnostics"][-1]["stderr_tail"]) - validate_experiment_result(result, self.plan, self.contract) - - -class PolyglotShardAssignmentRegressionTest(unittest.TestCase): - def setUp(self) -> None: - self.retained = json.loads(POLYGLOT_PHP_IDENTITY_FIXTURE.read_bytes()) - native = self.retained["native_result"] - self.fixture = CandidateRecordFixture(native["artifact_versions"]) - self.contract = load_contract(CONTRACT_PATH) - self.plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - self.temporary = tempfile.TemporaryDirectory() - self.root = Path(self.temporary.name) - self.artifact_root = self.root / "published-server" - self.result_dir = self.root / "result" - self.specification = self.contract["experiments"]["polyglot"] - self.runners = {runner["id"]: runner for runner in self.specification["runners"]} - for runner in self.specification["runners"]: - runner_path = self.artifact_root / runner["path"] - runner_path.parent.mkdir(parents=True, exist_ok=True) - runner_path.write_text("#!/usr/bin/env bash\n", encoding="utf-8") - - def tearDown(self) -> None: - self.temporary.cleanup() - self.fixture.close() - - def execute_polyglot(self) -> dict[str, Any]: - def execute(command: list[str], **arguments: object) -> tuple[int, bool]: - stdout_path = arguments["stdout_path"] - stderr_path = arguments["stderr_path"] - assert isinstance(stdout_path, Path) - assert isinstance(stderr_path, Path) - stdout_path.write_text("", encoding="utf-8") - stderr_path.write_text("", encoding="utf-8") - native_dir = Path(command[-1]) - runner = self.runners[native_dir.name] - if runner["id"] == self.retained["runner"]: - native = self.retained["native_result"] - else: - native = successful_native_result( - self.plan, - runner["required_distributions"], - required_artifact_versions=runner_required_artifact_versions(runner), - ) - (native_dir / runner["result"]).write_bytes(canonical_json(native)) - return 0, False - - with ( - mock.patch("scripts.beta_conformance.execute_command", side_effect=execute), - mock.patch( - "scripts.beta_conformance.runner_runtime_environment", - side_effect=lambda *_arguments: contextlib.nullcontext({}), - ), - ): - return run_experiment( - self.plan, - self.contract, - "polyglot", - self.artifact_root, - self.result_dir, - ) - - def test_retained_activities_result_reproduces_the_historical_php_assignment_rejection(self) -> None: - self.assertEqual("rc-current-2-0-20260801", self.retained["candidate"]) - self.assertEqual( - "bdd94127104739178d2048007f8dcd93e84cfd915c81e49a585be0aacd4b82af", - self.retained["historical_rejection"]["failure_fingerprint"], - ) - runner = self.runners[self.retained["runner"]] - historical_runner = json.loads(canonical_json(runner)) - historical_runner["required_distributions"] = [ - distribution for distribution in historical_runner["required_distributions"] if distribution != "sdk-php" - ] - - self.assertEqual( - self.retained["historical_rejection"]["reason"], - native_result_completeness_error( - self.retained["native_result"], - historical_runner["required_distributions"], - historical_runner, - ), - ) - - def test_retained_activities_result_passes_with_all_six_exact_distribution_identities(self) -> None: - result = self.execute_polyglot() - - self.assertEqual("pass", result["outcome"]) - self.assertEqual("passed", result["classification"]) - activities = next( - diagnostic["native_summary"] - for diagnostic in result["diagnostics"] - if diagnostic["runner"] == self.retained["runner"] - ) - self.assertEqual( - {"workflow", "waterline", "server", "cli", "sdk-python", "sdk-php"}, - set(activities["executed_distribution_identities"]), - ) - - def test_unassigned_distribution_claim_in_the_retained_shard_still_fails_closed(self) -> None: - native = json.loads(canonical_json(self.retained["native_result"])) - native["artifact_versions"]["sdk-rust"] = self.plan["artifact_tuple"]["sdk-rust"]["version"] - native["executed_distribution_identities"]["sdk-rust"] = json.loads( - canonical_json(self.plan["distribution_identities"]["sdk-rust"]) - ) - runner = self.runners[self.retained["runner"]] - - self.assertIn( - "artifact versions outside its required distributions: sdk-rust", - native_result_completeness_error(native, runner["required_distributions"], runner), - ) - - def test_another_shards_php_identity_cannot_mask_a_missing_activities_identity(self) -> None: - result = self.execute_polyglot() - activities = next( - diagnostic["native_summary"] - for diagnostic in result["diagnostics"] - if diagnostic["runner"] == self.retained["runner"] - ) - activities["artifact_versions"].pop("sdk-php") - activities["executed_distribution_identities"].pop("sdk-php") - - with self.assertRaisesRegex(ConformanceError, "does not retain its exact distribution assignment"): - validate_experiment_result(result, self.plan, self.contract) - - -class EvidenceTest(unittest.TestCase): - def setUp(self) -> None: - self.fixture = CandidateRecordFixture() - self.contract = load_contract(CONTRACT_PATH) - self.plan = prepare_plan( - self.fixture.repository, - self.fixture.manifest, - self.contract, - self.fixture.commit, - runtime_dependencies(), - ) - - def tearDown(self) -> None: - self.fixture.close() - - def test_diagnostic_text_is_bounded(self) -> None: - value = bounded_text("x" * 9000, 8192) - self.assertEqual(8192, len(value)) - self.assertTrue(value.endswith("…")) - - def native_identity_results(self) -> tuple[dict[str, object], dict[str, object], dict[str, object]]: - specification = self.contract["experiments"]["replay"] - complete = experiment_result( - self.plan, - "replay", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-19T00:00:00Z", - "passed", - 1, - [successful_diagnostic(self.plan, specification["required_distributions"])], - ) - unreadable_diagnostic = successful_diagnostic(self.plan, specification["required_distributions"]) - unreadable_diagnostic.update( - { - "exit_code": 1, - "native_outcome": None, - "runner_blocked": True, - "native_result_size_bytes": 512, - "native_result_sha256": None, - "native_summary": None, - } - ) - unreadable = experiment_result( - self.plan, - "replay", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-19T00:00:00Z", - "infrastructure_failure", - 1, - [unreadable_diagnostic], - ) - oversized_unreadable = json.loads(canonical_json(unreadable)) - oversized_unreadable["diagnostics"][0]["native_result_size_bytes"] = NATIVE_RESULT_LIMIT + 1 - return complete, unreadable, oversized_unreadable - - def test_result_validator_accepts_complete_and_unreadable_native_identity_shapes(self) -> None: - complete, unreadable, oversized_unreadable = self.native_identity_results() - - validate_experiment_result(complete, self.plan) - validate_experiment_result(unreadable, self.plan) - validate_experiment_result(oversized_unreadable, self.plan) - - def test_result_schema_accepts_complete_and_unreadable_native_identity_shapes(self) -> None: - validator = beta_schema_validator("result-schema.json") - complete, unreadable, oversized_unreadable = self.native_identity_results() - - validator.validate(complete) - validator.validate(unreadable) - validator.validate(oversized_unreadable) - - def test_parsed_native_summary_requires_a_complete_identity(self) -> None: - validator = beta_schema_validator("result-schema.json") - complete, unreadable, oversized_unreadable = self.native_identity_results() - complete["diagnostics"][0]["native_result_sha256"] = None - - with self.assertRaisesRegex(ConformanceError, "parsed native results"): - validate_experiment_result(complete, self.plan) - with self.assertRaises(ValidationError): - validator.validate(complete) - - for invalid_unreadable in (unreadable, oversized_unreadable): - with self.subTest(size=invalid_unreadable["diagnostics"][0]["native_result_size_bytes"]): - invalid_unreadable["diagnostics"][0]["runner_blocked"] = False - with self.assertRaisesRegex(ConformanceError, "unreadable infrastructure evidence"): - validate_experiment_result(invalid_unreadable, self.plan) - with self.assertRaises(ValidationError): - validator.validate(invalid_unreadable) - - def test_result_validator_requires_exclusive_native_identity_fields(self) -> None: - specification = self.contract["experiments"]["replay"] - result = injected_failure_result( - self.plan, - "replay", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-19T00:00:00Z", - ) - del result["diagnostics"][0]["native_result_size_bytes"] - with self.assertRaisesRegex(ConformanceError, "native result identity shape"): - validate_experiment_result(result, self.plan) - - result = injected_failure_result( - self.plan, - "replay", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-19T00:00:00Z", - ) - diagnostic = result["diagnostics"][0] - diagnostic["native_result_size_bytes"] = NATIVE_RESULT_LIMIT + 1 - diagnostic["native_result_sha256"] = "a" * 64 - diagnostic["native_result_prefix_sha256"] = "b" * 64 - diagnostic["native_result_prefix_bytes"] = NATIVE_RESULT_PREFIX_LIMIT - with self.assertRaisesRegex(ConformanceError, "complete native identities"): - validate_experiment_result(result, self.plan) - - def test_aggregate_binds_each_result_and_preserves_red_product_failure(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - for experiment in EXPERIMENTS: - owner = self.contract["experiments"][experiment]["owning_contract"] - clients = self.contract["experiments"][experiment]["required_clients"] - distributions = self.contract["experiments"][experiment]["required_distributions"] - if experiment == "signals-queries": - result = injected_failure_result( - self.plan, - experiment, - owner, - clients, - distributions, - "2026-07-17T00:00:00Z", - ) - else: - specification = self.contract["experiments"][experiment] - result = experiment_result( - self.plan, - experiment, - owner, - clients, - distributions, - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - path = root / experiment / "experiment-result.json" - path.parent.mkdir() - path.write_bytes(canonical_json(result)) - - suite, retained = aggregate_results( - self.plan, - self.contract, - root, - run_id=12345, - run_attempt=2, - source_candidate=self.plan["candidate"]["name"], - source_head_sha=self.plan["runner"]["revision"], - generated_at="2026-07-20T20:20:06Z", - ) - - self.assertEqual("fail", suite["outcome"]) - self.assertEqual(set(EXPERIMENTS), set(retained)) - self.assertEqual("product_failure", suite["experiments"]["signals-queries"]["classification"]) - self.assertEqual("2026-07-20T20:20:06Z", suite["generated_at"]) - self.assertEqual( - "beta-conformance/portable-beta-test/12345.2", - suite["github_run"]["evidence_tag"], - ) - - def test_aggregate_records_a_missing_matrix_result_as_infrastructure_failure(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - suite, retained = aggregate_results( - self.plan, - self.contract, - Path(temporary), - run_id=12345, - run_attempt=1, - source_candidate=self.plan["candidate"]["name"], - source_head_sha=self.plan["runner"]["revision"], - ) - self.assertEqual({}, retained) - self.assertEqual("fail", suite["outcome"]) - for experiment in EXPERIMENTS: - self.assertEqual("infrastructure_failure", suite["experiments"][experiment]["classification"]) - self.assertRegex(suite["experiments"][experiment]["failure_fingerprint"], r"^[0-9a-f]{64}$") - - def test_aggregate_rejects_a_plan_from_another_source_run(self) -> None: - cases = { - "candidate": { - "source_candidate": "another-candidate", - "source_head_sha": self.plan["runner"]["revision"], - }, - "commit": { - "source_candidate": self.plan["candidate"]["name"], - "source_head_sha": "f" * 40, - }, - } - for name, source in cases.items(): - with ( - self.subTest(name=name), - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex( - ConformanceError, - "execution plan does not bind the source workflow", - ), - ): - aggregate_results( - self.plan, - self.contract, - Path(temporary), - run_id=12345, - run_attempt=1, - **source, - ) - - def test_green_suite_retains_executed_identities_for_all_required_distributions(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - for experiment in EXPERIMENTS: - specification = self.contract["experiments"][experiment] - result = experiment_result( - self.plan, - experiment, - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - path = root / experiment / "experiment-result.json" - path.parent.mkdir() - path.write_bytes(canonical_json(result)) - - suite, _ = aggregate_results( - self.plan, - self.contract, - root, - run_id=12345, - run_attempt=1, - source_candidate=self.plan["candidate"]["name"], - source_head_sha=self.plan["runner"]["revision"], - ) - - self.assertEqual("pass", suite["outcome"]) - self.assertEqual(set(DISTRIBUTIONS), set(suite["executed_distribution_identities"])) - self.assertEqual(self.plan["runtime_dependencies"], suite["runtime_dependencies"]) - beta_schema_validator("suite-result-schema.json").validate(suite) - - def test_green_suite_retains_registry_native_python_and_waterline_service_identities(self) -> None: - fixture = CandidateRecordFixture({"sdk-python": "2.0.0-beta.10"}) - try: - contract = load_contract(CONTRACT_PATH) - plan = prepare_plan( - fixture.repository, - fixture.manifest, - contract, - fixture.commit, - runtime_dependencies(), - ) - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - for experiment in EXPERIMENTS: - specification = contract["experiments"][experiment] - result = experiment_result( - plan, - experiment, - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-24T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(plan, specification), - ) - for diagnostic in result["diagnostics"]: - summary = diagnostic["native_summary"] - if "sdk-python" in summary["artifact_versions"]: - summary["artifact_versions"]["sdk-python"] = "2.0.0b10" - if "sdk-python" in summary["executed_distribution_identities"]: - summary["executed_distribution_identities"]["sdk-python"]["locator"] = ( - "pypi:durable-workflow@2.0.0b10" - ) - path = root / experiment / "experiment-result.json" - path.parent.mkdir() - path.write_bytes(canonical_json(result)) - - suite, _ = aggregate_results( - plan, - contract, - root, - run_id=12345, - run_attempt=1, - source_candidate=plan["candidate"]["name"], - source_head_sha=plan["runner"]["revision"], - ) - - self.assertEqual("pass", suite["outcome"]) - self.assertEqual(set(DISTRIBUTIONS), set(suite["executed_distribution_identities"])) - self.assertEqual( - "pypi:durable-workflow@2.0.0b10", - suite["executed_distribution_identities"]["sdk-python"]["locator"], - ) - self.assertEqual( - plan["distribution_identities"]["waterline-service"]["artifacts"], - suite["executed_distribution_identities"]["waterline-service"]["artifacts"], - ) - beta_schema_validator("suite-result-schema.json").validate(suite) - finally: - fixture.close() - - def test_aggregate_rejects_a_passing_result_missing_a_declared_runner(self) -> None: - specification = self.contract["experiments"]["polyglot"] - result = experiment_result( - self.plan, - "polyglot", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - result["diagnostics"].pop() - - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "polyglot" / "experiment-result.json" - path.parent.mkdir() - path.write_bytes(canonical_json(result)) - with self.assertRaisesRegex(ConformanceError, "every declared runner"): - aggregate_results( - self.plan, - self.contract, - Path(temporary), - run_id=12345, - run_attempt=1, - source_candidate=self.plan["candidate"]["name"], - source_head_sha=self.plan["runner"]["revision"], - ) - - def test_retained_runtime_version_is_bound_without_a_runtime_execution_identity(self) -> None: - specification = self.contract["experiments"]["polyglot"] - result = experiment_result( - self.plan, - "polyglot", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - php_summary = next( - diagnostic["native_summary"] for diagnostic in result["diagnostics"] if diagnostic["runner"] == "php-sdk" - ) - - self.assertEqual({"sdk-php", "server"}, set(php_summary["artifact_versions"])) - self.assertEqual({"sdk-php"}, set(php_summary["executed_distribution_identities"])) - validate_experiment_result(result, self.plan, self.contract) - - missing_runtime_version = json.loads(canonical_json(result)) - next( - diagnostic["native_summary"] - for diagnostic in missing_runtime_version["diagnostics"] - if diagnostic["runner"] == "php-sdk" - )["artifact_versions"].pop("server") - with self.assertRaisesRegex(ConformanceError, "exact distribution assignment"): - validate_experiment_result(missing_runtime_version, self.plan, self.contract) - - mismatched_runtime_version = json.loads(canonical_json(result)) - next( - diagnostic["native_summary"] - for diagnostic in mismatched_runtime_version["diagnostics"] - if diagnostic["runner"] == "php-sdk" - )["artifact_versions"]["server"] = "0.0.0-wrong" - with self.assertRaisesRegex(ConformanceError, "mismatched native artifact evidence"): - validate_experiment_result(mismatched_runtime_version, self.plan, self.contract) - - runtime_identity = json.loads(canonical_json(result)) - next( - diagnostic["native_summary"] - for diagnostic in runtime_identity["diagnostics"] - if diagnostic["runner"] == "php-sdk" - )["executed_distribution_identities"]["server"] = json.loads( - canonical_json(self.plan["distribution_identities"]["server"]) - ) - with self.assertRaisesRegex(ConformanceError, "identities outside its exact assignment"): - validate_experiment_result(runtime_identity, self.plan, self.contract) - - peer_version = json.loads(canonical_json(result)) - next( - diagnostic["native_summary"] - for diagnostic in peer_version["diagnostics"] - if diagnostic["runner"] == "php-sdk" - )["artifact_versions"]["sdk-python"] = self.plan["artifact_tuple"]["sdk-python"]["version"] - with self.assertRaisesRegex(ConformanceError, "versions outside its exact assignment"): - validate_experiment_result(peer_version, self.plan, self.contract) - - def test_aggregate_rejects_an_exact_peer_only_runner_claim(self) -> None: - specification = self.contract["experiments"]["heartbeats"] - result = experiment_result( - self.plan, - "heartbeats", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - php_summary = result["diagnostics"][0]["native_summary"] - php_summary["artifact_versions"]["sdk-python"] = self.plan["artifact_tuple"]["sdk-python"]["version"] - php_summary["executed_distribution_identities"]["sdk-python"] = json.loads( - canonical_json(self.plan["distribution_identities"]["sdk-python"]) - ) - - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "heartbeats" / "experiment-result.json" - path.parent.mkdir() - path.write_bytes(canonical_json(result)) - with self.assertRaisesRegex(ConformanceError, "outside its exact assignment"): - aggregate_results( - self.plan, - self.contract, - Path(temporary), - run_id=12345, - run_attempt=1, - source_candidate=self.plan["candidate"]["name"], - source_head_sha=self.plan["runner"]["revision"], - ) - - def test_aggregate_rejects_a_peer_only_identity_without_an_extra_version(self) -> None: - specification = self.contract["experiments"]["heartbeats"] - result = experiment_result( - self.plan, - "heartbeats", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - php_summary = result["diagnostics"][0]["native_summary"] - php_summary["executed_distribution_identities"]["sdk-python"] = json.loads( - canonical_json(self.plan["distribution_identities"]["sdk-python"]) - ) - - with self.assertRaisesRegex(ConformanceError, "identities outside its exact assignment"): - validate_experiment_result(result, self.plan, self.contract) - - def test_aggregate_rejects_a_duplicate_runner_terminal(self) -> None: - specification = self.contract["experiments"]["heartbeats"] - result = experiment_result( - self.plan, - "heartbeats", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - result["diagnostics"].insert( - 1, - json.loads(canonical_json(result["diagnostics"][0])), - ) - - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "heartbeats" / "experiment-result.json" - path.parent.mkdir() - path.write_bytes(canonical_json(result)) - with self.assertRaisesRegex(ConformanceError, "duplicate passing terminal"): - aggregate_results( - self.plan, - self.contract, - Path(temporary), - run_id=12345, - run_attempt=1, - source_candidate=self.plan["candidate"]["name"], - source_head_sha=self.plan["runner"]["revision"], - ) - - def test_aggregate_rejects_an_unknown_runner(self) -> None: - specification = self.contract["experiments"]["heartbeats"] - result = experiment_result( - self.plan, - "heartbeats", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - result["diagnostics"][1]["runner"] = "undeclared-python" - - with self.assertRaisesRegex(ConformanceError, "unknown runner"): - validate_experiment_result(result, self.plan, self.contract) - - def test_contract_validator_requires_every_declared_scenario_cell(self) -> None: - specification = self.contract["experiments"]["signals-queries"] - result = experiment_result( - self.plan, - "signals-queries", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - result["diagnostics"][0]["native_summary"]["scenario_statuses"].pop() - - with self.assertRaisesRegex(ConformanceError, "declared scenario cells"): - validate_experiment_result(result, self.plan, self.contract) - - def test_aggregate_rejects_incomplete_product_failure_runner_summaries(self) -> None: - specification = self.contract["experiments"]["signals-queries"] - diagnostic = successful_runner_diagnostics(self.plan, specification)[0] - diagnostic.update({"exit_code": 1, "native_outcome": "fail"}) - retained = experiment_result( - self.plan, - "signals-queries", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "product_failure", - 1, - [diagnostic], - ) - adversarial_summaries = ( - ( - "missing-assignment-with-wrong-schema-and-no-scenarios", - { - "artifact_versions": {}, - "executed_distribution_identities": {}, - "schema": "adversarial.result/v1", - "scenario_statuses": [], - }, - "exact distribution assignment", - ), - ("wrong-schema", {"schema": "adversarial.result/v1"}, "declared schema"), - ("missing-scenario-cells", {"scenario_statuses": []}, "declared scenario cells"), - ) - - for label, summary_changes, expected_error in adversarial_summaries: - with self.subTest(label=label), tempfile.TemporaryDirectory() as temporary: - result = json.loads(canonical_json(retained)) - result["diagnostics"][0]["native_summary"].update(summary_changes) - path = Path(temporary) / "signals-queries" / "experiment-result.json" - path.parent.mkdir() - path.write_bytes(canonical_json(result)) - - with self.assertRaisesRegex(ConformanceError, expected_error): - aggregate_results( - self.plan, - self.contract, - Path(temporary), - run_id=12345, - run_attempt=1, - source_candidate=self.plan["candidate"]["name"], - source_head_sha=self.plan["runner"]["revision"], - ) - - def test_contract_validator_accepts_a_transient_then_pass_runner_lifecycle(self) -> None: - specification = self.contract["experiments"]["heartbeats"] - result = experiment_result( - self.plan, - "heartbeats", - specification["owning_contract"], - specification["required_clients"], - specification["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - successful_runner_diagnostics(self.plan, specification), - ) - transient = json.loads(canonical_json(result["diagnostics"][0])) - transient.update( - { - "exit_code": 75, - "native_outcome": None, - "runner_blocked": True, - "stderr_tail": "package download returned 503", - "stderr_sha256": sha256_bytes(b"package download returned 503"), - "native_result_size_bytes": None, - "native_result_sha256": None, - "native_summary": None, - } - ) - result["diagnostics"][0]["attempt"] = 2 - result["diagnostics"].insert(0, transient) - result["retry"]["attempts"] = 2 - - validate_experiment_result(result, self.plan, self.contract) - - def test_result_validator_rejects_a_different_source_tuple(self) -> None: - result = experiment_result( - self.plan, - "replay", - "deterministic-replay", - self.contract["experiments"]["replay"]["required_clients"], - self.contract["experiments"]["replay"]["required_distributions"], - "2026-07-17T00:00:00Z", - "passed", - 1, - [successful_diagnostic(self.plan)], - ) - result["source_identities"] = dict(result["source_identities"]) - result["source_identities"]["sdk-rust"] = "f" * 40 - with self.assertRaisesRegex(RuntimeError, "mismatched source_identities binding"): - validate_experiment_result(result, self.plan) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_beta_continuity.py b/tests/test_beta_continuity.py deleted file mode 100644 index 848598f..0000000 --- a/tests/test_beta_continuity.py +++ /dev/null @@ -1,2252 +0,0 @@ -from __future__ import annotations - -import hashlib -import json -import os -import subprocess -import tempfile -import unittest -from copy import deepcopy -from pathlib import Path -from typing import Any -from unittest.mock import Mock, patch - -from scripts.beta_candidate import COMPONENTS, CandidateError, manifest_digest -from scripts.beta_conformance import ( - DISTRIBUTIONS, - LEGACY_EXPERIMENT_RESULT_SCHEMA, - LEGACY_PLAN_SCHEMA, - LEGACY_SUITE_RESULT_SCHEMA, - NATIVE_FAILURE_COMPONENT_LIMIT, - NATIVE_FAILURE_PROJECTION_LIMIT, - RUNTIME_DEPENDENCY_SELECTORS, - distribution_version, - experiment_result, -) -from scripts.beta_conformance import ( - EXPERIMENTS as CONFORMANCE_EXPERIMENTS, -) -from scripts.beta_conformance import ( - PLAN_SCHEMA as CONFORMANCE_PLAN_SCHEMA, -) -from scripts.beta_continuity import ( - EVIDENCE_SCHEMA, - ContinuityError, - PlanBlocked, - accepted_plan_authority, - accepted_publication_state, - advance_command, - authority_issue, - build_plan, - close_authority_issue, - conformance_evidence, - dispatch_accepted_continuity, - dispatch_recovery, - ensure_conformance_execution_or_retention, - exact_completion_authority, - load_config, - next_version, - phase_tag, - plan_command, - public_release_tags, - record_continuity_resolution, - record_phase, - recovery_publication_triggers, - require_partial_publication, - route_blockers, - select_versions, - validate_interrupted_evidence, - validate_resolution_source, -) -from scripts.release_plan import ( - LEGACY_SCHEMA as LEGACY_RELEASE_PLAN_SCHEMA, -) -from scripts.release_plan import ( - SCHEMA as RELEASE_PLAN_SCHEMA, -) -from scripts.release_plan import ( - candidate_manifest, -) -from tests.verification_fixture import candidate_verification, legacy_beta_one_release_plan - -ROOT = Path(__file__).resolve().parents[1] -ROUTED_BLOCKER_LABELS = ( - "authority:github", - "beta:blocker", - "kind:release-blocker", - "priority:P1", - "status:ready", -) - - -class PlanningClient: - def __init__( - self, - *, - stale_manifests: bool = False, - blocker_versions: dict[str, list[str]] | None = None, - occupied_versions: dict[str, set[str]] | None = None, - release_pages: dict[str, dict[int, list[dict[str, object]]]] | None = None, - ) -> None: - self.stale_manifests = stale_manifests - self.blocker_versions = blocker_versions or {} - self.occupied_versions = occupied_versions or {} - self.release_pages = release_pages or {} - self.commits = {name: f"{index + 1:040x}" for index, name in enumerate(COMPONENTS)} - self.latest = { - "workflow": "2.0.0-alpha.291", - "waterline": "2.0.0-alpha.137", - "server": "0.2.693", - "cli": "0.1.93", - "sdk-php": "0.1.13", - "sdk-python": "0.4.104", - "sdk-rust": "0.1.20", - } - self.requested_urls: list[str] = [] - - def json(self, url: str) -> object: - self.requested_urls.append(url) - for name, component in COMPONENTS.items(): - if url == f"https://api.github.com/repos/{component.repository}/issues?state=all&per_page=100": - return [ - { - "body": ( - "Blocks https://github.com/durable-workflow/.github/issues/2.\n\n" - f"" - ), - "labels": [{"name": label} for label in (*ROUTED_BLOCKER_LABELS[:-1], "status:done")], - "number": index + 1, - "state": "closed", - } - for index, version in enumerate(self.blocker_versions.get(name, [])) - ] - if f"repos/{component.repository}/branches/" in url: - return {"commit": {"sha": self.commits[name]}} - release_prefix = f"https://api.github.com/repos/{component.repository}/releases?per_page=100&page=" - if url.startswith(release_prefix): - page = int(url.removeprefix(release_prefix)) - return self.release_pages.get(name, {}).get( - page, - [{"draft": False, "tag_name": self.latest[name]}] if page == 1 else [], - ) - encoded_version = url.rsplit("/", 1)[-1] - if url.startswith(f"https://api.github.com/repos/{component.repository}/git/ref/tags/"): - if "tag" in self.occupied_versions.get(name, set()): - return {"ref": f"refs/tags/{encoded_version}"} - return None - if url.startswith(f"https://api.github.com/repos/{component.repository}/releases/tags/"): - if "release" in self.occupied_versions.get(name, set()): - return {"draft": True, "tag_name": encoded_version} - return None - raise AssertionError(f"unexpected JSON URL: {url}") - - def bytes(self, url: str, *, accept: str | None = None) -> bytes: - self.assert_raw(accept) - if "sdk-python" in url: - version = "0.4.104" if self.stale_manifests else "0.4.105" - return f'[project]\nname = "durable-workflow"\nversion = "{version}"\n'.encode() - if "sdk-rust" in url: - version = "0.1.20" if self.stale_manifests else "0.1.21" - return f'[package]\nname = "durable-workflow"\nversion = "{version}"\n'.encode() - raise AssertionError(f"unexpected bytes URL: {url}") - - @staticmethod - def assert_raw(accept: str | None) -> None: - if accept != "application/vnd.github.raw+json": - raise AssertionError(f"unexpected Accept: {accept}") - - -def run(command: list[str], directory: Path) -> str: - return subprocess.run(command, cwd=directory, check=True, text=True, capture_output=True).stdout.strip() - - -def continuity_plan(name: str = "continuity-test") -> dict[str, object]: - return { - "schema": RELEASE_PLAN_SCHEMA, - "plan": name, - "channel": "alpha", - "foundation": { - "tag": "beta-candidate/beta-continuity-foundation", - "commit": "4995052410bd4301c5796ffba54e0b6d2f490ed1", - }, - "components": { - component: { - "commit": f"{index + 1:040x}", - "version": ( - f"2.0.0-alpha.{index + 1}" if component in {"workflow", "waterline"} else f"0.1.{index + 1}" - ), - } - for index, component in enumerate(COMPONENTS) - }, - "beta_authorization": None, - } - - -def historical_continuity_plan() -> dict[str, object]: - return { - "schema": LEGACY_RELEASE_PLAN_SCHEMA, - "plan": "workspace-unavailable-recovery-f46818553161", - "channel": "alpha", - "foundation": { - "tag": "beta-candidate/beta-continuity-foundation", - "commit": "4995052410bd4301c5796ffba54e0b6d2f490ed1", - }, - "components": { - "workflow": { - "version": "2.0.0-alpha.292", - "commit": "7309173116dfc80ec8d22034ab9362e84dd55be4", - }, - "waterline": { - "version": "2.0.0-alpha.138", - "commit": "b29fdbd8aa99a50cd688938dde2e03a6b8e26388", - }, - "server": { - "version": "0.2.694", - "commit": "4f36343556b1698eba1fdaf78bc2b9c9d32b9329", - }, - "cli": { - "version": "0.1.94", - "commit": "36bde75882980e834854a145c9ad0f61ceec4659", - }, - "sdk-php": { - "version": "0.1.15", - "commit": "0673333d475a5b2d6b336790844bd126536925d9", - }, - "sdk-python": { - "version": "0.4.104", - "commit": "46d494075c92b307844e83e44ca4de1c93f90715", - }, - "sdk-rust": { - "version": "0.1.20", - "commit": "e1cbb36befdb90e19d9244e8deaccf9b871d01ba", - }, - }, - "beta_authorization": None, - } - - -def conformance_release_fixture( - plan: dict[str, Any], - run_id: int, - *, - run_attempt: int = 1, - legacy: bool = False, - sensitive: bool = False, -) -> tuple[dict[str, Any], dict[str, bytes], dict[str, Any]]: - candidate = candidate_manifest(plan) - tag = f"beta-conformance/{candidate['candidate']}/{run_id}.{run_attempt}" - fixture_id = str(run_id) if run_attempt == 1 else f"{run_id}.{run_attempt}" - runtime_dependencies = {} - for index, (name, selector) in enumerate(RUNTIME_DEPENDENCY_SELECTORS.items(), start=1): - digest = f"sha256:{index:064x}" - runtime_dependencies[name] = { - "selector": selector, - "image": f"{selector.rsplit(':', 1)[0]}@{digest}", - "manifest_digest": digest, - } - distribution_identities = { - name: { - "kind": component.distribution, - "locator": ( - f"{component.distribution}:{component.package}@{plan['components'][component_name]['version']}" - ), - "artifacts": [{"name": f"{name}.artifact", "sha256": f"{index + 10:064x}"}], - } - for index, (name, (component_name, component)) in enumerate(DISTRIBUTIONS.items()) - } - server_digest = f"sha256:{99:064x}" - conformance_plan = { - "schema": CONFORMANCE_PLAN_SCHEMA, - "candidate": { - "name": candidate["candidate"], - "manifest_sha256": manifest_digest(candidate), - "verification_sha256": "a" * 64, - "record_ref": f"beta-candidate/{candidate['candidate']}", - "record_commit": "b" * 40, - }, - "artifact_tuple": plan["components"], - "source_identities": {name: identity["commit"] for name, identity in plan["components"].items()}, - "distribution_identities": distribution_identities, - "runtime_dependencies": runtime_dependencies, - "runner": { - "repository": "durable-workflow/.github", - "revision": "c" * 40, - "contract_sha256": "d" * 64, - }, - "server_runner": { - "image": f"docker.io/durableworkflow/server@{server_digest}", - "manifest_digest": server_digest, - "source_commit": plan["components"]["server"]["commit"], - }, - "waterline_service_runner": { - "image": f"docker.io/durableworkflow/waterline@sha256:{98:064x}", - "manifest_digest": f"sha256:{98:064x}", - "source_commit": plan["components"]["waterline"]["commit"], - }, - "experiments": list(CONFORMANCE_EXPERIMENTS), - } - empty_digest = hashlib.sha256(b"").hexdigest() - payloads = {} - for name in CONFORMANCE_EXPERIMENTS: - diagnostic = { - "runner": "fixture", - "attempt": run_attempt, - "exit_code": 0, - "timed_out": False, - "native_outcome": "pass", - "runner_blocked": False, - "stdout_tail": "", - "stdout_sha256": empty_digest, - "stderr_tail": "", - "stderr_sha256": empty_digest, - "native_result_size_bytes": 128, - "native_result_sha256": "e" * 64, - "native_result_prefix_sha256": None, - "native_result_prefix_bytes": None, - "native_summary": { - "schema": "fixture.result/v1", - "artifact_versions": { - distribution: distribution_version(plan["components"], distribution) - for distribution in DISTRIBUTIONS - }, - "executed_distribution_identities": deepcopy(distribution_identities), - "scenario_statuses": [{"id": "fixture", "status": "pass"}], - "failure_projection": { - "max_bytes": NATIVE_FAILURE_PROJECTION_LIMIT, - "component_max_bytes": NATIVE_FAILURE_COMPONENT_LIMIT, - "truncated": False, - "scenarios": [], - }, - "local_product_source_checkout_used": False, - }, - "findings": [], - } - result = experiment_result( - conformance_plan, - name, - "fixture-contract", - ["sdk-php", "sdk-python", "sdk-rust"], - list(DISTRIBUTIONS), - "2026-07-20T10:00:00Z", - "passed", - run_attempt, - [diagnostic], - ) - if legacy: - result = deepcopy(result) - result["schema"] = LEGACY_EXPERIMENT_RESULT_SCHEMA - result["distribution_identities"].pop("waterline-service") - result.pop("waterline_service_runner") - result["required_distributions"].remove("waterline-service") - result["source_policy"]["orchestration_source"] = "exact_server_container" - native_summary = result["diagnostics"][0]["native_summary"] - native_summary["artifact_versions"].pop("waterline-service") - native_summary["executed_distribution_identities"].pop("waterline-service") - if sensitive and name == "replay": - result["diagnostics"][0]["stdout_tail"] = "password=published-secret" - payloads[f"https://downloads.example/{fixture_id}/{name}.json"] = json.dumps( - result, - sort_keys=True, - separators=(",", ":"), - ).encode() - run = { - "repository": "durable-workflow/.github", - "run_id": run_id, - "run_attempt": run_attempt, - "evidence_tag": tag, - } - if legacy: - conformance_plan["schema"] = LEGACY_PLAN_SCHEMA - conformance_plan["distribution_identities"].pop("waterline-service") - conformance_plan.pop("waterline_service_runner") - suite = { - "schema": LEGACY_SUITE_RESULT_SCHEMA if legacy else "durable-workflow.beta-conformance.suite-result/v2", - "candidate": conformance_plan["candidate"], - "artifact_tuple": conformance_plan["artifact_tuple"], - "source_identities": conformance_plan["source_identities"], - "distribution_identities": conformance_plan["distribution_identities"], - "runtime_dependencies": conformance_plan["runtime_dependencies"], - "runner": conformance_plan["runner"], - "server_runner": conformance_plan["server_runner"], - "github_run": run, - "outcome": "pass", - "experiments": { - name: { - "classification": "passed", - "outcome": "pass", - "result_sha256": hashlib.sha256( - payloads[f"https://downloads.example/{fixture_id}/{name}.json"] - ).hexdigest(), - } - for name in CONFORMANCE_EXPERIMENTS - }, - } - if not legacy: - suite["waterline_service_runner"] = conformance_plan["waterline_service_runner"] - suite_url = f"https://downloads.example/{fixture_id}/suite-result.json" - payloads[suite_url] = json.dumps(suite, sort_keys=True, separators=(",", ":")).encode() - release_url = f"https://github.com/durable-workflow/.github/releases/tag/{tag}" - release = { - "assets": [{"browser_download_url": url, "name": url.rsplit("/", 1)[-1]} for url in payloads], - "draft": False, - "html_url": release_url, - "tag_name": tag, - } - return release, payloads, {"release": release_url, "run": run, "tag": tag} - - -class PaginatedReleaseClient: - def __init__(self, pages: dict[int, list[dict[str, Any]]], payloads: dict[str, bytes]) -> None: - self.pages = pages - self.payloads = payloads - self.requested_pages: list[int] = [] - - def json(self, url: str) -> list[dict[str, Any]]: - prefix = "https://api.github.com/repos/durable-workflow/.github/releases?per_page=100&page=" - if not url.startswith(prefix): - raise AssertionError(f"unexpected JSON URL: {url}") - page = int(url.removeprefix(prefix)) - self.requested_pages.append(page) - return self.pages.get(page, []) - - def bytes(self, url: str) -> bytes: - return self.payloads[url] - - -class BetaContinuityTest(unittest.TestCase): - def test_completed_authority_is_a_valid_scheduled_no_op_boundary(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - - class CompletedIssueClient: - @staticmethod - def json(_url: str) -> dict[str, object]: - return { - "body": "", - "html_url": "https://github.com/durable-workflow/.github/issues/2", - "labels": [ - {"name": "authority:github"}, - {"name": "beta:blocker"}, - {"name": "completion:evidence-verified"}, - {"name": "kind:cross-repository"}, - {"name": "status:done"}, - ], - "state": "closed", - "updated_at": "2026-07-20T00:00:00Z", - } - - with self.assertRaises(ContinuityError): - authority_issue(config, CompletedIssueClient()) # type: ignore[arg-type] - issue = authority_issue(config, CompletedIssueClient(), allow_completed=True) # type: ignore[arg-type] - self.assertEqual("closed", issue["state"]) - - def test_config_is_machine_validated(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - - self.assertEqual("workspace-unavailable-beta-continuity-release-pages", config["drill"]) - self.assertEqual("durable-workflow/.github", config["authority_issue"]["repository"]) - self.assertEqual( - { - 10: "beta-continuity-post-acceptance-publication", - 11: "continuity-converges-routed-release-blockers", - }, - {item["number"]: item["work_id"] for item in config["evidence_work_items"]}, - ) - self.assertEqual("workflow", config["first_component"]) - self.assertEqual("workspace-unavailable-release-pages", config["plan_prefix"]) - self.assertEqual( - "beta-continuity/workspace-unavailable-0b191da0d140/interrupted", - config["superseded_interruption"]["tag"], - ) - - def test_evidence_work_item_inventory_is_declarative_and_unique(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - future = { - "number": 12, - "repository": "durable-workflow/.github", - "required_labels": [ - "authority:github", - "beta:blocker", - "completion:evidence-required", - "kind:release-blocker", - "status:ready", - ], - "work_id": "future-evidence-required-correction", - } - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "config.json" - config["evidence_work_items"].append(future) - path.write_text(json.dumps(config), encoding="utf-8") - self.assertEqual(3, len(load_config(path)["evidence_work_items"])) - - config["evidence_work_items"].append({**future, "work_id": "duplicate-location"}) - path.write_text(json.dumps(config), encoding="utf-8") - with self.assertRaisesRegex(ContinuityError, "duplicate authority"): - load_config(path) - - def test_version_allocation_uses_the_next_numeric_public_identity(self) -> None: - self.assertEqual( - "2.0.0-alpha.292", - next_version("workflow", ["2.0.0-alpha.9", "2.0.0-alpha.291", "not-a-release"]), - ) - self.assertEqual("0.4.103", next_version("sdk-python", ["0.4.99", "0.4.102", "1.0.0-beta.1"])) - - def test_fresh_selection_uses_complete_reordered_release_histories(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - workflow_page = [{"draft": False, "tag_name": f"2.0.0-alpha.{number}"} for number in range(100, 0, -1)] - python_page = [{"draft": False, "tag_name": f"0.4.{number}"} for number in range(100, 0, -1)] - client = PlanningClient( - release_pages={ - "workflow": { - 1: workflow_page, - 2: [ - {"draft": False, "tag_name": "2.0.0-alpha.200"}, - {"draft": False, "tag_name": "2.0.0-alpha.291"}, - ], - }, - "sdk-python": { - 1: python_page, - 2: [ - {"draft": False, "tag_name": "0.4.102"}, - {"draft": False, "tag_name": "0.4.104"}, - ], - }, - } - ) - - selection = select_versions(config, client) # type: ignore[arg-type] - - self.assertEqual("2.0.0-alpha.292", selection["versions"]["workflow"]) - self.assertEqual("0.4.105", selection["versions"]["sdk-python"]) - self.assertIn( - "https://api.github.com/repos/durable-workflow/workflow/releases?per_page=100&page=2", - client.requested_urls, - ) - self.assertIn( - "https://api.github.com/repos/durable-workflow/sdk-python/releases?per_page=100&page=2", - client.requested_urls, - ) - - def test_release_history_pagination_fails_closed_when_a_page_repeats(self) -> None: - page = [{"draft": False, "tag_name": f"2.0.0-alpha.{number}"} for number in range(1, 101)] - client = PlanningClient(release_pages={"workflow": {1: page, 2: page}}) - - with self.assertRaisesRegex(ContinuityError, "release pagination did not advance"): - public_release_tags(client, COMPONENTS["workflow"].repository) # type: ignore[arg-type] - - def test_release_history_pagination_fails_closed_at_the_page_bound(self) -> None: - pages = { - page: [{"draft": False, "tag_name": f"2.0.0-alpha.{(page - 1) * 100 + number}"} for number in range(1, 101)] - for page in (1, 2) - } - client = PlanningClient(release_pages={"workflow": pages}) - - with ( - patch("scripts.beta_continuity.GITHUB_RELEASE_PAGE_LIMIT", 2), - self.assertRaisesRegex(ContinuityError, "exceeded the pagination bound"), - ): - public_release_tags(client, COMPONENTS["workflow"].repository) # type: ignore[arg-type] - - def test_fresh_selection_rejects_an_occupied_proposed_version(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - client = PlanningClient(occupied_versions={"sdk-python": {"tag", "release"}}) - - with self.assertRaisesRegex( - ContinuityError, - r"durable-workflow/sdk-python@0\.4\.105.*tag and release authority", - ): - select_versions(config, client) # type: ignore[arg-type] - - def test_plan_binds_seven_heads_and_requires_unoccupied_manifest_versions(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - client = PlanningClient() - - with patch("scripts.beta_continuity.resolve_tag", return_value=None): - plan, expected = build_plan(config, client) # type: ignore[arg-type] - - self.assertEqual(RELEASE_PLAN_SCHEMA, plan["schema"]) - self.assertEqual(set(COMPONENTS), set(plan["components"])) - self.assertEqual("2.0.0-alpha.292", plan["components"]["workflow"]["version"]) - self.assertEqual("0.4.105", plan["components"]["sdk-python"]["version"]) - self.assertEqual("0.1.21", plan["components"]["sdk-rust"]["version"]) - self.assertEqual(expected, {name: identity["commit"] for name, identity in plan["components"].items()}) - self.assertTrue(plan["plan"].startswith("workspace-unavailable-")) - - def test_plan_routes_stale_source_versions_as_component_blockers(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - - with ( - patch("scripts.beta_continuity.resolve_tag", return_value=None), - self.assertRaises(PlanBlocked) as raised, - ): - build_plan(config, PlanningClient(stale_manifests=True)) # type: ignore[arg-type] - - blockers = raised.exception.blockers - self.assertEqual({"sdk-python", "sdk-rust"}, {blocker["component"] for blocker in blockers}) - self.assertTrue(all(blocker["repository"].startswith("durable-workflow/") for blocker in blockers)) - - def test_planning_records_the_selection_before_routing_source_blockers(self) -> None: - client = PlanningClient(stale_manifests=True) - issue = { - "number": 2, - "repository": "durable-workflow/.github", - "state": "open", - "work_id": "github-only-beta-continuity-drill", - } - selection_record = { - "status": "created", - "tag": "beta-continuity-selection/workspace-unavailable-beta-continuity-release-pages", - "commit": "f" * 40, - } - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - with ( - patch("scripts.beta_continuity.PublicClient", return_value=client), - patch("scripts.beta_continuity.authority_issue", return_value=issue), - patch("scripts.beta_continuity.accepted_plan", return_value=None), - patch("scripts.beta_continuity.public_selection", return_value=None), - patch("scripts.beta_continuity.record_selection", return_value=selection_record) as record, - patch("scripts.beta_continuity.resolve_tag", return_value=None), - patch.dict(os.environ, {"GITHUB_SHA": "c" * 40}), - self.assertRaises(PlanBlocked), - ): - plan_command( - ROOT / "beta-continuity" / "config.json", - root / "release-plan.json", - root / "expected.json", - root / "state.json", - None, - ) - - record.assert_called_once() - state = json.loads((root / "state.json").read_text(encoding="utf-8")) - self.assertEqual("blocked", state["outcome"]) - self.assertEqual(selection_record["tag"], state["selection"]["tag"]) - self.assertEqual("0.4.105", state["selection"]["versions"]["sdk-python"]) - - def test_retained_selection_reuses_published_versions_without_successor_loop(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - client = PlanningClient() - selection = select_versions(config, client) # type: ignore[arg-type] - client.latest.update({"sdk-python": "0.4.105", "sdk-rust": "0.1.21"}) - published_commits = { - (COMPONENTS["sdk-python"].repository, "0.4.105"): "a" * 40, - (COMPONENTS["sdk-rust"].repository, "0.1.21"): "b" * 40, - } - - def resolve_selected_tag(_client: object, repository: str, version: str) -> str | None: - return published_commits.get((repository, version)) - - with patch("scripts.beta_continuity.resolve_tag", side_effect=resolve_selected_tag): - plan, expected = build_plan(config, client, selection) # type: ignore[arg-type] - - self.assertEqual("0.4.105", selection["versions"]["sdk-python"]) - self.assertEqual("0.1.21", selection["versions"]["sdk-rust"]) - self.assertEqual("0.4.105", plan["components"]["sdk-python"]["version"]) - self.assertEqual("a" * 40, expected["sdk-python"]) - self.assertEqual("0.1.21", plan["components"]["sdk-rust"]["version"]) - self.assertEqual("b" * 40, expected["sdk-rust"]) - - def test_planning_reuses_the_immutable_selection_without_recalculating(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - client = PlanningClient() - selection = select_versions(config, client) # type: ignore[arg-type] - selection_record = { - "tag": "beta-continuity-selection/workspace-unavailable-beta-continuity-release-pages", - "commit": "f" * 40, - "sha256": manifest_digest(selection), - } - issue = { - "number": 2, - "repository": "durable-workflow/.github", - "state": "open", - "work_id": "github-only-beta-continuity-drill", - } - client.requested_urls.clear() - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - with ( - patch("scripts.beta_continuity.PublicClient", return_value=client), - patch("scripts.beta_continuity.authority_issue", return_value=issue), - patch("scripts.beta_continuity.accepted_plan", return_value=None), - patch("scripts.beta_continuity.public_selection", return_value=(selection, selection_record)), - patch("scripts.beta_continuity.select_versions") as select, - patch("scripts.beta_continuity.record_selection") as record, - patch("scripts.beta_continuity.resolve_tag", return_value=None), - patch.dict(os.environ, {"GITHUB_SHA": "c" * 40}), - ): - plan_command( - ROOT / "beta-continuity" / "config.json", - root / "release-plan.json", - root / "expected.json", - root / "state.json", - None, - ) - - select.assert_not_called() - record.assert_not_called() - self.assertFalse(any("/releases?" in url for url in client.requested_urls)) - - def test_fresh_drill_ignores_historical_closed_blocker_versions(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - client = PlanningClient( - blocker_versions={ - "sdk-python": ["0.4.103", "0.4.104"], - "sdk-rust": ["0.1.18", "0.1.20"], - } - ) - first = select_versions(config, client) # type: ignore[arg-type] - second = select_versions(config, client) # type: ignore[arg-type] - - self.assertEqual("0.4.105", first["versions"]["sdk-python"]) - self.assertEqual("0.1.21", first["versions"]["sdk-rust"]) - self.assertEqual(first, second) - self.assertFalse(any("/issues?" in url for url in client.requested_urls)) - - def test_untrusted_marker_cannot_suppress_protected_blocker_routing(self) -> None: - marker = "" - - class RoutingWriter: - def __init__(self) -> None: - self.issues: list[dict[str, object]] = [ - { - "body": marker, - "labels": [], - "number": 1, - }, - { - "body": marker, - "labels": [{"name": label} for label in (*ROUTED_BLOCKER_LABELS[:-1], "status:blocked")], - "number": 2, - }, - { - "body": marker, - "labels": [{"name": label} for label in ROUTED_BLOCKER_LABELS], - "number": 3, - "pull_request": {"url": "https://api.github.com/repos/example/pulls/3"}, - }, - ] - self.created: list[dict[str, object]] = [] - - def list(self, _path: str) -> list[dict[str, object]]: - return self.issues - - def request(self, method: str, _path: str, payload: dict[str, object]) -> None: - self.assert_post(method) - self.created.append(payload) - self.issues.append( - { - **payload, - "labels": [{"name": label} for label in payload["labels"]], - "number": len(self.issues) + 1, - "state": "open", - } - ) - - @staticmethod - def assert_post(method: str) -> None: - if method != "POST": - raise AssertionError(f"unexpected method: {method}") - - state = { - "outcome": "blocked", - "selection": {"tag": "beta-continuity-selection/workspace-unavailable-beta-continuity-release-pages"}, - "blockers": [ - { - "component": "sdk-python", - "reason": "source manifest has not reached the retained version", - "repository": COMPONENTS["sdk-python"].repository, - "slug": "sdk-python-source-version-0.4.103", - "version": "0.4.103", - } - ], - } - writer = RoutingWriter() - with tempfile.TemporaryDirectory() as temporary: - state_path = Path(temporary) / "state.json" - state_path.write_text(json.dumps(state), encoding="utf-8") - with patch("scripts.beta_continuity.GitHubWriter", return_value=writer): - route_blockers(ROOT / "beta-continuity" / "config.json", state_path) - route_blockers(ROOT / "beta-continuity" / "config.json", state_path) - - self.assertEqual(1, len(writer.created)) - self.assertEqual(list(ROUTED_BLOCKER_LABELS), writer.created[0]["labels"]) - - def test_external_intake_is_exempt_from_the_proactive_creation_budget(self) -> None: - class RoutingWriter: - def __init__(self) -> None: - self.issues = [ - { - "body": "External intake.", - "labels": [{"name": "kind:defect"}, {"name": "status:triage"}], - "number": number, - "state": "open", - } - for number in range(1, 4) - ] - self.requests: list[tuple[str, str, dict[str, object]]] = [] - - def list(self, _path: str) -> list[dict[str, object]]: - return self.issues - - def request(self, method: str, path: str, payload: dict[str, object]) -> None: - self.requests.append((method, path, payload)) - - state = { - "outcome": "blocked", - "selection": {"tag": "beta-continuity-selection/workspace-unavailable-beta-continuity-release-pages"}, - "blockers": [ - { - "component": "sdk-python", - "reason": "source manifest has not reached the retained version", - "repository": COMPONENTS["sdk-python"].repository, - "slug": "sdk-python-source-version-0.4.103", - "version": "0.4.103", - } - ], - } - writer = RoutingWriter() - with tempfile.TemporaryDirectory() as temporary: - state_path = Path(temporary) / "state.json" - state_path.write_text(json.dumps(state), encoding="utf-8") - with patch("scripts.beta_continuity.GitHubWriter", return_value=writer): - route_blockers(ROOT / "beta-continuity" / "config.json", state_path) - - self.assertEqual("POST", writer.requests[0][0]) - self.assertEqual(list(ROUTED_BLOCKER_LABELS), writer.requests[0][2]["labels"]) - - def test_proactive_blocker_consolidates_when_one_actionable_issue_exists(self) -> None: - root = { - "body": "Existing release blocker root.", - "created_at": "2026-08-20T00:00:00Z", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:release-blocker"}, - {"name": "priority:P1"}, - {"name": "status:ready"}, - ], - "number": 4, - "state": "open", - } - - class RoutingWriter: - def __init__(self) -> None: - self.issues = [root] - self.requests: list[tuple[str, str, dict[str, object]]] = [] - - def list(self, _path: str) -> list[dict[str, object]]: - return self.issues - - def request(self, method: str, path: str, payload: dict[str, object]) -> None: - self.requests.append((method, path, payload)) - - state = { - "outcome": "blocked", - "selection": {"tag": "beta-continuity-selection/workspace-unavailable-beta-continuity-release-pages"}, - "blockers": [ - { - "component": "sdk-python", - "reason": "source manifest has not reached the retained version", - "repository": COMPONENTS["sdk-python"].repository, - "slug": "sdk-python-source-version-0.4.103", - "version": "0.4.103", - } - ], - } - writer = RoutingWriter() - with tempfile.TemporaryDirectory() as temporary: - state_path = Path(temporary) / "state.json" - state_path.write_text(json.dumps(state), encoding="utf-8") - with patch("scripts.beta_continuity.GitHubWriter", return_value=writer): - route_blockers(ROOT / "beta-continuity" / "config.json", state_path) - route_blockers(ROOT / "beta-continuity" / "config.json", state_path) - routed_state = json.loads(state_path.read_text(encoding="utf-8")) - - self.assertEqual(["PATCH"], [request[0] for request in writer.requests]) - self.assertIn("Additional acceptance criteria", root["body"]) - self.assertEqual(1, root["body"].count("beta-continuity-consolidated-finding")) - self.assertEqual("consolidated", routed_state["routing"][0]["action"]) - - def test_completed_protected_blocker_is_reopened_and_reactivated(self) -> None: - marker = ( - "Blocks https://github.com/durable-workflow/.github/issues/2.\n\n" - "" - ) - - class RoutingWriter: - def __init__(self) -> None: - self.requests: list[tuple[str, str, dict[str, object]]] = [] - self.issues = [ - { - "body": marker, - "labels": [ - {"name": label} - for label in ( - *ROUTED_BLOCKER_LABELS[:-1], - "status:done", - "component:sdk-python", - ) - ], - "number": 5, - "state": "closed", - } - ] - - def list(self, _path: str) -> list[dict[str, object]]: - return self.issues - - def request(self, method: str, path: str, payload: dict[str, object]) -> None: - self.requests.append((method, path, payload)) - issue = self.issues[0] - issue["state"] = payload["state"] - issue["labels"] = [{"name": label} for label in payload["labels"]] - - state = { - "outcome": "blocked", - "selection": {"tag": "beta-continuity-selection/workspace-unavailable-beta-continuity-release-pages"}, - "blockers": [ - { - "component": "sdk-python", - "reason": "source manifest has not reached the retained version", - "repository": COMPONENTS["sdk-python"].repository, - "slug": "sdk-python-source-version-0.4.103", - "version": "0.4.103", - } - ], - } - with tempfile.TemporaryDirectory() as temporary: - state_path = Path(temporary) / "state.json" - state_path.write_text(json.dumps(state), encoding="utf-8") - writer = RoutingWriter() - with patch("scripts.beta_continuity.GitHubWriter", return_value=writer): - route_blockers(ROOT / "beta-continuity" / "config.json", state_path) - route_blockers(ROOT / "beta-continuity" / "config.json", state_path) - - self.assertEqual( - [ - ( - "PATCH", - f"/repos/{COMPONENTS['sdk-python'].repository}/issues/5", - { - "state": "open", - "labels": [ - "authority:github", - "beta:blocker", - "component:sdk-python", - "kind:release-blocker", - "priority:P1", - "status:ready", - ], - }, - ) - ], - writer.requests, - ) - - def test_interruption_requires_a_provably_partial_publication(self) -> None: - require_partial_publication({"workflow": {"version": "2.0.0-alpha.292"}}, ["waterline"]) - with self.assertRaises(ContinuityError): - require_partial_publication({name: {} for name in COMPONENTS}, []) - with self.assertRaises(ContinuityError): - require_partial_publication({}, list(COMPONENTS)) - - def test_acceptance_baseline_partitions_the_exact_plan(self) -> None: - plan = continuity_plan() - python_identity = plan["components"]["sdk-python"] - evidence = { - "schema": EVIDENCE_SCHEMA, - "phase": "accepted", - "outcome": "accepted", - "observed_at": "2026-07-20T10:00:00Z", - "release_plan": { - "tag": f"release-plan/{plan['plan']}", - "sha256": manifest_digest(plan), - }, - "candidate_identity": { - "components": plan["components"], - "plan_sha256": manifest_digest(plan), - }, - "public_components_at_acceptance": {"sdk-python": python_identity}, - "pending_components_at_acceptance": [name for name in COMPONENTS if name != "sdk-python"], - } - with patch("scripts.beta_continuity.read_public_json_file", return_value=evidence): - baseline = accepted_publication_state(object(), plan, "a" * 40) # type: ignore[arg-type] - - self.assertEqual({"sdk-python"}, set(baseline["public_components"])) - self.assertNotIn("sdk-python", baseline["pending_components"]) - - evidence.pop("pending_components_at_acceptance") - with ( - patch("scripts.beta_continuity.read_public_json_file", return_value=evidence), - self.assertRaisesRegex(ContinuityError, "complete publication baseline"), - ): - accepted_publication_state(object(), plan, "a" * 40) # type: ignore[arg-type] - - def test_terminal_authority_binds_plan_source_and_qualification_artifacts(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - plan = continuity_plan("workspace-unavailable-recovery-test") - plan_commit = "1" * 40 - accepted_commit = "2" * 40 - completion_commit = "3" * 40 - complete_commit = "4" * 40 - noop_commit = "5" * 40 - controller_commit = "6" * 40 - plan_tag = f"release-plan/{plan['plan']}" - completion_tag = f"release-candidate/{plan['channel']}/{plan['plan']}" - published = { - name: { - "commit": identity["commit"], - "published_at": "2026-07-20T10:10:00Z", - "release_id": index + 1, - "url": f"https://github.com/{COMPONENTS[name].repository}/releases/tag/{identity['version']}", - "version": identity["version"], - } - for index, (name, identity) in enumerate(plan["components"].items()) - } - candidate = candidate_manifest(plan) - public_verification = candidate_verification(candidate, verified_at="2026-07-20T10:20:00Z") - qualification_targets = { - name: { - "branch": "v2" if name in {"workflow", "waterline"} else "main", - "commit": identity["commit"], - "protected_checks": ["qualification"], - "successful_check_runs": {"qualification": index + 10}, - } - for index, (name, identity) in enumerate(plan["components"].items()) - } - qualification_targets["github-control-plane"] = { - "branch": "main", - "commit": controller_commit, - "protected_checks": ["qualification"], - "successful_check_runs": {"qualification": 99}, - } - qualification = { - "schema": "durable-workflow.github-target-qualification/v1", - "targets": qualification_targets, - } - recorded_conformance_tag = f"beta-conformance/{candidate['candidate']}/300.1" - recorded_conformance = { - "release": f"https://github.com/durable-workflow/.github/releases/tag/{recorded_conformance_tag}", - "run": { - "repository": "durable-workflow/.github", - "run_id": 300, - "run_attempt": 1, - "evidence_tag": recorded_conformance_tag, - }, - "tag": recorded_conformance_tag, - } - replacement_conformance_tag = f"beta-conformance/{candidate['candidate']}/301.1" - replacement_conformance = { - "release": f"https://github.com/durable-workflow/.github/releases/tag/{replacement_conformance_tag}", - "run": { - "repository": "durable-workflow/.github", - "run_id": 301, - "run_attempt": 1, - "evidence_tag": replacement_conformance_tag, - }, - "tag": replacement_conformance_tag, - } - plan_record = {"tag": plan_tag, "commit": plan_commit, "sha256": manifest_digest(plan)} - acceptance = { - "schema": EVIDENCE_SCHEMA, - "github_run": {"sha": controller_commit}, - } - completion_artifact = { - "schema": "durable-workflow.release-candidate/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "release_plan": {"tag": plan_tag, "commit": plan_commit, "sha256": manifest_digest(plan)}, - "components": plan["components"], - } - completion_verification = { - "schema": "durable-workflow.release-candidate-verification/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "release_plan_sha256": manifest_digest(plan), - "public_verification": public_verification, - } - complete_evidence = { - "schema": EVIDENCE_SCHEMA, - "drill": config["drill"], - "phase": "complete", - "outcome": "passed", - "release_plan": {"tag": plan_tag, "sha256": manifest_digest(plan)}, - "accepted_phase": f"beta-continuity/{plan['plan']}/accepted", - "interrupted_phase": f"beta-continuity/{plan['plan']}/interrupted", - "resumed_phase": f"beta-continuity/{plan['plan']}/resumed", - "plan_record": plan_record, - "public_verification": {"tag": completion_tag, "commit": completion_commit}, - "conformance": recorded_conformance, - "published_components": published, - } - noop_evidence = { - "schema": EVIDENCE_SCHEMA, - "drill": config["drill"], - "phase": "no-op-confirmed", - "outcome": "successful-scheduled-no-op-confirmed", - "release_plan": {"tag": plan_tag, "sha256": manifest_digest(plan)}, - "complete_phase": { - "tag": f"beta-continuity/{plan['plan']}/complete", - "commit": complete_commit, - }, - "successful_no_op_run": { - "conclusion": "success", - "created_at": "2026-07-20T10:30:00Z", - "id": 301, - "url": "https://github.com/durable-workflow/.github/actions/runs/301", - }, - } - records = { - (plan_commit, "release-plan.json"): plan, - (accepted_commit, "continuity-evidence.json"): acceptance, - (accepted_commit, "target-qualification-evidence.json"): qualification, - (completion_commit, "release-candidate.json"): completion_artifact, - (completion_commit, "verification.json"): completion_verification, - (complete_commit, "continuity-evidence.json"): complete_evidence, - (complete_commit, "release-plan.json"): plan, - (noop_commit, "continuity-evidence.json"): noop_evidence, - (noop_commit, "release-plan.json"): plan, - } - tags = { - plan_tag: plan_commit, - f"beta-continuity/{plan['plan']}/accepted": accepted_commit, - f"beta-continuity/{plan['plan']}/complete": complete_commit, - f"beta-continuity/{plan['plan']}/no-op-confirmed": noop_commit, - completion_tag: completion_commit, - } - - def read_record(_client: object, commit: str, filename: str) -> object: - return records[(commit, filename)] - - client = object() - with ( - patch("scripts.beta_continuity.resolve_tag", side_effect=lambda _client, _repo, tag: tags.get(tag)), - patch("scripts.beta_continuity.plan_record", return_value=plan_record), - patch( - "scripts.beta_continuity.public_phase_commit", - side_effect=lambda _client, _plan, phase: tags.get(f"beta-continuity/{plan['plan']}/{phase}"), - ), - patch( - "scripts.beta_continuity.accepted_plan_authority", - return_value={"commit": accepted_commit}, - ), - patch("scripts.beta_continuity.read_public_json_file", side_effect=read_record), - patch("scripts.beta_continuity.component_publications", return_value=(published, [])), - patch( - "scripts.beta_continuity.conformance_evidence", - return_value=replacement_conformance, - ) as conformance_selection, - ): - authority = exact_completion_authority( - client, # type: ignore[arg-type] - config, - plan, - complete_commit, - noop_commit, - ) - self.assertEqual(plan_record, authority["plan_record"]) - self.assertEqual(manifest_digest(qualification), authority["qualification"]["sha256"]) - self.assertEqual(replacement_conformance, authority["conformance"]) - self.assertEqual(recorded_conformance, complete_evidence["conformance"]) - conformance_selection.assert_called_once_with( - client, - plan, - preferred=recorded_conformance, - ) - - qualification_targets["sdk-python"]["commit"] = "f" * 40 - with self.assertRaisesRegex(ContinuityError, "sdk-python.*exact plan source"): - exact_completion_authority( - client, # type: ignore[arg-type] - config, - plan, - complete_commit, - noop_commit, - ) - - def test_terminal_authority_fails_closed_without_public_artifact_evidence(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - plan = continuity_plan("workspace-unavailable-recovery-test") - plan_record = { - "tag": f"release-plan/{plan['plan']}", - "commit": "1" * 40, - "sha256": manifest_digest(plan), - } - - with ( - patch("scripts.beta_continuity.plan_record", return_value=plan_record), - patch("scripts.beta_continuity.read_public_json_file", side_effect=[plan, None, {}]), - patch("scripts.beta_continuity.public_phase_commit", return_value="2" * 40), - patch("scripts.beta_continuity.accepted_plan_authority", return_value={"commit": "2" * 40}), - patch("scripts.beta_continuity.accepted_qualification_evidence", return_value={}), - patch("scripts.beta_continuity.resolve_tag", return_value="3" * 40), - self.assertRaisesRegex(ContinuityError, "public completion artifact differs from the exact plan"), - ): - exact_completion_authority( - object(), # type: ignore[arg-type] - config, - plan, - "4" * 40, - "5" * 40, - ) - - def test_completion_fails_closed_when_a_configured_evidence_work_item_is_missing(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - parent_specification = config["authority_issue"] - parent = { - "body": f"", - "labels": [{"name": label} for label in config["required_issue_labels"]], - "number": parent_specification["number"], - "state": "open", - } - present_specification = config["evidence_work_items"][0] - present = { - "body": "", - "labels": [{"name": label} for label in present_specification["required_labels"]], - "number": present_specification["number"], - "state": "open", - } - missing_specification = config["evidence_work_items"][1] - parent_path = f"/repos/{parent_specification['repository']}/issues/{parent_specification['number']}" - present_path = f"/repos/{present_specification['repository']}/issues/{present_specification['number']}" - missing_path = f"/repos/{missing_specification['repository']}/issues/{missing_specification['number']}" - - class MissingWorkItemWriter: - def __init__(self) -> None: - self.requests: list[tuple[str, str]] = [] - - @staticmethod - def get(path: str) -> dict[str, object] | None: - if path == parent_path: - return parent - if path == present_path: - return present - if path == missing_path: - return None - raise AssertionError(f"unexpected GET path: {path}") - - def request(self, method: str, path: str, _payload: dict[str, object]) -> None: - self.requests.append((method, path)) - - writer = MissingWorkItemWriter() - with self.assertRaises(ContinuityError) as raised: - close_authority_issue(writer, config, {}) # type: ignore[arg-type] - - self.assertIn( - f"trusted evidence work item {missing_path} has an invalid GitHub response", - str(raised.exception), - ) - self.assertEqual([], writer.requests) - self.assertEqual("open", parent["state"]) - - def test_routed_blockers_close_before_parent_and_ignore_untrusted_markers(self) -> None: - config = load_config(ROOT / "beta-continuity" / "config.json") - plan = continuity_plan("workspace-unavailable-recovery-test") - component = "sdk-python" - repository = COMPONENTS[component].repository - version = plan["components"][component]["version"] - dependency = "Blocks https://github.com/durable-workflow/.github/issues/2." - marker = f"" - trusted = { - "body": f"{dependency}\n\n{marker}", - "html_url": f"https://github.com/{repository}/issues/5", - "labels": [{"name": label} for label in (*ROUTED_BLOCKER_LABELS, f"component:{component}")], - "number": 5, - "state": "open", - } - untrusted = { - "body": f"{dependency}\n\n{marker}", - "html_url": f"https://github.com/{repository}/issues/1", - "labels": [], - "number": 1, - "state": "open", - } - mismatched = { - "body": (f"{dependency}\n\n"), - "labels": [{"name": label} for label in ROUTED_BLOCKER_LABELS], - "number": 2, - "state": "open", - } - parent = { - "body": "", - "html_url": "https://github.com/durable-workflow/.github/issues/2", - "labels": [{"name": label} for label in config["required_issue_labels"]], - "number": 2, - "state": "open", - } - live_work_item_bodies = { - 10: (""), - 11: (""), - } - evidence_work_items = { - specification["number"]: { - "body": live_work_item_bodies[specification["number"]], - "html_url": (f"https://github.com/{specification['repository']}/issues/{specification['number']}"), - "labels": [{"name": label} for label in specification["required_labels"]], - "number": specification["number"], - "state": "open", - } - for specification in config["evidence_work_items"] - } - - class ClosureWriter: - def __init__(self) -> None: - self.issues = {repository: [untrusted, mismatched, trusted]} - self.parent = parent - self.evidence_work_items = evidence_work_items - self.comments: dict[str, list[dict[str, object]]] = {} - self.requests: list[tuple[str, str]] = [] - self.fail_blocker_patch_once = False - - def list(self, path: str) -> list[dict[str, object]]: - if path.endswith("/comments"): - return self.comments.setdefault(path, []) - for slug, issues in self.issues.items(): - if path.startswith(f"/repos/{slug}/issues?"): - return issues - return [] - - def get(self, path: str) -> dict[str, object]: - if path == "/repos/durable-workflow/.github/issues/2": - return self.parent - for number, work_item in self.evidence_work_items.items(): - if path == f"/repos/durable-workflow/.github/issues/{number}": - return work_item - for slug, issues in self.issues.items(): - prefix = f"/repos/{slug}/issues/" - if path.startswith(prefix): - number = int(path.removeprefix(prefix)) - return next(issue for issue in issues if issue["number"] == number) - raise AssertionError(f"unexpected GET path: {path}") - - def request(self, method: str, path: str, payload: dict[str, object]) -> None: - self.requests.append((method, path)) - if method == "POST": - self.comments.setdefault(path, []).append(payload) - return - if path == f"/repos/{repository}/issues/5" and self.fail_blocker_patch_once: - self.fail_blocker_patch_once = False - raise ContinuityError("injected blocker close interruption") - issue = self.get(path) - issue["state"] = payload["state"] - issue["labels"] = [{"name": label} for label in payload["labels"]] - - completion = { - "plan": plan, - "plan_record": { - "tag": f"release-plan/{plan['plan']}", - "commit": "a" * 40, - "sha256": manifest_digest(plan), - }, - "public_verification": { - "tag": f"release-candidate/{plan['channel']}/{plan['plan']}", - "commit": "b" * 40, - }, - "qualification": { - "tag": f"beta-continuity/{plan['plan']}/accepted", - "commit": "c" * 40, - "sha256": "d" * 64, - }, - "conformance": { - "release": "https://github.com/durable-workflow/.github/releases/tag/example", - "tag": "beta-conformance/example/1.1", - }, - } - writer = ClosureWriter() - with self.assertRaisesRegex(ContinuityError, "active routed blocker.*differs"): - close_authority_issue(writer, config, completion) # type: ignore[arg-type] - self.assertEqual([], writer.requests) - mismatched["state"] = "closed" - mismatched["labels"] = [{"name": label} for label in (*ROUTED_BLOCKER_LABELS[:-1], "status:done")] - expected_work_item_body = evidence_work_items[11]["body"] - evidence_work_items[11]["body"] = ( - f"{expected_work_item_body}\n" - ) - with self.assertRaisesRegex(ContinuityError, "does not match its configured work-id"): - close_authority_issue(writer, config, completion) # type: ignore[arg-type] - evidence_work_items[11]["body"] = expected_work_item_body - expected_work_item_labels = evidence_work_items[11]["labels"] - evidence_work_items[11]["labels"] = [ - label for label in expected_work_item_labels if label["name"] != "beta:blocker" - ] - with self.assertRaisesRegex(ContinuityError, "does not match its configured work-id"): - close_authority_issue(writer, config, completion) # type: ignore[arg-type] - evidence_work_items[11]["labels"] = expected_work_item_labels - self.assertEqual([], writer.requests) - writer.fail_blocker_patch_once = True - with self.assertRaisesRegex(ContinuityError, "injected blocker close interruption"): - close_authority_issue(writer, config, completion) # type: ignore[arg-type] - first = close_authority_issue(writer, config, completion) # type: ignore[arg-type] - first_requests = list(writer.requests) - second = close_authority_issue(writer, config, completion) # type: ignore[arg-type] - - self.assertEqual(first, second) - self.assertEqual( - [ - ("POST", f"/repos/{repository}/issues/5/comments"), - ("PATCH", f"/repos/{repository}/issues/5"), - ("PATCH", f"/repos/{repository}/issues/5"), - ("POST", "/repos/durable-workflow/.github/issues/10/comments"), - ("PATCH", "/repos/durable-workflow/.github/issues/10"), - ("POST", "/repos/durable-workflow/.github/issues/11/comments"), - ("PATCH", "/repos/durable-workflow/.github/issues/11"), - ("POST", "/repos/durable-workflow/.github/issues/2/comments"), - ("PATCH", "/repos/durable-workflow/.github/issues/2"), - ], - first_requests, - ) - self.assertEqual(first_requests, writer.requests) - self.assertEqual("open", untrusted["state"]) - self.assertEqual("closed", mismatched["state"]) - self.assertEqual("closed", trusted["state"]) - self.assertTrue(all(item["state"] == "closed" for item in evidence_work_items.values())) - self.assertEqual("closed", parent["state"]) - self.assertEqual([5], [item["number"] for item in first["blockers"]]) - self.assertEqual([10, 11], [item["number"] for item in first["evidence_work_items"]]) - - def test_release_plan_callback_requires_and_dispatches_the_exact_public_acceptance(self) -> None: - plan = continuity_plan("workspace-unavailable-recovery-test") - acceptance = { - "commit": "a" * 40, - "pending_components": ["workflow"], - "public_components": {name: {} for name in COMPONENTS if name != "workflow"}, - "tag": f"beta-continuity/{plan['plan']}/accepted", - } - - class CallbackWriter: - def __init__(self) -> None: - self.runs: list[dict[str, object]] = [] - self.dispatches: list[tuple[str, str, str, dict[str, str]]] = [] - self.dispatch_error: ContinuityError | None = None - - def get(self, _path: str) -> dict[str, object]: - return {"workflow_runs": self.runs} - - def dispatch(self, repository: str, workflow: str, ref: str, inputs: dict[str, str]) -> None: - if self.dispatch_error is not None: - raise self.dispatch_error - self.dispatches.append((repository, workflow, ref, inputs)) - - writer = CallbackWriter() - with tempfile.TemporaryDirectory() as temporary: - plan_path = Path(temporary) / "release-plan.json" - plan_path.write_text(json.dumps(plan), encoding="utf-8") - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.accepted_plan_authority", return_value=acceptance), - patch("scripts.beta_continuity.GitHubWriter", return_value=writer), - patch.dict(os.environ, {"GITHUB_TOKEN": "token"}), - ): - dispatch_accepted_continuity(plan_path, None) - - expected_tag = f"release-plan/{plan['plan']}" - self.assertEqual( - [("durable-workflow/.github", "beta-continuity.yml", "main", {"plan_tag": expected_tag})], - writer.dispatches, - ) - - writer.runs.append( - { - "conclusion": None, - "display_title": f"Continue {expected_tag}", - "id": 101, - "status": "in_progress", - } - ) - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.accepted_plan_authority", return_value=acceptance), - patch("scripts.beta_continuity.GitHubWriter", return_value=writer), - patch.dict(os.environ, {"GITHUB_TOKEN": "token"}), - ): - dispatch_accepted_continuity(plan_path, None) - self.assertEqual(1, len(writer.dispatches)) - - writer.runs[-1].update({"conclusion": "success", "status": "completed"}) - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.accepted_plan_authority", return_value=acceptance), - patch("scripts.beta_continuity.GitHubWriter", return_value=writer), - patch.dict(os.environ, {"GITHUB_TOKEN": "token"}), - ): - dispatch_accepted_continuity(plan_path, None) - self.assertEqual(1, len(writer.dispatches)) - - writer.runs[-1].update({"conclusion": "failure", "status": "completed"}) - writer.dispatch_error = ContinuityError("GitHub dispatch failed (500)") - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.accepted_plan_authority", return_value=acceptance), - patch("scripts.beta_continuity.GitHubWriter", return_value=writer), - patch.dict(os.environ, {"GITHUB_TOKEN": "token"}), - self.assertRaisesRegex(ContinuityError, "dispatch failed"), - ): - dispatch_accepted_continuity(plan_path, None) - - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.accepted_plan_authority", return_value=None), - patch("scripts.beta_continuity.GitHubWriter") as writer_factory, - patch.dict(os.environ, {"GITHUB_TOKEN": "token"}), - ): - dispatch_accepted_continuity(plan_path, None) - writer_factory.assert_not_called() - - def test_public_acceptance_binds_the_callback_to_the_recorded_plan(self) -> None: - plan = continuity_plan("workspace-unavailable-recovery-test") - mutated = continuity_plan("workspace-unavailable-recovery-test") - mutated["components"]["workflow"]["commit"] = "f" * 40 - with ( - patch("scripts.beta_continuity.resolve_tag", return_value="a" * 40), - patch("scripts.beta_continuity.read_public_json_file", return_value=mutated), - self.assertRaisesRegex(ContinuityError, "differs from the recorded release plan"), - ): - accepted_plan_authority(object(), plan) # type: ignore[arg-type] - - def test_public_acceptance_rejects_a_tag_that_moves_during_callback_validation(self) -> None: - plan = continuity_plan("workspace-unavailable-recovery-test") - evidence = { - "schema": EVIDENCE_SCHEMA, - "phase": "accepted", - "outcome": "accepted", - "observed_at": "2026-07-20T10:00:00Z", - "release_plan": { - "tag": f"release-plan/{plan['plan']}", - "sha256": manifest_digest(plan), - }, - "candidate_identity": { - "components": plan["components"], - "plan_sha256": manifest_digest(plan), - }, - "public_components_at_acceptance": {}, - "pending_components_at_acceptance": list(COMPONENTS), - } - with ( - patch("scripts.beta_continuity.resolve_tag", side_effect=["a" * 40, "b" * 40]) as resolve, - patch("scripts.beta_continuity.read_public_json_file", side_effect=[plan, evidence]), - self.assertRaisesRegex(ContinuityError, "moved while the callback was validated"), - ): - accepted_plan_authority(object(), plan) # type: ignore[arg-type] - - self.assertEqual(2, resolve.call_count) - - def test_callback_plan_input_must_match_the_exact_accepted_identity(self) -> None: - plan = continuity_plan("workspace-unavailable-recovery-test") - issue = { - "number": 2, - "repository": "durable-workflow/.github", - "state": "open", - "work_id": "github-only-beta-continuity-drill", - } - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.authority_issue", return_value=issue), - patch("scripts.beta_continuity.accepted_plan", return_value=plan), - self.assertRaisesRegex(ContinuityError, "but exact accepted plan is"), - ): - plan_command( - ROOT / "beta-continuity" / "config.json", - root / "release-plan.json", - root / "expected.json", - root / "state.json", - None, - "release-plan/unrelated", - ) - - def test_only_post_acceptance_exact_plan_recovery_can_trigger_interruption(self) -> None: - plan_tag = "release-plan/workspace-unavailable-recovery-test" - acceptance = { - "observed_at": "2026-07-20T10:00:00Z", - "pending_components": [name for name in COMPONENTS if name != "sdk-python"], - "public_components": {"sdk-python": {"version": "0.4.103"}}, - } - published = { - "sdk-python": { - "published_at": "2026-07-20T09:00:00Z", - "version": "0.4.103", - }, - "workflow": { - "published_at": "2026-07-20T10:05:00Z", - "version": "2.0.0-alpha.292", - }, - } - - class RecoveryWriter: - created_at = "2026-07-20T10:01:00Z" - - def get(self, _path: str) -> dict[str, object]: - return { - "workflow_runs": [ - { - "conclusion": "success", - "created_at": self.created_at, - "display_title": f"Recover {plan_tag}", - "event": "workflow_dispatch", - "html_url": "https://github.com/durable-workflow/workflow/actions/runs/123", - "id": 123, - "status": "completed", - } - ] - } - - writer = RecoveryWriter() - triggers = recovery_publication_triggers(writer, plan_tag, acceptance, published) # type: ignore[arg-type] - self.assertEqual({"workflow"}, set(triggers)) - self.assertNotIn("sdk-python", triggers) - - writer.created_at = "2026-07-20T10:06:00Z" - self.assertEqual( - {}, - recovery_publication_triggers(writer, plan_tag, acceptance, published), # type: ignore[arg-type] - ) - - def test_invalid_immutable_interruption_requires_a_new_identity(self) -> None: - plan = continuity_plan() - acceptance = { - "commit": "a" * 40, - "observed_at": "2026-07-20T10:00:00Z", - "pending_components": ["workflow"], - "tag": "beta-continuity/continuity-test/accepted", - } - invalid = { - "accepted_phase": {"tag": acceptance["tag"], "commit": acceptance["commit"]}, - "phase": "interrupted", - } - with ( - patch( - "scripts.beta_continuity.read_public_json_file", - side_effect=[invalid, plan], - ), - self.assertRaisesRegex(ContinuityError, "new continuity identity"), - ): - validate_interrupted_evidence( - object(), # type: ignore[arg-type] - plan, - "b" * 40, - acceptance, - ) - - def test_completion_waits_for_a_later_scheduled_no_op_before_closing(self) -> None: - plan = continuity_plan("workspace-unavailable-recovery-test") - issue = { - "number": 2, - "repository": "durable-workflow/.github", - "state": "open", - "work_id": "github-only-beta-continuity-drill", - } - completion = { - "phase": "complete", - "observed_at": "2026-07-20T10:00:00Z", - "github_run": {"id": "100"}, - } - - def phase_commit(_client: object, _plan: object, phase: str) -> str | None: - return {"accepted": "a" * 40, "complete": "c" * 40}.get(phase) - - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - plan_path = root / "release-plan.json" - state_path = root / "state.json" - plan_path.write_text(json.dumps(plan), encoding="utf-8") - - class NoopWriter: - @staticmethod - def get(_path: str) -> dict[str, object]: - return { - "workflow_runs": [ - { - "conclusion": "success", - "created_at": "2026-07-20T10:10:00Z", - "event": "schedule", - "html_url": "https://github.com/durable-workflow/.github/actions/runs/102", - "id": 102, - "status": "completed", - } - ] - } - - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.GitHubWriter", return_value=NoopWriter()), - patch("scripts.beta_continuity.authority_issue", return_value=issue), - patch("scripts.beta_continuity.public_phase_commit", side_effect=phase_commit), - patch("scripts.beta_continuity.read_public_json_file", return_value=completion), - patch("scripts.beta_continuity.close_authority_issue") as close, - patch.dict( - os.environ, - {"GITHUB_EVENT_NAME": "workflow_dispatch", "GITHUB_RUN_ID": "101"}, - clear=False, - ), - ): - advance_command(ROOT / "beta-continuity" / "config.json", plan_path, state_path, None, None) - - self.assertEqual("waiting-for-subsequent-scheduled-no-op", json.loads(state_path.read_bytes())["outcome"]) - close.assert_not_called() - - with ( - patch("scripts.beta_continuity.PublicClient"), - patch("scripts.beta_continuity.GitHubWriter", return_value=NoopWriter()), - patch("scripts.beta_continuity.authority_issue", return_value=issue), - patch("scripts.beta_continuity.public_phase_commit", side_effect=phase_commit), - patch("scripts.beta_continuity.read_public_json_file", return_value=completion), - patch( - "scripts.beta_continuity.record_phase", - return_value={"tag": "beta-continuity/test/no-op-confirmed", "commit": "d" * 40}, - ) as record, - patch("scripts.beta_continuity.ensure_issue_comment"), - patch("scripts.beta_continuity.exact_completion_authority", return_value={}), - patch("scripts.beta_continuity.close_authority_issue") as close, - patch.dict( - os.environ, - {"GITHUB_EVENT_NAME": "schedule", "GITHUB_RUN_ID": "103"}, - clear=False, - ), - ): - advance_command(ROOT / "beta-continuity" / "config.json", plan_path, state_path, None, None) - - self.assertEqual("no-op-confirmed", record.call_args.args[2]) - close.assert_called_once() - - def test_failed_first_recovery_is_retried_once_without_duplicate_active_or_successful_runs(self) -> None: - plan_tag = "release-plan/workspace-unavailable-20260720" - - class RecoveryWriter: - def __init__(self) -> None: - self.runs = [ - { - "id": 101, - "display_title": f"Recover {plan_tag}", - "status": "completed", - "conclusion": "failure", - } - ] - self.dispatches: list[tuple[str, str, str, dict[str, str]]] = [] - - def get(self, _path: str) -> dict[str, object]: - return {"workflow_runs": self.runs} - - def dispatch(self, repository: str, workflow: str, ref: str, inputs: dict[str, str]) -> None: - self.dispatches.append((repository, workflow, ref, inputs)) - - writer = RecoveryWriter() - - dispatch_recovery(writer, "workflow", plan_tag) # type: ignore[arg-type] - self.assertEqual(1, len(writer.dispatches)) - - writer.runs.append( - { - "id": 102, - "display_title": f"Recover {plan_tag}", - "status": "queued", - "conclusion": None, - } - ) - dispatch_recovery(writer, "workflow", plan_tag) # type: ignore[arg-type] - self.assertEqual(1, len(writer.dispatches)) - - writer.runs[-1]["status"] = "in_progress" - dispatch_recovery(writer, "workflow", plan_tag) # type: ignore[arg-type] - self.assertEqual(1, len(writer.dispatches)) - - writer.runs[-1].update({"status": "completed", "conclusion": "success"}) - dispatch_recovery(writer, "workflow", plan_tag) # type: ignore[arg-type] - self.assertEqual(1, len(writer.dispatches)) - - require_partial_publication( - {"workflow": {"version": "2.0.0-alpha.292"}}, - [name for name in COMPONENTS if name != "workflow"], - ) - - def test_completed_conformance_recovers_artifacts_without_rerunning_the_matrix(self) -> None: - candidate = candidate_manifest(continuity_plan("workspace-unavailable-recovery-test")) - - class ConformanceWriter: - def __init__(self) -> None: - self.source_runs: list[dict[str, object]] = [] - self.retention_runs: list[dict[str, object]] = [] - self.dispatches: list[tuple[str, str, str, dict[str, str]]] = [] - - def get(self, path: str) -> dict[str, object]: - if "beta-conformance-retention.yml" in path: - return {"workflow_runs": self.retention_runs} - if "beta-conformance.yml" in path: - return {"workflow_runs": self.source_runs} - raise AssertionError(path) - - def dispatch(self, repository: str, workflow: str, ref: str, inputs: dict[str, str]) -> None: - self.dispatches.append((repository, workflow, ref, inputs)) - - writer = ConformanceWriter() - progress = ensure_conformance_execution_or_retention(writer, candidate) # type: ignore[arg-type] - self.assertEqual("conformance-execution-requested", progress["outcome"]) - self.assertEqual("beta-conformance.yml", writer.dispatches[-1][1]) - self.assertEqual("none", writer.dispatches[-1][3]["injected_canary_failure_experiment"]) - - writer.dispatches.clear() - writer.source_runs.append( - { - "conclusion": "failure", - "display_title": f"Conformance {candidate['candidate']}", - "html_url": "https://github.com/durable-workflow/.github/actions/runs/29775218461", - "id": 29775218461, - "run_attempt": 1, - "status": "completed", - } - ) - progress = ensure_conformance_execution_or_retention(writer, candidate) # type: ignore[arg-type] - - self.assertEqual("conformance-retention-requested", progress["outcome"]) - self.assertEqual( - [ - ( - "durable-workflow/.github", - "beta-conformance-retention.yml", - "main", - {"source_run_attempt": "1", "source_run_id": "29775218461"}, - ) - ], - writer.dispatches, - ) - - writer.retention_runs.append( - { - "conclusion": "success", - "display_title": "Retain conformance 29775218461.1", - "event": "workflow_run", - "id": 29780000000, - "run_attempt": 1, - "status": "completed", - } - ) - ensure_conformance_execution_or_retention(writer, candidate) # type: ignore[arg-type] - self.assertEqual(1, len(writer.dispatches)) - - writer.dispatches.clear() - writer.retention_runs = [ - { - "conclusion": "failure", - "display_title": "Retain conformance 29775218461.1", - "event": "workflow_run", - "id": 29780000001, - "run_attempt": 1, - "status": "completed", - } - ] - progress = ensure_conformance_execution_or_retention(writer, candidate) # type: ignore[arg-type] - self.assertEqual("conformance-retention-requested", progress["outcome"]) - self.assertEqual(1, len(writer.dispatches)) - self.assertEqual("beta-conformance-retention.yml", writer.dispatches[0][1]) - - writer.retention_runs.append( - { - "conclusion": "failure", - "display_title": "Retain conformance 29775218461.1", - "event": "workflow_dispatch", - "id": 29780000002, - "run_attempt": 1, - "status": "completed", - } - ) - writer.dispatches.clear() - progress = ensure_conformance_execution_or_retention(writer, candidate) # type: ignore[arg-type] - self.assertEqual("conformance-retention-terminal-failure", progress["outcome"]) - self.assertEqual([], writer.dispatches) - - def test_unsafe_recorded_conformance_release_is_replaced_by_the_earliest_newer_valid_release(self) -> None: - plan = continuity_plan("existing-release-safety-test") - unsafe_release, unsafe_payloads, unsafe_reference = conformance_release_fixture( - plan, - 12345, - sensitive=True, - ) - replacement_release, replacement_payloads, replacement_reference = conformance_release_fixture(plan, 12346) - later_release, later_payloads, _later_reference = conformance_release_fixture( - plan, - 12346, - run_attempt=2, - ) - unrelated = [{"draft": False, "tag_name": f"unrelated-release-{index}"} for index in range(98)] - client = PaginatedReleaseClient( - {1: [later_release, unsafe_release, *unrelated], 2: [replacement_release]}, - unsafe_payloads | replacement_payloads | later_payloads, - ) - - self.assertEqual( - replacement_reference, - conformance_evidence( - client, # type: ignore[arg-type] - plan, - preferred=unsafe_reference, - ), - ) - self.assertEqual([1, 2], client.requested_pages) - - def test_valid_recorded_conformance_release_is_preserved_beyond_the_first_release_page(self) -> None: - plan = historical_continuity_plan() - preferred_release, preferred_payloads, preferred_reference = conformance_release_fixture( - plan, - 12346, - legacy=True, - ) - later_release, later_payloads, _later_reference = conformance_release_fixture(plan, 12347) - unrelated = [{"draft": False, "tag_name": f"unrelated-release-{index}"} for index in range(99)] - client = PaginatedReleaseClient( - {1: [later_release, *unrelated], 2: [preferred_release]}, - preferred_payloads | later_payloads, - ) - - self.assertEqual( - preferred_reference, - conformance_evidence(client, plan, preferred=preferred_reference), # type: ignore[arg-type] - ) - self.assertEqual([1, 2], client.requested_pages) - - def test_legacy_conformance_is_only_selected_for_the_exact_recorded_reference(self) -> None: - plan = continuity_plan("legacy-conformance-selector-boundary-test") - legacy_release, legacy_payloads, _legacy_reference = conformance_release_fixture( - plan, - 12345, - legacy=True, - ) - replacement_release, replacement_payloads, replacement_reference = conformance_release_fixture( - plan, - 12346, - ) - _missing_release, _missing_payloads, missing_reference = conformance_release_fixture(plan, 12344) - - class ReleaseClient: - def __init__(self, releases: list[dict[str, Any]], payloads: dict[str, bytes]) -> None: - self.releases = releases - self.payloads = payloads - - def json(self, _url: str) -> list[dict[str, Any]]: - return self.releases - - def bytes(self, url: str) -> bytes: - return self.payloads[url] - - legacy_client = ReleaseClient([legacy_release], legacy_payloads) - self.assertIsNone(conformance_evidence(legacy_client, plan)) # type: ignore[arg-type] - self.assertIsNone( - conformance_evidence( - legacy_client, # type: ignore[arg-type] - plan, - preferred=_legacy_reference, - ) - ) - - client = ReleaseClient( - [replacement_release, legacy_release], - legacy_payloads | replacement_payloads, - ) - self.assertEqual( - replacement_reference, - conformance_evidence(client, plan), # type: ignore[arg-type] - ) - self.assertEqual( - replacement_reference, - conformance_evidence(client, plan, preferred=missing_reference), # type: ignore[arg-type] - ) - - historical = legacy_beta_one_release_plan() - historical_release, historical_payloads, historical_reference = conformance_release_fixture( - historical, - 12347, - legacy=True, - ) - self.assertEqual( - historical_reference, - conformance_evidence( - ReleaseClient([historical_release], historical_payloads), # type: ignore[arg-type] - historical, - preferred=historical_reference, - ), - ) - - unrecorded = deepcopy(historical) - unrecorded["plan"] = "arbitrary-new-v1-plan" - with self.assertRaisesRegex(CandidateError, "not an exact recorded historical contract"): - conformance_evidence(ReleaseClient([], {}), unrecorded) # type: ignore[arg-type] - - def test_missing_recorded_conformance_release_uses_only_the_earliest_newer_run_attempt(self) -> None: - plan = continuity_plan("missing-conformance-authority-test") - older_release, older_payloads, _older_reference = conformance_release_fixture(plan, 12344) - _missing_release, _missing_payloads, missing_reference = conformance_release_fixture(plan, 12345) - replacement_release, replacement_payloads, replacement_reference = conformance_release_fixture(plan, 12346) - later_release, later_payloads, _later_reference = conformance_release_fixture( - plan, - 12346, - run_attempt=2, - ) - client = PaginatedReleaseClient( - {1: [later_release, older_release, replacement_release]}, - older_payloads | replacement_payloads | later_payloads, - ) - - self.assertEqual( - replacement_reference, - conformance_evidence(client, plan, preferred=missing_reference), # type: ignore[arg-type] - ) - - def test_multiple_valid_conformance_releases_preserve_recorded_authority(self) -> None: - plan = continuity_plan("stable-conformance-authority-test") - first_release, first_payloads, first_reference = conformance_release_fixture(plan, 12346) - later_release, later_payloads, _later_reference = conformance_release_fixture(plan, 12347) - - class MultipleReleaseClient: - def __init__(self, releases: list[dict[str, Any]]) -> None: - self.releases = releases - - def json(self, _url: str) -> list[dict[str, Any]]: - return self.releases - - @staticmethod - def bytes(url: str) -> bytes: - return (first_payloads | later_payloads)[url] - - for releases in ([later_release, first_release], [first_release, later_release]): - with self.subTest(releases=[release["tag_name"] for release in releases]): - client = MultipleReleaseClient(releases) - self.assertEqual(first_reference, conformance_evidence(client, plan)) # type: ignore[arg-type] - self.assertEqual( - first_reference, - conformance_evidence(client, plan, preferred=first_reference), # type: ignore[arg-type] - ) - - def test_conformance_release_requires_exact_runtime_dependency_evidence(self) -> None: - plan = continuity_plan("runtime-dependency-binding-test") - release, payloads, reference = conformance_release_fixture(plan, 12348) - - class ReleaseClient: - def __init__(self, retained_payloads: dict[str, bytes]) -> None: - self.retained_payloads = retained_payloads - - @staticmethod - def json(_url: str) -> list[dict[str, Any]]: - return [release] - - def bytes(self, url: str) -> bytes: - return self.retained_payloads[url] - - self.assertEqual(reference, conformance_evidence(ReleaseClient(payloads), plan)) # type: ignore[arg-type] - - suite_url = "https://downloads.example/12348/suite-result.json" - missing_payloads = dict(payloads) - missing_suite = json.loads(missing_payloads[suite_url]) - del missing_suite["runtime_dependencies"] - missing_payloads[suite_url] = json.dumps(missing_suite, sort_keys=True, separators=(",", ":")).encode() - self.assertIsNone(conformance_evidence(ReleaseClient(missing_payloads), plan)) # type: ignore[arg-type] - - drifted_payloads = dict(payloads) - experiment = CONFORMANCE_EXPERIMENTS[0] - experiment_url = f"https://downloads.example/12348/{experiment}.json" - drifted_result = json.loads(drifted_payloads[experiment_url]) - drifted_digest = f"sha256:{88:064x}" - drifted_result["runtime_dependencies"]["mysql"].update( - { - "image": f"docker.io/library/mysql@{drifted_digest}", - "manifest_digest": drifted_digest, - } - ) - drifted_payloads[experiment_url] = json.dumps( - drifted_result, - sort_keys=True, - separators=(",", ":"), - ).encode() - drifted_suite = json.loads(drifted_payloads[suite_url]) - drifted_suite["experiments"][experiment]["result_sha256"] = hashlib.sha256( - drifted_payloads[experiment_url] - ).hexdigest() - drifted_payloads[suite_url] = json.dumps( - drifted_suite, - sort_keys=True, - separators=(",", ":"), - ).encode() - self.assertIsNone(conformance_evidence(ReleaseClient(drifted_payloads), plan)) # type: ignore[arg-type] - - def test_phase_records_are_append_only_and_idempotent(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - remote = root / "remote.git" - checkout = root / "checkout" - run(["git", "init", "--bare", str(remote)], root) - run(["git", "clone", str(remote), str(checkout)], root) - plan = continuity_plan() - evidence = { - "schema": EVIDENCE_SCHEMA, - "drill": "continuity-test", - "phase": "interrupted", - "observed_at": "2026-07-20T00:00:00Z", - } - - first = record_phase(checkout, plan, "interrupted", evidence) - second = record_phase(checkout, plan, "interrupted", evidence) - - self.assertEqual("created", first["status"]) - self.assertEqual("existing", second["status"]) - self.assertEqual(first["commit"], second["commit"]) - self.assertEqual( - first["commit"], - run( - ["git", "ls-remote", "--refs", "origin", f"refs/tags/{phase_tag(plan, 'interrupted')}"], - checkout, - ).split()[0], - ) - recorded = json.loads( - run( - [ - "git", - "show", - "refs/beta-candidate-check/" - + hashlib.sha256(phase_tag(plan, "interrupted").encode()).hexdigest() - + ":continuity-evidence.json", - ], - checkout, - ) - ) - self.assertEqual(evidence, recorded) - - def test_continuity_resolution_record_is_append_only_and_digest_named(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - remote = root / "remote.git" - checkout = root / "checkout" - run(["git", "init", "--bare", str(remote)], root) - run(["git", "clone", str(remote), str(checkout)], root) - selection_path = ROOT / "release-plans" / "continuity-successor-selection.json" - selection = json.loads(selection_path.read_text(encoding="utf-8")) - qualification = { - "repository": "durable-workflow/.github", - "workflow": ".github/workflows/beta-candidate.yml", - "event": "push", - "head_branch": "main", - "head_sha": "9" * 40, - "run_id": 987, - "run_attempt": 2, - "status": "completed", - "conclusion": "success", - } - resolution = { - **selection, - "schema": "durable-workflow.release-plan-continuity-resolution/v2", - "qualification": qualification, - } - public_client = Mock() - public_client.json.return_value = [] - - with ( - patch( - "scripts.beta_continuity.qualified_resolution_source", - return_value=qualification, - ), - patch( - "scripts.beta_continuity.validate_continuity_resolution_authority", - return_value=resolution, - ), - patch( - "scripts.beta_continuity.PublicClient", - return_value=public_client, - ), - ): - first = record_continuity_resolution( - checkout, - selection_path, - source_sha=qualification["head_sha"], - run_id=qualification["run_id"], - run_attempt=qualification["run_attempt"], - ) - second = record_continuity_resolution( - checkout, - selection_path, - source_sha=qualification["head_sha"], - run_id=qualification["run_id"], - run_attempt=qualification["run_attempt"], - ) - - self.assertEqual("created", first["status"]) - self.assertEqual("existing", second["status"]) - self.assertEqual(first["commit"], second["commit"]) - self.assertTrue(first["tag"].endswith(manifest_digest(resolution))) - self.assertEqual( - first["commit"], - run( - ["git", "ls-remote", "--refs", "origin", f"refs/tags/{first['tag']}"], - checkout, - ).split()[0], - ) - - def test_continuity_resolution_source_requires_completed_candidate_push(self) -> None: - workflow = { - "id": 41, - "name": "Beta candidate", - "path": ".github/workflows/beta-candidate.yml", - "state": "active", - } - run = { - "id": 987, - "run_attempt": 2, - "workflow_id": workflow["id"], - "repository": {"full_name": "durable-workflow/.github"}, - "head_repository": {"full_name": "durable-workflow/.github"}, - "path": ".github/workflows/beta-candidate.yml@main", - "event": "push", - "head_branch": "main", - "head_sha": "9" * 40, - "status": "completed", - "conclusion": "success", - } - source = validate_resolution_source( - run, - workflow, - expected_run_id=987, - expected_run_attempt=2, - ) - self.assertEqual("9" * 40, source["head_sha"]) - failures = ( - ("absent", None, "qualification is absent"), - ("pending", {**run, "status": "in_progress", "conclusion": None}, "qualification is pending"), - ("failed", {**run, "conclusion": "failure"}, "qualification failed"), - ("cancelled", {**run, "conclusion": "cancelled"}, "qualification was cancelled"), - ("mismatched SHA", {**run, "head_sha": "invalid"}, "invalid source revision"), - ( - "untrusted workflow", - {**run, "path": ".github/workflows/untrusted.yml@main"}, - "untrusted workflow", - ), - ) - for label, candidate_run, message in failures: - with self.subTest(label=label), self.assertRaisesRegex(ContinuityError, message): - validate_resolution_source( - candidate_run, - workflow, - expected_run_id=987, - expected_run_attempt=2, - ) - - def test_workflow_is_scheduled_and_uses_protected_github_authority(self) -> None: - source = (ROOT / ".github" / "workflows" / "beta-continuity.yml").read_text(encoding="utf-8") - retention = ( - ROOT / ".github" / "workflows" / "beta-continuity-resolution.yml" - ).read_text(encoding="utf-8") - - self.assertIn("schedule:", source) - self.assertIn("plan_tag:", source) - self.assertIn("environment: beta-product-work", source) - self.assertIn("--expected-plan-tag", source) - self.assertIn("scripts/beta_continuity.py advance", source) - self.assertIn("scripts/beta_continuity.py route-blockers", source) - self.assertNotIn("scripts/beta_continuity.py record-resolution", source) - self.assertIn("workflow_run:", retention) - self.assertIn("workflows: [Beta candidate]", retention) - self.assertIn("scripts/beta_continuity.py resolution-source", retention) - self.assertIn("scripts/beta_continuity.py record-resolution", retention) - self.assertIn("release-plans/continuity-successor-selection.json", retention) - self.assertIn("needs.bind.outputs.source_head_sha", retention) - self.assertIn("if: ${{ steps.plan.outcome == 'success' }}", source) - self.assertNotIn("run: exit 1", source) - self.assertNotIn("/workspace", source) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_cli_release_authority.py b/tests/test_cli_release_authority.py deleted file mode 100644 index 5cbe1c9..0000000 --- a/tests/test_cli_release_authority.py +++ /dev/null @@ -1,711 +0,0 @@ -from __future__ import annotations - -import base64 -import copy -import hashlib -import os -import subprocess -import tempfile -import unittest -from pathlib import Path -from types import ModuleType -from unittest import mock - -import yaml - -from scripts import beta_candidate, component_release_recovery -from scripts.beta_candidate import CLI_ASSETS, CandidateError, canonical_cli_embedded_identity -from scripts.component_release_recovery import RecoveryError -from scripts.release_plan import ( - require_distribution_identity, - revalidate_conflict_public_evidence, -) - -VERIFIERS = ( - (beta_candidate, CandidateError), - (component_release_recovery, RecoveryError), -) -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] - - -def inspected_build_info(version: str, commit: str) -> str: - source = ( - " None: - self.version = version - self.contents = {name: f"fixture {name}\n".encode() for name in CLI_ASSETS - {"SHA256SUMS"}} - self.checksums = "".join( - f"{hashlib.sha256(content).hexdigest()} {name}\n" for name, content in sorted(self.contents.items()) - ).encode() - - def json(self, _url: str) -> dict[str, object]: - return { - "id": 123, - "tag_name": self.version, - "draft": False, - "html_url": f"https://github.com/durable-workflow/cli/releases/tag/{self.version}", - "assets": [ - { - "id": index, - "name": name, - "browser_download_url": f"https://example.invalid/{name}", - } - for index, name in enumerate(sorted(CLI_ASSETS), start=1) - ], - } - - def bytes(self, _url: str) -> bytes: - return self.checksums - - def download(self, url: str, path: Path, *, expected_sha256: str) -> dict[str, object]: - content = self.contents[url.rsplit("/", 1)[-1]] - if expected_sha256 != hashlib.sha256(content).hexdigest(): - raise AssertionError("fixture download checksum mismatch") - path.write_bytes(content) - return {"url": url, "size": len(content), "sha256": expected_sha256} - - -def verify_cli_release( - module: ModuleType, - client: CliReleaseFixtureClient, - version: str, - commit: str, -) -> dict[str, object]: - if module is beta_candidate: - with tempfile.TemporaryDirectory() as temporary: - return module.verify_github_release( - client, - module.COMPONENTS["cli"], - version, - commit, - Path(temporary), - ) - return module.verify_cli(client, module.COMPONENTS["cli"], version, commit) - - -class CliReleaseAuthorityTest(unittest.TestCase): - def test_verifiers_accept_qualified_main_authority_and_isolate_phar_execution(self) -> None: - version = "0.1.94" - commit = "36bde75882980e834854a145c9ad0f61ceec4659" - allowed_path = "/opt/php/bin:/usr/bin" - - for module, _error_type in VERIFIERS: - with self.subTest(module=module.__name__): - calls: list[tuple[list[str], dict[str, object]]] = [] - - def run( - command: list[str], - calls: list[tuple[list[str], dict[str, object]]] = calls, - current_module: ModuleType = module, - **kwargs: object, - ) -> subprocess.CompletedProcess[str]: - calls.append((command, kwargs)) - if command[0] == "php": - return subprocess.CompletedProcess( - command, - 0, - stdout=( - inspected_build_info(version, commit) - if current_module is beta_candidate - else f"dw {version} (commit {commit[:12]}, built 2026-07-20)" - ), - stderr="", - ) - if "--source-digest" in command: - return subprocess.CompletedProcess(command, 1, stdout="", stderr="not exact-tag authority") - return subprocess.CompletedProcess(command, 0, stdout="verified", stderr="") - - with ( - mock.patch.dict( - os.environ, - {"PATH": allowed_path, "GH_TOKEN": "secret", "DATABASE_URL": "secret"}, - clear=False, - ), - mock.patch.object(module.shutil, "which", return_value="/usr/bin/tool"), - mock.patch.object(module.subprocess, "run", side_effect=run), - ): - evidence = verify_cli_release(module, CliReleaseFixtureClient(version), version, commit) - - attestations = [command for command, _kwargs in calls if command[0] == "gh"] - main_attestations = [command for command in attestations if "--signer-workflow" in command] - self.assertEqual(len(CLI_ASSETS) + 1, len(attestations)) - self.assertEqual(len(CLI_ASSETS), len(main_attestations)) - for command in main_attestations: - self.assertEqual("refs/heads/main", command[command.index("--source-ref") + 1]) - self.assertEqual( - "durable-workflow/cli/.github/workflows/release.yml", - command[command.index("--signer-workflow") + 1], - ) - self.assertNotIn("--source-digest", command) - self.assertEqual( - { - "mode": "qualified-main-workflow", - "ref": "refs/heads/main", - "workflow": "durable-workflow/cli/.github/workflows/release.yml", - }, - evidence["build_attestation_authority"], - ) - self.assertEqual(commit, evidence["package_source"]["commit"]) - if module is beta_candidate: - self.assertEqual( - canonical_cli_embedded_identity(version, commit), - evidence["package_source"]["embedded_phar_identity"], - ) - self.assertEqual({"PATH": allowed_path}, calls[-1][1]["env"]) - phar_argument = calls[-1][0][-1] if module is beta_candidate else calls[-1][0][1] - self.assertEqual(Path(phar_argument).parent, calls[-1][1]["cwd"]) - - def test_control_plane_cli_verifiers_are_isolated_from_write_authority(self) -> None: - workflows = { - "beta-candidate.yml": { - "verification": "python scripts/beta_candidate.py verify", - "mutation": "python scripts/beta_candidate.py record", - "handoff": { - "candidate.json", - "verification.json", - "handoff.json", - }, - "recoverable": True, - }, - "release-plan-observer.yml": { - "verification": "python scripts/release_plan.py observe", - "mutation": "python scripts/release_plan.py complete", - "handoff": { - "release-plan.json", - "release-preparation.json", - "candidate-verifier-input.json", - "release-state.json", - "verification.json", - "handoff.json", - }, - "recoverable": True, - }, - "release-plan-supersession.yml": { - "verification": "python scripts/release_plan.py prepare-supersession", - "mutation": "python scripts/release_plan.py record-supersession", - "handoff": { - "release-plan-failure.json", - "authoritative-successor-release-plan.json", - }, - "recoverable": False, - }, - } - - self.assertEqual( - { - "beta-candidate.yml", - "release-plan-observer.yml", - "release-plan-supersession.yml", - }, - set(workflows), - ) - for filename, contract in workflows.items(): - with self.subTest(workflow=filename): - source = (REPOSITORY_ROOT / ".github" / "workflows" / filename).read_text(encoding="utf-8") - workflow = yaml.safe_load(source) - self.assertEqual({}, workflow["permissions"]) - jobs = workflow["jobs"] - for job_name, job in jobs.items(): - step_ids = [step["id"] for step in job["steps"] if "id" in step] - self.assertEqual( - len(step_ids), - len(set(step_ids)), - f"{filename} job {job_name} has duplicate step IDs", - ) - verification_name, verification_job = next( - (name, job) - for name, job in jobs.items() - if any(contract["verification"] in step.get("run", "") for step in job["steps"]) - ) - mutation_name, mutation_job = next( - (name, job) - for name, job in jobs.items() - if any(contract["mutation"] in step.get("run", "") for step in job["steps"]) - ) - self.assertNotEqual(verification_name, mutation_name) - self.assertEqual("read", verification_job["permissions"]["contents"]) - self.assertNotIn("write", verification_job["permissions"].values()) - self.assertEqual("write", mutation_job["permissions"]["contents"]) - mutation_needs = ( - {mutation_job["needs"]} if isinstance(mutation_job["needs"], str) else set(mutation_job["needs"]) - ) - self.assertIn(verification_name, mutation_needs) - - for job in (verification_job, mutation_job): - checkouts = [ - step for step in job["steps"] if str(step.get("uses", "")).startswith("actions/checkout@") - ] - self.assertGreaterEqual(len(checkouts), 1) - for checkout in checkouts: - self.assertEqual( - "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803", - checkout["uses"], - ) - self.assertIs(checkout["with"]["persist-credentials"], False) - self.assertEqual("${{ github.sha }}", checkout["with"]["ref"]) - - verification = next( - step for step in verification_job["steps"] if contract["verification"] in step.get("run", "") - ) - mutation = next(step for step in mutation_job["steps"] if contract["mutation"] in step.get("run", "")) - upload = next( - step - for step in verification_job["steps"] - if step.get("uses") - == "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" - and contract["handoff"].issubset(set(step["with"].get("path", "").splitlines())) - ) - bound_upload = next( - step for step in verification_job["steps"] if step.get("id") == "privileged-handoff" - ) - download = next( - step - for step in mutation_job["steps"] - if step.get("uses") - == "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" - ) - digest_validator = mutation_job["steps"][mutation_job["steps"].index(download) + 1] - - self.assertEqual( - "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a", - bound_upload["uses"], - ) - self.assertIs(bound_upload["with"]["archive"], False) - self.assertEqual("error", bound_upload["with"]["if-no-files-found"]) - self.assertRegex(bound_upload["with"]["path"], r"^[a-z0-9][a-z0-9._-]*\.tar$") - self.assertEqual( - "${{ steps.privileged-handoff.outputs.artifact-id }}", - verification_job["outputs"]["artifact-id"], - ) - self.assertEqual( - "${{ steps.privileged-handoff.outputs.artifact-digest }}", - verification_job["outputs"]["artifact-digest"], - ) - self.assertEqual("${{ github.run_id }}", verification_job["outputs"]["source-run-id"]) - self.assertEqual("${{ github.run_attempt }}", verification_job["outputs"]["source-run-attempt"]) - self.assertEqual( - f"${{{{ needs.{verification_name}.outputs.artifact-id }}}}", - download["with"]["artifact-ids"], - ) - self.assertEqual("error", download["with"]["digest-mismatch"]) - self.assertEqual("${{ github.token }}", download["with"]["github-token"]) - self.assertEqual("${{ github.repository }}", download["with"]["repository"]) - self.assertEqual( - f"${{{{ needs.{verification_name}.outputs.source-run-id }}}}", - download["with"]["run-id"], - ) - self.assertEqual("Validate the exact producer artifact before use", digest_validator["name"]) - self.assertEqual( - f"${{{{ needs.{verification_name}.outputs.artifact-digest }}}}", - digest_validator["env"]["EXPECTED_ARTIFACT_DIGEST"], - ) - self.assertLess(mutation_job["steps"].index(digest_validator), mutation_job["steps"].index(mutation)) - - if contract["recoverable"]: - validator = next( - step for step in mutation_job["steps"] if "handoff_recovery.py validate" in step.get("run", "") - ) - producer = next( - step - for step in verification_job["steps"] - if "handoff_recovery.py create" in step.get("run", "") - ) - self.assertEqual("read", mutation_job["permissions"]["actions"]) - self.assertIn("${{ github.run_attempt }}", upload["with"]["name"]) - self.assertEqual(30, upload["with"]["retention-days"]) - self.assertNotIn("name", download["with"]) - self.assertIn( - f"${{{{ needs.{verification_name}.outputs.handoff-attempt }}}}", - validator["run"], - ) - self.assertEqual( - "${{ steps.handoff.outputs.producer_attempt }}", - verification_job["outputs"]["handoff-attempt"], - ) - self.assertIn("--producer-attempt", validator["run"]) - self.assertIn("--workflow-ref", producer["run"]) - probe = next( - step for step in mutation_job["steps"] if "recorder-only recovery" in step.get("name", "") - ) - self.assertEqual( - "${{ inputs.recorder_recovery_probe && github.run_attempt == 1 }}", - probe["if"], - ) - self.assertLess(mutation_job["steps"].index(validator), mutation_job["steps"].index(probe)) - self.assertLess(mutation_job["steps"].index(probe), mutation_job["steps"].index(mutation)) - self.assertEqual( - contract["handoff"], - set(upload["with"]["path"].splitlines()), - ) - self.assertIn( - "GIT_CONFIG_KEY_0=http.https://github.com/.extraheader", - mutation["run"], - ) - self.assertIn( - 'GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $git_authorization"', - mutation["run"], - ) - self.assertEqual("${{ github.token }}", mutation["env"]["GITHUB_TOKEN"]) - for token_name in ("GITHUB_TOKEN", "GH_TOKEN"): - if token_name in verification.get("env", {}): - self.assertEqual("${{ github.token }}", verification["env"][token_name]) - - def test_observer_writer_binds_mutation_to_trusted_plan_discovery(self) -> None: - workflow = yaml.safe_load( - (REPOSITORY_ROOT / ".github/workflows/release-plan-observer.yml").read_text(encoding="utf-8") - ) - observe = workflow["jobs"]["observe"] - record = workflow["jobs"]["record"] - trusted_plan_tag = "${{ needs.observe.outputs.plan-tag }}" - trusted_plan_sha256 = "${{ needs.observe.outputs.plan-sha256 }}" - trusted_preparation_sha256 = "${{ needs.observe.outputs.preparation-sha256 }}" - trusted_verification_outcome = "${{ needs.observe.outputs.verification-outcome }}" - self.assertEqual("${{ steps.plan.outputs.tag }}", observe["outputs"]["plan-tag"]) - self.assertEqual("read", observe["permissions"]["attestations"]) - self.assertNotIn("write", observe["permissions"].values()) - self.assertEqual("${{ steps.plan.outputs.plan_sha256 }}", observe["outputs"]["plan-sha256"]) - self.assertEqual( - "${{ steps.plan.outputs.preparation_sha256 }}", - observe["outputs"]["preparation-sha256"], - ) - - rediscovery = next(step for step in record["steps"] if "release_plan.py discover" in step.get("run", "")) - handoff = next( - step for step in record["steps"] if "release_plan.py validate-observation-handoff" in step.get("run", "") - ) - upload = next(step for step in record["steps"] if "gh release upload" in step.get("run", "")) - self.assertEqual(trusted_plan_tag, rediscovery["env"]["EXPECTED_PLAN_TAG"]) - self.assertIn('--tag "$EXPECTED_PLAN_TAG"', rediscovery["run"]) - self.assertEqual(trusted_plan_tag, handoff["env"]["EXPECTED_PLAN_TAG"]) - self.assertEqual(trusted_plan_sha256, handoff["env"]["EXPECTED_PLAN_SHA256"]) - self.assertEqual( - trusted_preparation_sha256, - handoff["env"]["EXPECTED_PREPARATION_SHA256"], - ) - self.assertEqual( - trusted_verification_outcome, - handoff["env"]["EXPECTED_VERIFICATION_OUTCOME"], - ) - self.assertEqual("${{ github.token }}", handoff["env"]["GH_TOKEN"]) - self.assertEqual("${{ github.token }}", handoff["env"]["GITHUB_TOKEN"]) - self.assertEqual("read", record["permissions"]["attestations"]) - for argument in ( - "--authoritative-plan", - "--authoritative-preparation", - "--expected-plan-tag", - "--expected-plan-sha256", - "--expected-preparation-sha256", - "--expected-verification-outcome", - ): - self.assertIn(argument, handoff["run"]) - self.assertEqual(trusted_plan_tag, upload["env"]["PLAN_TAG"]) - self.assertNotIn("steps.handoff.outputs", upload["env"]["PLAN_TAG"]) - self.assertLess(record["steps"].index(rediscovery), record["steps"].index(handoff)) - self.assertLess(record["steps"].index(handoff), record["steps"].index(upload)) - - def test_scheduled_observer_stops_cleanly_without_an_actionable_plan(self) -> None: - workflow = yaml.safe_load( - (REPOSITORY_ROOT / ".github/workflows/release-plan-observer.yml").read_text(encoding="utf-8") - ) - observe = workflow["jobs"]["observe"] - record = workflow["jobs"]["record"] - discover = next(step for step in observe["steps"] if step.get("id") == "plan") - no_op = next( - step - for step in observe["steps"] - if step.get("name") == "Report truthful scheduled no-op" - ) - package = next( - step - for step in observe["steps"] - if step.get("name") == "Package the privileged recorder handoff as one immutable file" - ) - upload = next( - step - for step in observe["steps"] - if step.get("id") == "privileged-handoff" - ) - - self.assertIn("arguments+=(--allow-empty)", discover["run"]) - self.assertEqual("steps.plan.outputs.available == 'false'", no_op["if"]) - self.assertEqual("${{ steps.plan.outputs.available }}", package["env"]["PLAN_AVAILABLE"]) - self.assertIn('if [ "$PLAN_AVAILABLE" = true ]; then', package["run"]) - self.assertIn("--files-from /dev/null", package["run"]) - self.assertNotIn("if", upload) - self.assertIn("needs.observe.outputs.plan-available == 'true'", record["if"]) - - def test_supersession_writer_binds_mutation_to_dispatch_authority(self) -> None: - workflow = yaml.safe_load( - (REPOSITORY_ROOT / ".github/workflows/release-plan-supersession.yml").read_text(encoding="utf-8") - ) - steps = workflow["jobs"]["record"]["steps"] - handoff = next(step for step in steps if "release_plan.py validate-supersession-handoff" in step.get("run", "")) - mutation = next(step for step in steps if "release_plan.py record-supersession" in step.get("run", "")) - - self.assertEqual("${{ inputs.failed_plan_tag }}", handoff["env"]["FAILED_PLAN_TAG"]) - self.assertEqual( - "${{ inputs.conflicting_components }}", - handoff["env"]["CONFLICTING_COMPONENTS"], - ) - self.assertNotIn("GITHUB_TOKEN", handoff.get("env", {})) - self.assertNotIn("GH_TOKEN", handoff.get("env", {})) - self.assertIn('--expected-failed-plan-tag "$FAILED_PLAN_TAG"', handoff["run"]) - self.assertIn( - '--expected-conflict-components "$CONFLICTING_COMPONENTS"', - handoff["run"], - ) - self.assertIn("authorized-release-plan-failure.json", handoff["run"]) - self.assertIn( - "record-supersession \\\n authorized-release-plan-failure.json", - mutation["run"], - ) - self.assertNotIn("isolated-supersession/release-plan-failure.json", mutation["run"]) - self.assertEqual("${{ github.token }}", mutation["env"]["GITHUB_TOKEN"]) - self.assertLess(steps.index(handoff), steps.index(mutation)) - - def test_supersession_write_revalidation_does_not_execute_the_downloaded_phar(self) -> None: - version = "0.1.95" - observed_commit = "e" * 40 - embedded_identity = canonical_cli_embedded_identity(version, observed_commit) - release = { - "id": 123, - "url": "https://github.com/durable-workflow/cli/releases/1", - } - distribution = { - "kind": "github-release", - "build_attestations_verified": True, - "build_attestation_authority": { - "mode": "exact-tag", - "ref": f"refs/tags/{version}", - "commit": observed_commit, - }, - "package_source": { - "commit": observed_commit, - "embedded_phar_identity": embedded_identity, - }, - "assets": [], - } - conflict = { - "component": "cli", - "version": version, - "observed_commit": observed_commit, - "reason": "published-version-source-conflict", - "github_release": release, - "distribution": distribution, - } - failed_plan = { - "components": { - "cli": {"version": version, "commit": "a" * 40}, - } - } - verifier = mock.Mock(return_value=distribution) - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value=observed_commit), - mock.patch("scripts.release_plan.github_release_conflict_evidence", return_value=release), - mock.patch("scripts.release_plan.verify_github_release", verifier), - ): - revalidate_conflict_public_evidence(conflict, failed_plan, {}, mock.Mock()) - - self.assertEqual(1, verifier.call_count) - self.assertEqual({}, verifier.call_args.kwargs) - - def test_verifiers_accept_exact_tag_authority_for_the_planned_package_source(self) -> None: - version = "0.1.95" - commit = "4" * 40 - - for module, _error_type in VERIFIERS: - with self.subTest(module=module.__name__): - calls: list[list[str]] = [] - - def run( - command: list[str], - calls: list[list[str]] = calls, - current_module: ModuleType = module, - **_kwargs: object, - ) -> subprocess.CompletedProcess[str]: - calls.append(command) - if command[0] == "php": - return subprocess.CompletedProcess( - command, - 0, - stdout=( - inspected_build_info(version, commit) - if current_module is beta_candidate - else f"dw {version} (commit {commit[:12]}, built 2026-07-21)" - ), - stderr="", - ) - return subprocess.CompletedProcess(command, 0, stdout="verified", stderr="") - - with ( - mock.patch.object(module.shutil, "which", return_value="/usr/bin/tool"), - mock.patch.object(module.subprocess, "run", side_effect=run), - ): - evidence = verify_cli_release(module, CliReleaseFixtureClient(version), version, commit) - - attestations = [command for command in calls if command[0] == "gh"] - self.assertEqual(len(CLI_ASSETS), len(attestations)) - for command in attestations: - self.assertEqual(commit, command[command.index("--source-digest") + 1]) - self.assertEqual(f"refs/tags/{version}", command[command.index("--source-ref") + 1]) - self.assertNotIn("--signer-workflow", command) - self.assertEqual( - { - "mode": "exact-tag", - "ref": f"refs/tags/{version}", - "commit": commit, - }, - evidence["build_attestation_authority"], - ) - self.assertEqual(commit, evidence["package_source"]["commit"]) - - def test_verifiers_reject_untrusted_main_workflow_or_ref(self) -> None: - version = "0.1.94" - commit = "3" * 40 - untrusted_authorities = ( - ("refs/heads/release", "durable-workflow/cli/.github/workflows/release.yml"), - ("refs/heads/main", "durable-workflow/cli/.github/workflows/untrusted.yml"), - ) - - for module, error_type in VERIFIERS: - for accepted_ref, accepted_workflow in untrusted_authorities: - with self.subTest(module=module.__name__, ref=accepted_ref, workflow=accepted_workflow): - - def run( - command: list[str], - accepted_ref: str = accepted_ref, - accepted_workflow: str = accepted_workflow, - **_kwargs: object, - ) -> subprocess.CompletedProcess[str]: - valid = ( - "--signer-workflow" in command - and command[command.index("--source-ref") + 1] == accepted_ref - and command[command.index("--signer-workflow") + 1] == accepted_workflow - ) - return subprocess.CompletedProcess( - command, 0 if valid else 1, stdout="", stderr="untrusted authority" - ) - - with ( - mock.patch.object(module.shutil, "which", return_value="/usr/bin/tool"), - mock.patch.object(module.subprocess, "run", side_effect=run), - self.assertRaisesRegex(error_type, "untrusted authority"), - ): - verify_cli_release(module, CliReleaseFixtureClient(version), version, commit) - - def test_verifiers_pin_one_attestation_mode_across_every_asset(self) -> None: - version = "0.1.94" - commit = "3" * 40 - - for module, error_type in VERIFIERS: - with self.subTest(module=module.__name__): - exact_tag_attempts = 0 - calls: list[list[str]] = [] - - def run( - command: list[str], - calls: list[list[str]] = calls, - **_kwargs: object, - ) -> subprocess.CompletedProcess[str]: - nonlocal exact_tag_attempts - calls.append(command) - if command[0] == "php": - self.fail("PHAR executed before every asset shared one authority") - if "--source-digest" in command: - exact_tag_attempts += 1 - return subprocess.CompletedProcess( - command, - 0 if exact_tag_attempts == 1 else 1, - stdout="", - stderr="authority differs", - ) - return subprocess.CompletedProcess(command, 0, stdout="", stderr="") - - with ( - mock.patch.object(module.shutil, "which", return_value="/usr/bin/tool"), - mock.patch.object(module.subprocess, "run", side_effect=run), - self.assertRaisesRegex(error_type, "exact-tag: authority differs"), - ): - verify_cli_release(module, CliReleaseFixtureClient(version), version, commit) - - self.assertEqual(2, exact_tag_attempts) - self.assertFalse(any("--signer-workflow" in command for command in calls)) - - def test_verifiers_reject_embedded_package_source_mismatch(self) -> None: - version = "0.1.94" - commit = "3" * 40 - - for module, error_type in VERIFIERS: - with self.subTest(module=module.__name__): - - def run( - command: list[str], - current_module: ModuleType = module, - **_kwargs: object, - ) -> subprocess.CompletedProcess[str]: - if command[0] == "php": - wrong_commit = "f" * 40 - return subprocess.CompletedProcess( - command, - 0, - stdout=( - inspected_build_info(version, wrong_commit) - if current_module is beta_candidate - else f"dw {version} (commit {wrong_commit[:12]}, built 2026-07-20)" - ), - stderr="", - ) - return subprocess.CompletedProcess(command, 0, stdout="verified", stderr="") - - with ( - mock.patch.object(module.shutil, "which", return_value="/usr/bin/tool"), - mock.patch.object(module.subprocess, "run", side_effect=run), - self.assertRaisesRegex(error_type, "does not embed planned source commit"), - ): - verify_cli_release(module, CliReleaseFixtureClient(version), version, commit) - - def test_release_plan_binds_package_source_and_attestation_authority_separately(self) -> None: - version = "0.1.94" - package_commit = "36bde75882980e834854a145c9ad0f61ceec4659" - distribution = { - "kind": "github-release", - "build_attestations_verified": True, - "build_attestation_authority": { - "mode": "qualified-main-workflow", - "ref": "refs/heads/main", - "workflow": "durable-workflow/cli/.github/workflows/release.yml", - }, - "package_source": { - "commit": package_commit, - "embedded_phar_identity": canonical_cli_embedded_identity(version, package_commit), - }, - } - - require_distribution_identity(distribution, "cli", version, package_commit) - - wrong_package_source = copy.deepcopy(distribution) - wrong_package_source["package_source"]["commit"] = "b" * 40 - with self.assertRaisesRegex(CandidateError, "bind the observed source commit"): - require_distribution_identity(wrong_package_source, "cli", version, package_commit) - - for field, value in ( - ("ref", "refs/heads/release"), - ("workflow", "durable-workflow/cli/.github/workflows/untrusted.yml"), - ): - with self.subTest(field=field): - untrusted_authority = copy.deepcopy(distribution) - untrusted_authority["build_attestation_authority"][field] = value - with self.assertRaisesRegex(CandidateError, "untrusted build attestation authority"): - require_distribution_identity(untrusted_authority, "cli", version, package_commit) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_component_release_recovery.py b/tests/test_component_release_recovery.py deleted file mode 100644 index 4fd913f..0000000 --- a/tests/test_component_release_recovery.py +++ /dev/null @@ -1,2247 +0,0 @@ -from __future__ import annotations - -import datetime as dt -import hashlib -import json -import os -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path -from unittest import mock - -from scripts.component_release_recovery import ( - CLI_ASSETS, - COMPONENTS, - CONTINUITY_RESOLUTION_SCHEMA, - CONTINUITY_RESOLUTION_TAG_PREFIX, - FOUNDATION_COMMIT, - FOUNDATION_TAG, - OCCUPIED_SOURCE_MANIFEST_REASON, - PREPARATION_SCHEMA, - SCHEMA, - SUPERSESSION_API_VERSION, - SUPERSESSION_REASON, - NotFound, - PublicClient, - PublicInfrastructureError, - RecoveryError, - beta_authorization_matches_plan, - canonical_json, - current_product_train_authorities, - direct_plan_lifecycle, - discover_plan, - list_release_plan_tags, - main, - manifest_digest, - resolve_component, - resolve_continuity_successor_fork, - revalidate_supersession_authority, - scheduled_continuity_pause, - select_implicit_plan_authority, - select_publication_run, - semver_precedence, - validate_continuity_resolution_qualification, - validate_plan, - validate_release_preparation, - validate_successor_transition, - verify_cli, - verify_composer, - verify_plan_authority, - verify_recovery_workflow_source, -) -from scripts.recovery_workflow_authority import normalized_source_sha256 -from tests.verification_fixture import legacy_beta_one_release_plan - - -def github_cli_result( - status: int = 200, - body: bytes = b"[]", - *, - stderr: bytes = b"", - **headers: str, -) -> subprocess.CompletedProcess[bytes]: - response_headers = b"".join(f"{name}: {value}\r\n".encode() for name, value in headers.items()) - output = f"HTTP/2.0 {status} response\r\n".encode() + response_headers + b"\r\n" + body - return subprocess.CompletedProcess( - ["gh", "api"], - 0 if 200 <= status <= 299 else 1, - output, - stderr, - ) - - -def plan(channel: str = "alpha") -> dict[str, object]: - prerelease = channel - return { - "schema": SCHEMA, - "plan": "component-recovery", - "channel": channel, - "foundation": {"tag": FOUNDATION_TAG, "commit": FOUNDATION_COMMIT}, - "components": { - name: { - "version": f"2.0.0-{prerelease}.{index + 1}" if name in {"workflow", "waterline"} else f"1.0.{index}", - "commit": f"{index + 1:040x}", - } - for index, name in enumerate(COMPONENTS) - }, - "beta_authorization": ( - {"tag": "beta-authorization/component-recovery", "commit": "f" * 40} - if channel in {"beta", "rc"} - else None - ), - } - - -def continuity_resolution_qualification() -> dict[str, object]: - return { - "repository": "durable-workflow/.github", - "workflow": ".github/workflows/beta-candidate.yml", - "event": "push", - "head_branch": "main", - "head_sha": "9" * 40, - "run_id": 987, - "run_attempt": 2, - "status": "completed", - "conclusion": "success", - } - - -def continuity_resolution_qualification_run() -> dict[str, object]: - qualification = continuity_resolution_qualification() - return { - "id": qualification["run_id"], - "run_attempt": qualification["run_attempt"], - "repository": {"full_name": "durable-workflow/.github"}, - "head_repository": {"full_name": "durable-workflow/.github"}, - "path": ".github/workflows/beta-candidate.yml@main", - "event": qualification["event"], - "head_branch": qualification["head_branch"], - "head_sha": qualification["head_sha"], - "status": qualification["status"], - "conclusion": qualification["conclusion"], - } - - -def preparation(candidate: dict[str, object]) -> dict[str, object]: - release_date = "2026-07-19" - components: dict[str, object] = {} - for name, identity in candidate["components"].items(): - heading = f"## [{identity['version']}] - {release_date}" - markdown = f"{heading}\n\nPrepared source changes.\n" - repository = COMPONENTS[name].repository - changelog = name in {"workflow", "waterline", "sdk-php", "sdk-python"} - components[name] = { - "version": identity["version"], - "source_commit": identity["commit"], - "release_notes": { - "format": "text/markdown", - "heading": heading, - "markdown": markdown, - "release_date": release_date, - "sha256": hashlib.sha256(markdown.encode()).hexdigest(), - "source": { - "kind": "changelog-unreleased" if changelog else "source-commit-message", - "sha256": "a" * 64, - "url": ( - f"https://github.com/{repository}/blob/{identity['commit']}/CHANGELOG.md" - if changelog - else f"https://github.com/{repository}/commit/{identity['commit']}" - ), - }, - }, - } - return { - "schema": PREPARATION_SCHEMA, - "release_plan": { - "tag": f"release-plan/{candidate['plan']}", - "sha256": manifest_digest(candidate), - }, - "components": components, - } - - -def supersession_record( - failed: dict[str, object], - successor: dict[str, object], - failed_commit: str, -) -> dict[str, object]: - identity = failed["components"]["workflow"] - observed_commit = "e" * 40 - environment_url = ( - "https://github.com/durable-workflow/.github/deployments/activity_log?" - "environments_filter=release-plan-supersession" - ) - protection = { - "custom_branch_policies": [{"id": 22, "name": "main"}], - "deployment_branch_policy": { - "custom_branch_policies": True, - "protected_branches": False, - }, - "environment_id": 11, - "environment_url": environment_url, - "required_reviewer_rule_ids": [33], - } - return { - "schema": "durable-workflow.release-plan-failure/v1", - "outcome": "terminal-failure", - "failed_plan": { - "tag": f"release-plan/{failed['plan']}", - "commit": failed_commit, - "sha256": manifest_digest(failed), - }, - "conflicts": [ - { - "component": "workflow", - "version": identity["version"], - "planned_commit": identity["commit"], - "observed_commit": observed_commit, - "reason": "published-version-source-conflict", - "github_release": { - "id": 44, - "url": "https://github.com/durable-workflow/workflow/releases/44", - }, - "distribution": { - "kind": "composer", - "source_reference": observed_commit, - "dist_reference": observed_commit, - }, - } - ], - "successor_plan": { - "tag": f"release-plan/{successor['plan']}", - "sha256": manifest_digest(successor), - }, - "authorization": { - "actor": "release-operator", - "environment": "release-plan-supersession", - "environment_approval": { - "comment": "approved", - "environments": [ - { - "html_url": environment_url, - "id": 11, - "name": "release-plan-supersession", - "node_id": "environment-node", - "url": ( - "https://api.github.com/repos/durable-workflow/.github/" - "environments/release-plan-supersession" - ), - } - ], - "run_attempt": 1, - "run_id": 456, - "state": "approved", - "user": { - "html_url": "https://github.com/release-reviewer", - "id": 55, - "login": "release-reviewer", - "node_id": "reviewer-node", - "url": "https://api.github.com/users/release-reviewer", - }, - }, - "environment_protection": protection, - "repository": "durable-workflow/.github", - "run_attempt": 1, - "run_id": 456, - "run_url": "https://github.com/durable-workflow/.github/actions/runs/456", - "workflow_commit": "f" * 40, - "workflow_ref": ( - "durable-workflow/.github/.github/workflows/" - "release-plan-supersession.yml@refs/heads/main" - ), - }, - } - - -def captured_github_authority( - record: dict[str, object], -) -> tuple[mock.Mock, dict[str, object]]: - authorization = record["authorization"] - protection = authorization["environment_protection"] - approval = authorization["environment_approval"] - environment = { - "id": protection["environment_id"], - "html_url": protection["environment_url"], - "protection_rules": [ - { - "id": protection["required_reviewer_rule_ids"][0], - "prevent_self_review": False, - "type": "required_reviewers", - "reviewers": [ - { - "type": "User", - "reviewer": { - **approval["user"], - "avatar_url": "https://avatars.githubusercontent.com/u/55?v=4", - "site_admin": False, - "type": "User", - }, - } - ], - } - ], - "deployment_branch_policy": protection["deployment_branch_policy"], - } - policies = { - "total_count": 1, - "branch_policies": [ - { - **protection["custom_branch_policies"][0], - "type": "branch", - } - ], - } - run = { - "actor": {"login": authorization["actor"]}, - "conclusion": "success", - "event": "workflow_dispatch", - "head_branch": "main", - "head_sha": authorization["workflow_commit"], - "html_url": authorization["run_url"], - "id": authorization["run_id"], - "path": ".github/workflows/release-plan-supersession.yml@main", - "repository": {"full_name": "durable-workflow/.github"}, - "run_attempt": authorization["run_attempt"], - "status": "completed", - } - history = json.loads( - json.dumps( - [ - { - "comment": approval["comment"], - "environments": [ - { - **approval["environments"][0], - "can_admins_bypass": True, - "created_at": "2026-07-23T00:00:00Z", - "updated_at": "2026-07-23T00:00:00Z", - } - ], - "state": approval["state"], - "user": { - **approval["user"], - "avatar_url": "https://avatars.githubusercontent.com/u/55?v=4", - "site_admin": False, - "type": "User", - }, - } - ] - ) - ) - responses = { - "environment": environment, - "policies": policies, - "run": run, - "history": history, - } - client = mock.Mock() - - def respond(url: str, **_kwargs: object) -> object: - if url.endswith("deployment-branch-policies?per_page=100"): - return responses["policies"] - if url.endswith("/approvals"): - return responses["history"] - if "/actions/runs/" in url: - return responses["run"] - if "/environments/" in url: - return responses["environment"] - raise AssertionError(f"unexpected GitHub authority request: {url}") - - client.json.side_effect = respond - return client, responses - - -class ComponentRecoveryContractTest(unittest.TestCase): - def test_immutable_plan_registry_is_consumed_as_one_complete_authority_set(self) -> None: - client = mock.Mock() - client.json.return_value = [ - {"ref": f"refs/tags/release-plan/completed-{index:03d}"} - for index in range(125) - ] - - tags = list_release_plan_tags(client) - - self.assertEqual(125, len(tags)) - client.json.assert_called_once_with( - "https://api.github.com/repos/durable-workflow/.github/" - "git/matching-refs/tags/release-plan/" - ) - - def test_scheduled_discovery_selects_completed_current_train_despite_older_release_update(self) -> None: - older = plan() - older["plan"] = "older-alpha" - newer = plan("beta") - newer["plan"] = "newer-beta" - tags = [f"release-plan/{older['plan']}", f"release-plan/{newer['plan']}"] - commits = {tags[0]: "a" * 40, tags[1]: "b" * 40} - recorded = { - "a" * 40: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - "b" * 40: dt.datetime(2026, 7, 22, tzinfo=dt.UTC), - } - - with ( - mock.patch( - "scripts.component_release_recovery.list_release_plan_tags", - # Mutable Releases API order after the older Release was edited. - return_value=[tags[0], tags[1]], - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=lambda _client, _repository, tag: commits[tag], - ), - mock.patch( - "scripts.component_release_recovery.read_plan_authority", - side_effect=[ - (older, preparation(older)), - (newer, preparation(newer)), - (older, preparation(older)), - (newer, preparation(newer)), - ], - ), - mock.patch( - "scripts.component_release_recovery.direct_plan_lifecycle", - side_effect=[ - ("actionable", None), - ("completed", None), - ("actionable", None), - ("completed", None), - ], - ), - mock.patch( - "scripts.component_release_recovery.immutable_plan_recorded_at", - side_effect=lambda _client, commit: recorded[commit], - ), - mock.patch( - "scripts.component_release_recovery.accepted_continuity_supersession", - return_value=None, - ), - ): - selected = select_implicit_plan_authority(mock.Mock()) - self.assertEqual(tags[1], selected["tag"]) - self.assertEqual("completed", selected["lifecycle"]) - - def test_scheduled_discovery_rejects_incomparable_current_product_trains(self) -> None: - first = plan("beta") - first["plan"] = "workflow-ahead" - second = json.loads(json.dumps(first)) - second["plan"] = "waterline-ahead" - first["components"]["workflow"]["version"] = "2.0.0-beta.11" - second["components"]["waterline"]["version"] = "2.0.0-beta.12" - authorities = [ - {"tag": f"release-plan/{first['plan']}", "plan": first}, - {"tag": f"release-plan/{second['plan']}", "plan": second}, - ] - - with ( - mock.patch( - "scripts.component_release_recovery.classify_plan_authorities", - return_value=authorities, - ), - self.assertRaisesRegex(RecoveryError, "conflicting current product trains"), - ): - select_implicit_plan_authority(mock.Mock()) - - def test_scheduled_discovery_rejects_equal_versions_with_different_commits(self) -> None: - first = plan("beta") - first["plan"] = "first-beta-authority" - second = json.loads(json.dumps(first)) - second["plan"] = "conflicting-beta-authority" - second["components"]["workflow"]["commit"] = "f" * 40 - authorities = [ - {"tag": f"release-plan/{first['plan']}", "plan": first}, - {"tag": f"release-plan/{second['plan']}", "plan": second}, - ] - - with self.assertRaisesRegex(RecoveryError, "conflicting current product trains"): - current_product_train_authorities(authorities) - - def test_scheduled_discovery_validates_strict_semver_before_selection(self) -> None: - for malformed in ("01.0.0", "1.0.0-alpha.01", "1.0.0-alpha..1", "1.0.0\n"): - candidate = plan("beta") - candidate["components"]["server"]["version"] = malformed - authority = { - "tag": f"release-plan/{candidate['plan']}", - "plan": candidate, - } - - with self.subTest(version=malformed), self.assertRaisesRegex( - RecoveryError, - "components.server.version is not exact SemVer", - ): - current_product_train_authorities([authority]) - - def test_malformed_scheduled_authority_fails_before_recovery_or_handoff(self) -> None: - candidate = plan("beta") - candidate["components"]["server"]["version"] = "01.0.0" - authority = { - "tag": f"release-plan/{candidate['plan']}", - "plan": candidate, - } - - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - evidence = root / "release-recovery-evidence.json" - plan_output = root / "release-plan.json" - preparation_output = root / "release-preparation.json" - github_output = root / "github-output" - arguments = [ - "component_release_recovery.py", - "resolve", - "--component", - "server", - "--plan-output", - str(plan_output), - "--preparation-output", - str(preparation_output), - "--evidence", - str(evidence), - "--github-output", - str(github_output), - "--allow-empty", - ] - - with ( - mock.patch.object(sys, "argv", arguments), - mock.patch( - "scripts.component_release_recovery.classify_plan_authorities", - return_value=[authority], - ), - mock.patch( - "scripts.component_release_recovery.resolve_component", - ) as recover_component, - ): - self.assertEqual(1, main()) - - recover_component.assert_not_called() - self.assertFalse(plan_output.exists()) - self.assertFalse(preparation_output.exists()) - self.assertFalse(github_output.exists()) - failure = json.loads(evidence.read_text()) - self.assertEqual("plan-discovery", failure["phase"]) - self.assertEqual("failed", failure["outcome"]) - self.assertIn("not exact SemVer", failure["reason"]) - - def test_semver_precedence_preserves_ordering_and_immutable_identity(self) -> None: - self.assertEqual( - semver_precedence("1.0.0+build.1"), - semver_precedence("1.0.0+build.2"), - ) - self.assertLess( - semver_precedence("1.0.0-beta.2"), - semver_precedence("1.0.0-beta.10"), - ) - self.assertLess( - semver_precedence("1.0.0-beta.10"), - semver_precedence("1.0.0"), - ) - - first = plan("beta") - first["components"]["server"]["version"] = "1.0.0+build.1" - second = json.loads(json.dumps(first)) - second["plan"] = "different-build-identity" - second["components"]["server"]["version"] = "1.0.0+build.2" - authorities = [ - {"tag": f"release-plan/{first['plan']}", "plan": first}, - {"tag": f"release-plan/{second['plan']}", "plan": second}, - ] - - with self.assertRaisesRegex(RecoveryError, "conflicting current product trains"): - current_product_train_authorities(authorities) - - def test_semver_precedence_compares_unbounded_numeric_identifiers(self) -> None: - long_numeric = "9" * 4301 - cases = ( - ("core", "1.0.0", f"{long_numeric}.0.0"), - ("prerelease", "1.0.0-alpha.1", f"1.0.0-alpha.{long_numeric}"), - ) - - for kind, lower_version, higher_version in cases: - lower = plan("beta") - lower["plan"] = f"unbounded-{kind}-lower" - lower["components"]["server"]["version"] = lower_version - higher = json.loads(json.dumps(lower)) - higher["plan"] = f"unbounded-{kind}-higher" - higher["components"]["server"]["version"] = higher_version - authorities = [ - {"tag": f"release-plan/{lower['plan']}", "plan": lower}, - {"tag": f"release-plan/{higher['plan']}", "plan": higher}, - ] - - with self.subTest(kind=kind): - self.assertEqual( - [f"release-plan/{higher['plan']}"], - [ - authority["tag"] - for authority in current_product_train_authorities(authorities) - ], - ) - - def test_semver_successors_cover_both_terminal_conflict_paths(self) -> None: - long_numeric = "9" * 4301 - cases = ( - ("release", "1.2.3", "1.2.4"), - ("prerelease", "1.2.3-alpha.9", "1.2.3-alpha.10"), - ("release-build", "1.2.3+build.1", "1.2.4+build.2"), - ( - "prerelease-build", - "1.2.3-alpha.9+build.1", - "1.2.3-alpha.10+build.2", - ), - ("single-numeric-prerelease", "1.2.3-9", "1.2.3-10"), - ( - "single-numeric-prerelease-build", - "1.2.3-9+build.1", - "1.2.3-10+build.2", - ), - ("nonnumeric-prerelease", "1.2.3-rc", "1.2.3-rc.1"), - ( - "nonnumeric-prerelease-build", - "1.2.3-rc+build.1", - "1.2.3-rc.1+build.2", - ), - ( - "long-core", - f"1.2.{long_numeric}", - f"1.2.1{'0' * 4301}", - ), - ( - "long-prerelease", - f"1.2.3-alpha.{long_numeric}", - f"1.2.3-alpha.1{'0' * 4301}", - ), - ) - - for reason in (SUPERSESSION_REASON, OCCUPIED_SOURCE_MANIFEST_REASON): - for label, previous_version, successor_version in cases: - failed = plan("beta") - failed["plan"] = f"semver-{label}-failed" - failed["components"]["server"]["version"] = previous_version - successor = json.loads(json.dumps(failed)) - successor["plan"] = f"semver-{label}-successor" - successor["components"]["server"]["version"] = successor_version - if reason == OCCUPIED_SOURCE_MANIFEST_REASON: - successor["components"]["server"]["commit"] = "e" * 40 - - with self.subTest(reason=reason, kind=label): - validate_successor_transition( - failed, - successor, - [{"component": "server", "reason": reason}], - ) - - failed = plan("beta") - failed["plan"] = "semver-long-skipped-failed" - failed["components"]["server"]["version"] = f"1.2.{long_numeric}" - successor = json.loads(json.dumps(failed)) - successor["plan"] = "semver-long-skipped-successor" - successor["components"]["server"]["version"] = f"1.2.2{'0' * 4301}" - if reason == OCCUPIED_SOURCE_MANIFEST_REASON: - successor["components"]["server"]["commit"] = "e" * 40 - - with self.subTest(reason=reason, kind="invalid"), self.assertRaises(RecoveryError) as raised: - validate_successor_transition( - failed, - successor, - [{"component": "server", "reason": reason}], - ) - self.assertEqual("plan-discovery", raised.exception.phase) - - def test_release_plan_accepts_valid_prerelease_and_build_metadata(self) -> None: - for valid in ( - "1.0.0-alpha.1", - "1.0.0-alpha.1+build.01", - "1.0.0+build.01", - ): - candidate = plan("beta") - candidate["components"]["server"]["version"] = valid - - with self.subTest(version=valid): - validate_plan(candidate) - - def test_scheduled_discovery_resolves_validated_source_manifest_successor(self) -> None: - predecessor = plan("beta") - predecessor["plan"] = "source-manifest-predecessor" - successor = json.loads(json.dumps(predecessor)) - successor["plan"] = "source-manifest-successor" - successor["components"]["workflow"]["commit"] = "f" * 40 - successor_tag = f"release-plan/{successor['plan']}" - successor_authority = { - "tag": successor_tag, - "plan": successor, - "lifecycle": "actionable", - "successor": None, - } - authorities = [ - { - "tag": f"release-plan/{predecessor['plan']}", - "plan": predecessor, - "lifecycle": "superseded", - "successor": { - "tag": successor_tag, - "sha256": manifest_digest(successor), - "plan": successor, - }, - }, - successor_authority, - ] - - self.assertEqual( - [successor_authority], - current_product_train_authorities(authorities), - ) - - def test_scheduled_discovery_retries_concurrent_terminal_supersession(self) -> None: - older = plan() - older["plan"] = "older-plan" - successor = plan() - successor["plan"] = "successor-plan" - successor["components"]["workflow"]["version"] = "2.0.0-alpha.2" - older_tag = f"release-plan/{older['plan']}" - successor_tag = f"release-plan/{successor['plan']}" - older_commit = "a" * 40 - successor_commit = "b" * 40 - failure_commit = "c" * 40 - failure = supersession_record(older, successor, older_commit) - registry_reads = 0 - superseded = False - - def list_tags(_client: mock.Mock) -> list[str]: - nonlocal registry_reads, superseded - registry_reads += 1 - if registry_reads == 2: - superseded = True - return [older_tag, successor_tag] if superseded else [older_tag] - - def resolve(_client: mock.Mock, _repository: str, tag: str) -> str | None: - if tag == older_tag: - return older_commit - if tag == successor_tag: - return successor_commit if superseded else None - if tag == f"release-plan-failure/{older['plan']}": - return failure_commit if superseded else None - return None - - def read_authority( - _client: mock.Mock, tag: str, _commit: str - ) -> tuple[dict[str, object], dict[str, object]]: - candidate = successor if tag == successor_tag else older - return candidate, preparation(candidate) - - def read_lifecycle_record( - _client: mock.Mock, _tag: str, _commit: str, filename: str - ) -> dict[str, object]: - if filename == "release-plan-failure.json": - return failure - if filename == "successor-release-plan.json": - return successor - raise AssertionError(f"unexpected lifecycle record: {filename}") - - recorded = { - older_commit: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - successor_commit: dt.datetime(2026, 7, 21, tzinfo=dt.UTC), - } - with ( - mock.patch( - "scripts.component_release_recovery.list_release_plan_tags", - side_effect=list_tags, - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=resolve, - ), - mock.patch( - "scripts.component_release_recovery.read_plan_authority", - side_effect=read_authority, - ), - mock.patch( - "scripts.component_release_recovery.read_record", - side_effect=read_lifecycle_record, - ), - mock.patch( - "scripts.component_release_recovery.immutable_plan_recorded_at", - side_effect=lambda _client, commit: recorded[commit], - ), - mock.patch( - "scripts.component_release_recovery.revalidate_supersession_authority", - ), - ): - selected = select_implicit_plan_authority(mock.Mock()) - - self.assertEqual(successor_tag, selected["tag"]) - self.assertEqual("actionable", selected["lifecycle"]) - self.assertEqual(4, registry_reads) - - def test_scheduled_discovery_fails_closed_after_bounded_churn(self) -> None: - with ( - mock.patch( - "scripts.component_release_recovery.classify_implicit_plan_authority", - return_value=({"tag": "release-plan/unconverged"}, ["release-plan/unconverged"]), - ) as classify, - mock.patch( - "scripts.component_release_recovery.implicit_plan_authority_converged", - return_value=False, - ), - self.assertRaisesRegex(RecoveryError, "did not converge after 3 attempts"), - ): - select_implicit_plan_authority(mock.Mock()) - - self.assertEqual(3, classify.call_count) - - def test_convergence_rechecks_nonselected_lifecycle_authority(self) -> None: - older = {"tag": "release-plan/older", "lifecycle": "completed"} - changed_older = {**older, "lifecycle": "superseded"} - latest = {"tag": "release-plan/latest", "lifecycle": "actionable"} - current_snapshot = [changed_older, latest] - - with mock.patch( - "scripts.component_release_recovery.classify_implicit_plan_authority", - side_effect=[ - (latest, [older, latest]), - (latest, current_snapshot), - (latest, current_snapshot), - (latest, current_snapshot), - ], - ) as classify: - selected = select_implicit_plan_authority(mock.Mock()) - - self.assertEqual(4, classify.call_count) - self.assertEqual(current_snapshot, selected["authority_snapshot"]) - - def test_final_implicit_boundary_rejects_stale_publish_but_manual_recovery_does_not(self) -> None: - candidate = plan() - candidate_preparation = preparation(candidate) - component = COMPONENTS["workflow"] - publication_preflight = mock.Mock(side_effect=NotFound("not published")) - implicit_authority = { - "authority_snapshot": [ - {"tag": "release-plan/older", "lifecycle": "actionable"} - ] - } - current_snapshot = [ - {"tag": "release-plan/older", "lifecycle": "superseded"}, - {"tag": "release-plan/successor", "lifecycle": "actionable"}, - ] - - with ( - mock.patch( - "scripts.component_release_recovery.verify_plan_authority", - return_value=({}, {}), - ), - mock.patch( - "scripts.component_release_recovery.validate_release_preparation", - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - return_value=None, - ), - mock.patch( - "scripts.component_release_recovery.classify_implicit_plan_authority", - return_value=(current_snapshot[-1], current_snapshot), - ) as classify, - mock.patch.dict( - "scripts.component_release_recovery.VERIFIERS", - {component.distribution: publication_preflight}, - ), - ): - with self.assertRaisesRegex(RecoveryError, "refusing a stale recovery action"): - resolve_component( - mock.Mock(), - "workflow", - "release-plan/older", - "a" * 40, - candidate, - candidate_preparation, - implicit_authority, - ) - - state, outputs = resolve_component( - mock.Mock(), - "workflow", - "release-plan/older", - "a" * 40, - candidate, - candidate_preparation, - ) - - self.assertEqual("publish", outputs["action"]) - self.assertEqual("publication", state["phase"]) - self.assertEqual(1, classify.call_count) - self.assertEqual(2, publication_preflight.call_count) - - def test_implicit_completed_plan_refuses_publication_when_artifact_is_absent( - self, - ) -> None: - candidate = plan() - candidate_preparation = preparation(candidate) - component = COMPONENTS["sdk-php"] - selected = { - "tag": "release-plan/completed", - "lifecycle": "completed", - } - implicit_authority = { - **selected, - "authority_snapshot": [selected], - } - - with ( - mock.patch( - "scripts.component_release_recovery.verify_plan_authority", - return_value=({}, {}), - ), - mock.patch( - "scripts.component_release_recovery.validate_release_preparation", - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - return_value=None, - ), - mock.patch( - "scripts.component_release_recovery.classify_implicit_plan_authority", - return_value=(selected, [selected]), - ), - mock.patch.dict( - "scripts.component_release_recovery.VERIFIERS", - { - component.distribution: mock.Mock( - side_effect=NotFound("not published") - ) - }, - ), - self.assertRaisesRegex( - RecoveryError, - "is completed; refusing publication instead of idempotent verification", - ), - ): - resolve_component( - mock.Mock(), - "sdk-php", - selected["tag"], - "a" * 40, - candidate, - candidate_preparation, - implicit_authority, - ) - - def test_terminal_failure_successor_requires_exact_authorized_plan_identity(self) -> None: - failed = plan() - failed["plan"] = "failed-plan" - authorized_successor = json.loads(json.dumps(failed)) - authorized_successor["plan"] = "successor-plan" - authorized_successor["components"]["workflow"]["version"] = "2.0.0-alpha.2" - recorded_successor = json.loads(json.dumps(authorized_successor)) - recorded_successor["components"]["workflow"]["commit"] = "e" * 40 - failed_tag = f"release-plan/{failed['plan']}" - successor_tag = f"release-plan/{authorized_successor['plan']}" - failed_commit = "a" * 40 - successor_commit = "b" * 40 - failure_commit = "c" * 40 - failure = supersession_record(failed, authorized_successor, failed_commit) - - with ( - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=[None, failure_commit], - ), - mock.patch( - "scripts.component_release_recovery.read_record", - side_effect=[failure, authorized_successor], - ), - mock.patch( - "scripts.component_release_recovery.revalidate_supersession_authority", - ), - ): - lifecycle, successor_identity = direct_plan_lifecycle( - mock.Mock(), - failed_tag, - failed_commit, - failed, - None, - ) - - self.assertEqual("superseded", lifecycle) - self.assertEqual( - { - "tag": successor_tag, - "sha256": manifest_digest(authorized_successor), - "plan": authorized_successor, - }, - successor_identity, - ) - - commits = {failed_tag: failed_commit, successor_tag: successor_commit} - recorded = { - failed_commit: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - successor_commit: dt.datetime(2026, 7, 21, tzinfo=dt.UTC), - } - with ( - mock.patch( - "scripts.component_release_recovery.list_release_plan_tags", - return_value=[failed_tag, successor_tag], - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=lambda _client, _repository, tag: commits[tag], - ), - mock.patch( - "scripts.component_release_recovery.read_plan_authority", - side_effect=[(failed, None), (recorded_successor, None)], - ), - mock.patch( - "scripts.component_release_recovery.direct_plan_lifecycle", - side_effect=[ - (lifecycle, successor_identity), - ("completed", None), - ], - ), - mock.patch( - "scripts.component_release_recovery.immutable_plan_recorded_at", - side_effect=lambda _client, commit: recorded[commit], - ), - mock.patch( - "scripts.component_release_recovery.accepted_continuity_supersession", - return_value=None, - ), - self.assertRaisesRegex(RecoveryError, "conflicting successor identity"), - ): - select_implicit_plan_authority(mock.Mock()) - - def test_terminal_failure_rejects_incomplete_lifecycle_authority(self) -> None: - failed = plan() - failed["plan"] = "failed-plan" - successor = json.loads(json.dumps(failed)) - successor["plan"] = "successor-plan" - successor["components"]["workflow"]["version"] = "2.0.0-alpha.2" - failed_tag = f"release-plan/{failed['plan']}" - failed_commit = "a" * 40 - incomplete = { - "schema": "durable-workflow.release-plan-failure/v1", - "outcome": "terminal-failure", - "failed_plan": { - "tag": failed_tag, - "commit": failed_commit, - "sha256": manifest_digest(failed), - }, - "successor_plan": { - "tag": f"release-plan/{successor['plan']}", - "sha256": manifest_digest(successor), - }, - } - - with ( - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=[None, "c" * 40], - ), - mock.patch( - "scripts.component_release_recovery.read_record", - side_effect=[incomplete, successor], - ), - self.assertRaisesRegex(RecoveryError, "record keys must be exactly"), - ): - direct_plan_lifecycle( - mock.Mock(), - failed_tag, - failed_commit, - failed, - None, - ) - - def test_terminal_failure_resolves_and_normalizes_captured_github_authority(self) -> None: - failed = plan() - successor = json.loads(json.dumps(failed)) - successor["plan"] = "successor-plan" - successor["components"]["workflow"]["version"] = "2.0.0-alpha.2" - record = supersession_record(failed, successor, "a" * 40) - client, _responses = captured_github_authority(record) - - revalidate_supersession_authority(record, client) - - mutations = ( - ("run", "id", 999), - ("run", "run_attempt", 2), - ("run", "path", ".github/workflows/release-plan-observer.yml@main"), - ("run", "head_sha", "0" * 40), - ("run", "conclusion", "failure"), - ("environment", "id", 999), - ("history", "state", "rejected"), - ("reviewer", "id", 999), - ) - for target, field, value in mutations: - with self.subTest(target=target, field=field): - changed = json.loads(json.dumps(record)) - client, responses = captured_github_authority(changed) - if target == "history": - responses["history"][0][field] = value - elif target == "reviewer": - responses["history"][0]["user"][field] = value - else: - responses[target][field] = value - with self.assertRaises(RecoveryError): - revalidate_supersession_authority(changed, client) - - def test_run_scoped_approval_history_cannot_authorize_a_rerun_attempt(self) -> None: - failed = plan() - successor = json.loads(json.dumps(failed)) - successor["plan"] = "successor-plan" - successor["components"]["workflow"]["version"] = "2.0.0-alpha.2" - record = supersession_record(failed, successor, "a" * 40) - record["authorization"]["run_attempt"] = 2 - record["authorization"]["environment_approval"]["run_attempt"] = 2 - client, responses = captured_github_authority(record) - responses["run"]["run_attempt"] = 2 - - with self.assertRaisesRegex(RecoveryError, "cannot prove protected approval for a rerun"): - revalidate_supersession_authority(record, client) - self.assertFalse(any(call.args[0].endswith("/approvals") for call in client.json.call_args_list)) - - def test_scheduled_discovery_fails_closed_on_ambiguous_or_incomplete_history(self) -> None: - first = plan("beta") - first["plan"] = "first" - second = plan("beta") - second["plan"] = "second" - tags = [f"release-plan/{first['plan']}", f"release-plan/{second['plan']}"] - commits = {tags[0]: "a" * 40, tags[1]: "b" * 40} - - cases = ( - ( - "ambiguous immutable Git recorded-at", - [("completed", None), ("completed", None)], - { - "a" * 40: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - "b" * 40: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - }, - ), - ( - "remains actionable", - [("actionable", None), ("completed", None)], - { - "a" * 40: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - "b" * 40: dt.datetime(2026, 7, 21, tzinfo=dt.UTC), - }, - ), - ) - for message, lifecycles, recorded in cases: - with ( - self.subTest(message=message), - mock.patch( - "scripts.component_release_recovery.list_release_plan_tags", - return_value=tags, - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=lambda _client, _repository, tag: commits[tag], - ), - mock.patch( - "scripts.component_release_recovery.read_plan_authority", - side_effect=[(first, preparation(first)), (second, preparation(second))], - ), - mock.patch( - "scripts.component_release_recovery.direct_plan_lifecycle", - side_effect=lifecycles, - ), - mock.patch( - "scripts.component_release_recovery.immutable_plan_recorded_at", - side_effect=lambda _client, commit, recorded=recorded: recorded[commit], - ), - mock.patch( - "scripts.component_release_recovery.accepted_continuity_supersession", - return_value=None, - ), - self.assertRaisesRegex(RecoveryError, message), - ): - select_implicit_plan_authority(mock.Mock()) - - def test_completed_continuity_successor_fork_fails_closed_regardless_of_ordering(self) -> None: - interrupted = plan() - interrupted["plan"] = "interrupted" - first_successor = plan() - first_successor["plan"] = "first-successor" - latest = plan("beta") - latest["plan"] = "latest" - plans = [interrupted, first_successor, latest] - tags = [f"release-plan/{candidate['plan']}" for candidate in plans] - plans_by_tag = dict(zip(tags, plans, strict=True)) - interruption_tag = "beta-continuity/interrupted/interrupted" - interruption_commit = "d" * 40 - interruption_evidence = {"outcome": "intentionally-interrupted"} - superseded = { - "tag": interruption_tag, - "commit": interruption_commit, - "evidence_sha256": manifest_digest(interruption_evidence), - "plan_sha256": manifest_digest(interrupted), - "reason": "missing-post-acceptance-publication-trigger", - } - commits = { - tags[0]: "a" * 40, - tags[1]: "b" * 40, - tags[2]: "c" * 40, - interruption_tag: interruption_commit, - } - orderings = [ - ( - tags, - { - commits[tags[0]]: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - commits[tags[1]]: dt.datetime(2026, 7, 21, tzinfo=dt.UTC), - commits[tags[2]]: dt.datetime(2026, 7, 22, tzinfo=dt.UTC), - }, - ), - ( - list(reversed(tags)), - { - commits[tags[0]]: dt.datetime(2026, 7, 20, tzinfo=dt.UTC), - commits[tags[1]]: dt.datetime(2026, 7, 22, tzinfo=dt.UTC), - commits[tags[2]]: dt.datetime(2026, 7, 21, tzinfo=dt.UTC), - }, - ), - ] - - for discovered_tags, recorded in orderings: - with ( - self.subTest(tags=discovered_tags, recorded=recorded), - mock.patch( - "scripts.component_release_recovery.list_release_plan_tags", - return_value=discovered_tags, - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=lambda _client, _repository, tag: commits[tag], - ), - mock.patch( - "scripts.component_release_recovery.read_plan_authority", - side_effect=lambda _client, tag, _commit: ( - plans_by_tag[tag], - preparation(plans_by_tag[tag]), - ), - ), - mock.patch( - "scripts.component_release_recovery.direct_plan_lifecycle", - side_effect=lambda _client, tag, *_args: ( - ("interrupted", interruption_tag) if tag == tags[0] else ("completed", None) - ), - ), - mock.patch( - "scripts.component_release_recovery.immutable_plan_recorded_at", - side_effect=lambda _client, commit, recorded=recorded: recorded[commit], - ), - mock.patch( - "scripts.component_release_recovery.accepted_continuity_supersession", - side_effect=lambda _client, authority: (None if authority["tag"] == tags[0] else superseded), - ), - mock.patch( - "scripts.component_release_recovery.list_continuity_resolution_tags", - return_value=[], - ), - mock.patch( - "scripts.component_release_recovery.read_record", - return_value=interruption_evidence, - ), - self.assertRaisesRegex(RecoveryError, "multiple continuity successors"), - ): - select_implicit_plan_authority(mock.Mock()) - - def test_continuity_successor_fork_requires_exact_digest_bound_resolution(self) -> None: - interrupted_plan = plan() - interrupted_plan["plan"] = "interrupted" - interrupted = { - "tag": "release-plan/interrupted", - "commit": "a" * 40, - "plan": interrupted_plan, - } - interruption = { - "tag": "beta-continuity/interrupted/interrupted", - "commit": "b" * 40, - "evidence_sha256": "c" * 64, - } - successors = [] - for index, name in enumerate(("first-successor", "second-successor"), start=1): - successor_plan = plan() - successor_plan["plan"] = name - successors.append( - { - "tag": f"release-plan/{name}", - "supersession": { - **interruption, - "continuity_claim": { - "plan": { - "tag": f"release-plan/{name}", - "commit": str(index) * 40, - "sha256": manifest_digest(successor_plan), - }, - "acceptance": { - "tag": f"beta-continuity/{name}/accepted", - "commit": str(index + 2) * 40, - "sha256": str(index + 4) * 64, - }, - }, - }, - } - ) - claims = [successor["supersession"]["continuity_claim"] for successor in successors] - resolution = { - "schema": CONTINUITY_RESOLUTION_SCHEMA, - "qualification": continuity_resolution_qualification(), - "interruption": { - "plan": { - "tag": interrupted["tag"], - "commit": interrupted["commit"], - "sha256": manifest_digest(interrupted_plan), - }, - "evidence": { - "tag": interruption["tag"], - "commit": interruption["commit"], - "sha256": interruption["evidence_sha256"], - }, - }, - "successor_claims": claims, - "selected_successor": claims[1]["plan"], - } - resolution_tag = f"{CONTINUITY_RESOLUTION_TAG_PREFIX}{interrupted_plan['plan']}/{manifest_digest(resolution)}" - client = mock.Mock() - client.json.return_value = continuity_resolution_qualification_run() - - with ( - mock.patch( - "scripts.component_release_recovery.list_continuity_resolution_tags", - return_value=[resolution_tag], - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - return_value="f" * 40, - ), - mock.patch( - "scripts.component_release_recovery.read_record", - return_value=resolution, - ), - ): - self.assertEqual( - "release-plan/second-successor", - resolve_continuity_successor_fork(client, interrupted, successors), - ) - - failure_cases = ( - ([], resolution, "multiple continuity successors"), - ([resolution_tag, resolution_tag[:-1] + "0"], resolution, "multiple continuity successor resolutions"), - ([resolution_tag], {**resolution, "successor_claims": list(reversed(claims))}, "invalid immutable"), - ([resolution_tag[:-1] + "0"], resolution, "invalid immutable"), - ) - for resolution_tags, record, message in failure_cases: - with ( - self.subTest(message=message), - mock.patch( - "scripts.component_release_recovery.list_continuity_resolution_tags", - return_value=resolution_tags, - ), - mock.patch( - "scripts.component_release_recovery.resolve_tag", - return_value="f" * 40, - ), - mock.patch( - "scripts.component_release_recovery.read_record", - return_value=record, - ), - self.assertRaisesRegex(RecoveryError, message), - ): - resolve_continuity_successor_fork(client, interrupted, successors) - - def test_continuity_resolution_requires_exact_successful_candidate_qualification(self) -> None: - qualification = continuity_resolution_qualification() - valid_run = continuity_resolution_qualification_run() - client = mock.Mock() - client.json.return_value = valid_run - self.assertEqual( - qualification, - validate_continuity_resolution_qualification(qualification, client), - ) - failures = ( - ("absent", None, "qualification is absent"), - ("pending", {**valid_run, "status": "in_progress", "conclusion": None}, "qualification is pending"), - ("failed", {**valid_run, "conclusion": "failure"}, "qualification failed"), - ("cancelled", {**valid_run, "conclusion": "cancelled"}, "qualification was cancelled"), - ("mismatched SHA", {**valid_run, "head_sha": "8" * 40}, "another source revision"), - ( - "untrusted workflow", - {**valid_run, "path": ".github/workflows/untrusted.yml@main"}, - "untrusted workflow", - ), - ) - for label, run, message in failures: - with self.subTest(label=label): - client.json.return_value = run - with self.assertRaisesRegex(RecoveryError, message): - validate_continuity_resolution_qualification(qualification, client) - - def test_recovery_composer_verification_expands_minified_exact_version_strictly(self) -> None: - component = COMPONENTS["sdk-php"] - commit = "a" * 40 - client = mock.Mock() - client.json.return_value = { - "minified": "composer/2.0", - "packages": { - component.package: [ - { - "version": "0.1.2", - "source": {"reference": commit}, - "dist": {"reference": commit}, - }, - {"version": "0.1.1"}, - ] - }, - } - - result = verify_composer(client, component, "0.1.1", commit) - self.assertEqual(commit, result["source_reference"]) - self.assertEqual(commit, result["dist_reference"]) - - client.json.return_value["packages"][component.package][1]["dist"] = {"reference": "b" * 40} - with self.assertRaisesRegex(RecoveryError, "Packagist identity.*not"): - verify_composer(client, component, "0.1.1", commit) - - def test_recovery_composer_verification_rejects_invalid_compact_identity_and_order(self) -> None: - component = COMPONENTS["sdk-php"] - commit = "a" * 40 - first = { - "version": "0.1.1", - "source": {"reference": commit}, - "dist": {"reference": commit}, - } - client = mock.Mock() - - cases = ( - ([first, {"version": "0.1.2"}], "strictly descending"), - ([first, {"version": "0.1.0"}, {"dist": {"reference": commit}}], "declare a version"), - ) - for versions, error in cases: - with self.subTest(error=error): - client.json.return_value = { - "minified": "composer/2.0", - "packages": {component.package: versions}, - } - with self.assertRaisesRegex(RecoveryError, error): - verify_composer(client, component, "0.1.1", commit) - - def test_recovery_composer_verification_rejects_ambiguous_exact_version_before_provenance(self) -> None: - component = COMPONENTS["sdk-php"] - commit = "a" * 40 - client = mock.Mock() - client.json.return_value = { - "packages": { - component.package: [ - { - "version": "0.1.1", - "source": {"reference": commit}, - "dist": {"reference": commit}, - }, - { - "version": "v0.1.1", - "source": {"reference": "b" * 40}, - "dist": {"reference": "b" * 40}, - }, - ] - } - } - - with self.assertRaisesRegex(RecoveryError, "multiple records"): - verify_composer(client, component, "0.1.1", commit) - - def test_scheduled_continuity_recovery_waits_for_remote_resume(self) -> None: - candidate = plan() - with ( - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=["a" * 40, None], - ), - mock.patch("scripts.component_release_recovery.read_record", return_value=candidate), - ): - paused = scheduled_continuity_pause(mock.Mock(), candidate) - - self.assertEqual( - f"beta-continuity/{candidate['plan']}/resumed", - paused["resumed_tag"], - ) - with ( - mock.patch( - "scripts.component_release_recovery.resolve_tag", - side_effect=["a" * 40, "b" * 40], - ), - mock.patch("scripts.component_release_recovery.read_record", return_value=candidate), - ): - self.assertIsNone(scheduled_continuity_pause(mock.Mock(), candidate)) - - def test_recovery_public_client_retries_transient_github_reads(self) -> None: - sleeps: list[float] = [] - client = PublicClient(token="test-token", max_attempts=3, retry_base_seconds=1, sleep=sleeps.append) - responses = [ - github_cli_result(503, **{"Retry-After": "4"}), - github_cli_result(0, stderr=b"connection reset by peer"), - github_cli_result(), - ] - - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - side_effect=responses, - ) as run, - ): - result = client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - - self.assertEqual([], result) - self.assertEqual([4, 2], sleeps) - self.assertEqual(3, run.call_count) - - def test_runner_rate_limit_headers_are_case_insensitive_and_use_reset_delay(self) -> None: - sleeps: list[float] = [] - client = PublicClient( - token="test-token", - max_attempts=2, - retry_base_seconds=1, - sleep=sleeps.append, - now=lambda: 100, - ) - responses = [ - github_cli_result( - 403, - b'{"message":"Forbidden"}', - **{ - "x-ratelimit-remaining": "0", - "X-rAtElImIt-ReSeT": "112", - }, - ), - github_cli_result(), - ] - - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - side_effect=responses, - ) as run, - ): - result = client.json("https://api.github.com/repos/durable-workflow/.github/releases") - - self.assertEqual([], result) - self.assertEqual([12], sleeps) - self.assertEqual(2, run.call_count) - - def test_recovery_public_client_uses_github_cli_and_retries_transient_certificate_failure( - self, - ) -> None: - sleeps: list[float] = [] - client = PublicClient(token="test-token", max_attempts=2, retry_base_seconds=1, sleep=sleeps.append) - certificate_error = github_cli_result( - 0, - stderr=b"x509: certificate signed by unknown authority", - ) - - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - side_effect=[certificate_error, github_cli_result()], - ) as run, - ): - result = client.json("https://api.github.com/repos/durable-workflow/.github/releases") - - self.assertEqual([], result) - self.assertEqual([1], sleeps) - command = run.call_args.args[0] - self.assertEqual( - ["gh", "api", "--hostname", "github.com", "--include", "--method", "GET"], - command[:7], - ) - self.assertEqual("repos/durable-workflow/.github/releases", command[-1]) - self.assertNotIn("--insecure", command) - self.assertEqual("test-token", run.call_args.kwargs["env"]["GH_TOKEN"]) - self.assertEqual("1", run.call_args.kwargs["env"]["GH_PROMPT_DISABLED"]) - - def test_recovery_public_client_reports_persistent_certificate_failure_as_runner_transport( - self, - ) -> None: - client = PublicClient(token="test-token", max_attempts=2, retry_base_seconds=1, sleep=lambda _delay: None) - certificate_error = github_cli_result( - 0, - stderr=b"x509: certificate signed by unknown authority", - ) - - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - return_value=certificate_error, - ) as run, - self.assertRaises(PublicInfrastructureError) as raised, - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases") - - self.assertEqual(2, run.call_count) - self.assertEqual( - { - "classification": "github-read-transient", - "endpoint_class": "releases-api", - "attempts": 2, - "reason": "retry-exhausted", - "failure": "transport=tls-certificate-verification", - }, - raised.exception.evidence, - ) - - def test_authenticated_requests_preserve_endpoint_api_versions(self) -> None: - cases = ( - ({"X-GitHub-Api-Version": SUPERSESSION_API_VERSION}, SUPERSESSION_API_VERSION), - ({}, "2022-11-28"), - ) - for headers, expected_version in cases: - with self.subTest(expected_version=expected_version): - client = PublicClient(token="test-token") - with ( - mock.patch.dict( - os.environ, - {"GITHUB_ACTIONS": "true", "GH_HOST": "redirected.example"}, - ), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - return_value=github_cli_result(), - ) as run, - ): - response = client.request( - "https://api.github.com/repos/durable-workflow/.github/actions/runs/456", - headers=headers, - ) - - self.assertEqual(b"[]", response.read()) - command = run.call_args.args[0] - declared_headers = [ - command[index + 1] - for index, argument in enumerate(command) - if argument == "--header" - ] - self.assertIn(f"X-GitHub-Api-Version: {expected_version}", declared_headers) - self.assertFalse(any(header.lower().startswith("authorization:") for header in declared_headers)) - self.assertEqual("test-token", run.call_args.kwargs["env"]["GH_TOKEN"]) - - def test_runner_environment_never_uses_a_live_urllib_api_call(self) -> None: - client = PublicClient(token="test-token") - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.urllib.request.urlopen", - side_effect=AssertionError("runner transport bypassed the GitHub CLI mock"), - ) as open_url, - mock.patch( - "scripts.component_release_recovery.subprocess.run", - return_value=github_cli_result(), - ) as run, - ): - self.assertEqual( - [], - client.json("https://api.github.com/repos/durable-workflow/.github/releases"), - ) - - open_url.assert_not_called() - run.assert_called_once() - - def test_recovery_public_client_never_retries_authentication_with_rate_limit_guidance(self) -> None: - sleeps: list[float] = [] - client = PublicClient(token="test-token", max_attempts=3, retry_base_seconds=1, sleep=sleeps.append) - error = github_cli_result( - 401, - b"Bad credentials: API rate limit exceeded", - **{"Retry-After": "20", "X-RateLimit-Remaining": "0"}, - ) - - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - return_value=error, - ) as run, - self.assertRaisesRegex(RecoveryError, r"public request failed \(401\)"), - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - - self.assertEqual([], sleeps) - self.assertEqual(1, run.call_count) - - def test_recovery_public_client_separates_exhausted_infrastructure_from_missing_resources(self) -> None: - client = PublicClient(token="test-token", max_attempts=2, retry_base_seconds=1, sleep=lambda _delay: None) - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - side_effect=[github_cli_result(503), github_cli_result(502)], - ) as run, - self.assertRaisesRegex( - PublicInfrastructureError, - r"endpoint_class=releases-api, attempts=2, reason=retry-exhausted, status=502", - ), - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases?per_page=100") - self.assertEqual(2, run.call_count) - - with ( - mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}), - mock.patch( - "scripts.component_release_recovery.subprocess.run", - return_value=github_cli_result(404), - ) as run, - self.assertRaisesRegex(NotFound, "public resource is absent"), - ): - client.json("https://api.github.com/repos/durable-workflow/.github/releases/tags/missing") - self.assertEqual(1, run.call_count) - - def test_explicit_discovery_accepts_only_an_exact_historical_v1_plan(self) -> None: - historical = legacy_beta_one_release_plan() - historical_tag = f"release-plan/{historical['plan']}" - record_commit = "a" * 40 - client = mock.Mock() - client.json.return_value = {"tag_name": historical_tag} - - with ( - mock.patch("scripts.component_release_recovery.resolve_tag", return_value=record_commit), - mock.patch( - "scripts.component_release_recovery.read_record", - side_effect=[historical, NotFound("no historical preparation", "plan-discovery")], - ), - mock.patch("scripts.component_release_recovery.validate_release_mirrors"), - mock.patch("scripts.component_release_recovery.verify_component"), - ): - selected = discover_plan(client, historical_tag, "waterline") - - self.assertEqual(historical_tag, selected[0]) - self.assertEqual(historical, selected[2]) - - unrecorded = {**historical, "plan": "beta-1-unrecorded"} - with ( - mock.patch("scripts.component_release_recovery.resolve_tag", return_value=record_commit), - mock.patch("scripts.component_release_recovery.read_record", return_value=unrecorded), - self.assertRaisesRegex(RecoveryError, "not an exact recorded historical contract"), - ): - discover_plan(client, f"release-plan/{unrecorded['plan']}", "waterline") - - def test_implicit_discovery_reverifies_a_completed_historical_v1_plan(self) -> None: - historical = legacy_beta_one_release_plan() - historical_tag = f"release-plan/{historical['plan']}" - record_commit = "a" * 40 - recorded_at = dt.datetime(2026, 7, 9, tzinfo=dt.UTC) - client = mock.Mock() - client.json.return_value = {"tag_name": historical_tag} - - def read_historical_record( - _client: mock.Mock, - _tag: str, - _commit: str, - filename: str, - ) -> dict[str, object]: - if filename == "release-plan.json": - return historical - raise NotFound("no historical preparation", "plan-discovery") - - with ( - mock.patch("scripts.component_release_recovery.list_release_plan_tags", return_value=[historical_tag]), - mock.patch("scripts.component_release_recovery.resolve_tag", return_value=record_commit), - mock.patch("scripts.component_release_recovery.read_record", side_effect=read_historical_record), - mock.patch( - "scripts.component_release_recovery.direct_plan_lifecycle", - return_value=("completed", None), - ), - mock.patch( - "scripts.component_release_recovery.immutable_plan_recorded_at", - return_value=recorded_at, - ), - mock.patch("scripts.component_release_recovery.accepted_continuity_supersession", return_value=None), - mock.patch("scripts.component_release_recovery.validate_release_mirrors"), - mock.patch("scripts.component_release_recovery.verify_component") as verify_component, - ): - selected = discover_plan(client, None, "waterline") - self.assertEqual(historical_tag, selected[0]) - verify_component.assert_called_once() - - unrecorded = {**historical, "plan": "beta-1-unrecorded"} - - def read_unrecorded_plan( - _client: mock.Mock, - _tag: str, - _commit: str, - _filename: str, - ) -> dict[str, object]: - return unrecorded - - with ( - mock.patch( - "scripts.component_release_recovery.list_release_plan_tags", - return_value=[f"release-plan/{unrecorded['plan']}"], - ), - mock.patch("scripts.component_release_recovery.resolve_tag", return_value=record_commit), - mock.patch("scripts.component_release_recovery.read_record", side_effect=read_unrecorded_plan), - self.assertRaisesRegex(RecoveryError, "not an exact recorded historical contract"), - ): - discover_plan(client, None, "waterline") - - def test_discovery_rejects_missing_preparation_for_an_incomplete_release(self) -> None: - candidate = plan() - tag = f"release-plan/{candidate['plan']}" - record_commit = "a" * 40 - client = mock.Mock() - client.json.return_value = { - "tag_name": tag, - "draft": False, - "assets": [ - { - "name": "release-plan.json", - "browser_download_url": "https://example.invalid/release-plan.json", - } - ], - } - client.bytes.return_value = canonical_json(candidate) - - with ( - mock.patch("scripts.component_release_recovery.resolve_tag", return_value=record_commit), - mock.patch( - "scripts.component_release_recovery.read_record", - side_effect=[candidate, NotFound("missing preparation", "plan-discovery")], - ), - mock.patch( - "scripts.component_release_recovery.verify_component", - side_effect=NotFound("release is incomplete"), - ), - self.assertRaisesRegex(RecoveryError, "only completed legacy releases"), - ): - discover_plan(client, tag, "workflow") - - def test_resolution_rejects_missing_preparation_before_publish(self) -> None: - candidate = plan() - with ( - mock.patch("scripts.component_release_recovery.verify_plan_authority", return_value=({}, {})), - mock.patch("scripts.component_release_recovery.resolve_tag", return_value=None), - self.assertRaisesRegex(RecoveryError, "release preparation required before publishing workflow"), - ): - resolve_component( - mock.Mock(), - "workflow", - f"release-plan/{candidate['plan']}", - "a" * 40, - candidate, - None, - ) - - def test_completed_legacy_release_is_the_only_missing_preparation_exception(self) -> None: - candidate = plan() - identity = candidate["components"]["workflow"] - public_evidence = {"version": identity["version"], "commit": identity["commit"]} - with ( - mock.patch("scripts.component_release_recovery.verify_plan_authority", return_value=({}, {})), - mock.patch("scripts.component_release_recovery.resolve_tag", return_value=identity["commit"]), - mock.patch("scripts.component_release_recovery.verify_component", return_value=public_evidence), - ): - state, outputs = resolve_component( - mock.Mock(), - "workflow", - f"release-plan/{candidate['plan']}", - "a" * 40, - candidate, - None, - ) - - self.assertEqual("skip", outputs["action"]) - self.assertEqual("complete", state["phase"]) - self.assertEqual(public_evidence, state["public_evidence"]) - self.assertNotIn("release_preparation", state) - - def test_dependency_progression_is_public_and_acyclic(self) -> None: - self.assertEqual((), COMPONENTS["workflow"].dependencies) - self.assertEqual((), COMPONENTS["sdk-php"].dependencies) - self.assertEqual(("workflow", "sdk-php"), COMPONENTS["waterline"].dependencies) - self.assertEqual(("workflow",), COMPONENTS["server"].dependencies) - self.assertEqual(("server",), COMPONENTS["cli"].dependencies) - self.assertEqual(("server",), COMPONENTS["sdk-python"].dependencies) - self.assertEqual(("server",), COMPONENTS["sdk-rust"].dependencies) - - def test_expected_default_branches_are_explicit(self) -> None: - self.assertEqual("v2", COMPONENTS["workflow"].default_branch) - self.assertEqual("v2", COMPONENTS["waterline"].default_branch) - for name in {"server", "cli", "sdk-php", "sdk-python", "sdk-rust"}: - self.assertEqual("main", COMPONENTS[name].default_branch) - - def test_alpha_and_beta_plans_validate_independently(self) -> None: - for channel in ("alpha", "beta"): - candidate = plan(channel) - validate_plan(candidate) - validate_release_preparation(preparation(candidate), candidate) - - def test_preparation_rejects_notes_for_another_version(self) -> None: - candidate = plan() - prepared = preparation(candidate) - prepared["components"]["server"]["version"] = "9.9.9" - with self.assertRaisesRegex(RecoveryError, "different planned identity"): - validate_release_preparation(prepared, candidate) - - def test_beta_plan_rejects_alpha_workflow_version(self) -> None: - candidate = plan("beta") - candidate["components"]["workflow"]["version"] = "2.0.0-alpha.8" - with self.assertRaisesRegex(RecoveryError, "2.0.0-beta.N"): - validate_plan(candidate) - - def test_publication_workflows_dispatch_in_the_declared_tag_context(self) -> None: - dispatching = { - "server": ("release.yml", "tag"), - "cli": ("release.yml", "tag"), - "sdk-python": ("publish.yml", "release_tag"), - "sdk-rust": ("release.yml", "release_tag"), - } - self.assertEqual(dispatching, { - name: (component.release_workflow, component.release_tag_input) - for name, component in COMPONENTS.items() - if component.release_workflow is not None - }) - for name, (workflow, tag_input) in dispatching.items(): - with self.subTest(component=name): - source = f'''on: - schedule: - workflow_dispatch: -jobs: - recover: - steps: - - run: python recovery.py resolve --preparation-output release-preparation.json - - name: Create the exact source tag - run: | - gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \\ - -f ref="refs/tags/$RELEASE_TAG" -f sha="$RELEASE_COMMIT" - - name: Start repository-owned publication - run: | - gh run list --workflow {workflow} \\ - --json databaseId,displayTitle,headBranch,headSha,status,conclusion - python scripts/ci/component-release-recovery.py select-publication-run \\ - --release-tag "$RELEASE_TAG" --release-commit "$RELEASE_COMMIT" - gh workflow run {workflow} --ref "$RELEASE_TAG" \\ - -f {tag_input}="$RELEASE_TAG" -f release_plan="$PLAN_TAG" -''' - expected_sha256 = normalized_source_sha256(source) - verify_recovery_workflow_source(name, source, expected_sha256) - with self.assertRaisesRegex(RecoveryError, "protected source identity"): - verify_recovery_workflow_source( - name, - source.replace('"$RELEASE_TAG" \\\n', '"$DEFAULT_BRANCH" \\\n', 1), - expected_sha256, - ) - with self.assertRaisesRegex(RecoveryError, "protected source identity"): - verify_recovery_workflow_source( - name, - source.replace(f'-f {tag_input}="$RELEASE_TAG"', f'-f {tag_input}="$DEFAULT_BRANCH"'), - expected_sha256, - ) - - def test_publication_run_selection_adopts_tag_triggered_runs(self) -> None: - release_tag = "1.2.3" - release_commit = "a" * 40 - - def run(status: str, conclusion: str | None, run_id: int = 17) -> dict[str, object]: - return { - "databaseId": run_id, - "displayTitle": f"Release {release_tag} for direct", - "headBranch": release_tag, - "headSha": release_commit, - "status": status, - "conclusion": conclusion, - } - - cases = ( - ("queued", None, "wait"), - ("in_progress", None, "wait"), - ("completed", "failure", "rerun"), - ("completed", "success", "complete"), - ) - for status, conclusion, action in cases: - with self.subTest(status=status, conclusion=conclusion): - self.assertEqual( - {"action": action, "run_id": 17, "status": status, "conclusion": conclusion}, - select_publication_run(release_tag, release_commit, [run(status, conclusion)]), - ) - self.assertEqual( - {"action": "dispatch", "run_id": None, "status": None, "conclusion": None}, - select_publication_run(release_tag, release_commit, []), - ) - with self.assertRaisesRegex(RecoveryError, "different source commit"): - select_publication_run( - release_tag, - release_commit, - [{**run("queued", None), "headSha": "b" * 40}], - ) - - def test_cli_release_rejects_assets_attested_for_the_wrong_source(self) -> None: - attested_commit = "a" * 40 - declared_commit = "b" * 40 - version = "1.2.3" - attested_ref = "refs/tags/1.2.2" - - class FixtureClient: - contents = {name: f"fixture {name}\n".encode() for name in CLI_ASSETS - {"SHA256SUMS"}} - checksums = "".join( - f"{hashlib.sha256(content).hexdigest()} {name}\n" for name, content in sorted(contents.items()) - ).encode() - - def __init__(self) -> None: - self.downloaded: set[str] = set() - - def json(self, _url: str) -> dict[str, object]: - return { - "id": 123, - "tag_name": version, - "draft": False, - "html_url": f"https://github.com/durable-workflow/cli/releases/tag/{version}", - "assets": [ - { - "id": index, - "name": name, - "browser_download_url": f"https://example.invalid/{name}", - } - for index, name in enumerate(sorted(CLI_ASSETS), start=1) - ], - } - - def bytes(self, _url: str) -> bytes: - return self.checksums - - def download(self, url: str, path: Path, *, expected_sha256: str) -> dict[str, object]: - name = url.rsplit("/", 1)[-1] - content = self.contents[name] - if expected_sha256 != hashlib.sha256(content).hexdigest(): - raise AssertionError("fixture download checksum mismatch") - path.write_bytes(content) - self.downloaded.add(name) - return {"url": url, "size": len(content), "sha256": expected_sha256} - - def verify_attestation(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]: - self.assertEqual("durable-workflow/cli", command[command.index("--repo") + 1]) - if "--source-digest" not in command: - return subprocess.CompletedProcess( - command, - 1, - stdout="", - stderr="workflow authority does not match the declared release", - ) - source_digest = command[command.index("--source-digest") + 1] - source_ref = command[command.index("--source-ref") + 1] - valid = source_digest == attested_commit and source_ref == attested_ref - return subprocess.CompletedProcess( - command, - 0 if valid else 1, - stdout="", - stderr="attestation source does not match the declared release", - ) - - client = FixtureClient() - shutil_module = mock.Mock() - shutil_module.which.return_value = "/usr/bin/gh" - subprocess_module = mock.Mock() - subprocess_module.run.side_effect = verify_attestation - with ( - mock.patch("scripts.component_release_recovery.shutil", shutil_module, create=True), - mock.patch("scripts.component_release_recovery.subprocess", subprocess_module, create=True), - self.assertRaisesRegex(RecoveryError, "build attestation failed"), - ): - verify_cli(client, COMPONENTS["cli"], version, declared_commit) - self.assertEqual(CLI_ASSETS - {"SHA256SUMS"}, client.downloaded) - - def test_post_discovery_failures_retain_explicit_and_scheduled_plan_identity(self) -> None: - candidate = plan() - plan_tag = "release-plan/plan-a" - record_commit = "d" * 40 - - for requested_tag in (plan_tag, None): - with self.subTest(requested_tag=requested_tag), tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - plan_output = root / "release-plan.json" - preparation_output = root / "release-preparation.json" - evidence_output = root / "release-recovery-evidence.json" - arguments = [ - "component_release_recovery.py", - "resolve", - "--component", - "server", - "--plan-output", - str(plan_output), - "--preparation-output", - str(preparation_output), - "--evidence", - str(evidence_output), - ] - if requested_tag is not None: - arguments.extend(("--plan-tag", requested_tag)) - else: - arguments.append("--allow-empty") - - with ( - mock.patch.object(sys, "argv", arguments), - mock.patch( - "scripts.component_release_recovery.discover_plan", - return_value=(plan_tag, record_commit, candidate, preparation(candidate), None), - ) as discover, - mock.patch( - "scripts.component_release_recovery.resolve_component", - side_effect=RecoveryError("post-discovery failure", "tag-preflight"), - ), - mock.patch( - "scripts.component_release_recovery.scheduled_continuity_pause", - return_value=None, - ) as continuity_pause, - ): - self.assertEqual(1, main()) - - discover.assert_called_once_with(mock.ANY, requested_tag, "server") - if requested_tag is None: - continuity_pause.assert_called_once_with(mock.ANY, candidate) - else: - continuity_pause.assert_not_called() - self.assertEqual(canonical_json(candidate), plan_output.read_bytes()) - self.assertEqual( - canonical_json(preparation(candidate)), - preparation_output.read_bytes(), - ) - evidence = json.loads(evidence_output.read_bytes()) - self.assertEqual(plan_tag, evidence["release_plan_tag"]) - self.assertEqual(candidate["plan"], evidence["plan"]) - self.assertEqual(candidate["channel"], evidence["channel"]) - self.assertEqual(record_commit, evidence["plan_record_commit"]) - self.assertEqual(plan_tag, evidence["durable_evidence"]["release_plan"]) - self.assertTrue(evidence["resume_action"].endswith(f" for {plan_tag}")) - - def test_scheduled_discovery_without_plan_authority_records_no_op(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - evidence_output = root / "release-recovery-evidence.json" - github_output = root / "github-output" - arguments = [ - "component_release_recovery.py", - "resolve", - "--component", - "server", - "--plan-output", - str(root / "release-plan.json"), - "--preparation-output", - str(root / "release-preparation.json"), - "--evidence", - str(evidence_output), - "--github-output", - str(github_output), - "--allow-empty", - ] - - with ( - mock.patch.object(sys, "argv", arguments), - mock.patch( - "scripts.component_release_recovery.discover_plan", - side_effect=RecoveryError("no public release plan is available", "plan-discovery"), - ), - ): - self.assertEqual(0, main()) - - evidence = json.loads(evidence_output.read_bytes()) - self.assertEqual("plan-discovery", evidence["phase"]) - self.assertEqual("no-op", evidence["outcome"]) - self.assertEqual("action=none\n", github_output.read_text()) - - def test_scheduled_transport_exhaustion_records_runner_outcome_and_no_publication_action(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - evidence_output = root / "release-recovery-evidence.json" - github_output = root / "github-output" - arguments = [ - "component_release_recovery.py", - "resolve", - "--component", - "server", - "--plan-output", - str(root / "release-plan.json"), - "--preparation-output", - str(root / "release-preparation.json"), - "--evidence", - str(evidence_output), - "--github-output", - str(github_output), - "--allow-empty", - ] - unavailable = PublicInfrastructureError( - "releases-api", - 5, - reason="retry-exhausted", - failure="transport=tls-certificate-verification", - ) - - with ( - mock.patch.object(sys, "argv", arguments), - mock.patch( - "scripts.component_release_recovery.discover_plan", - side_effect=unavailable, - ), - ): - self.assertEqual(75, main()) - - evidence = json.loads(evidence_output.read_bytes()) - self.assertEqual("runner-transport", evidence["phase"]) - self.assertEqual("runner-transport", evidence["outcome"]) - self.assertEqual(unavailable.evidence, evidence["transport"]) - self.assertEqual("action=none\n", github_output.read_text()) - - -class ReleaseCandidateChannelTest(unittest.TestCase): - def test_rc_plan_retains_coherent_beta_qualification(self) -> None: - candidate = plan("rc") - for identity in candidate["components"].values(): - identity["version"] = "2.0.0-rc.5" - validate_plan(candidate) - beta = plan("beta") - record = { - "schema": "durable-workflow.beta-authorization/v1", - "channel": "beta", - "candidate": beta["plan"], - "components": beta["components"], - } - for identity in record["components"].values(): - identity["version"] = "2.0.0-beta.21" - self.assertTrue( - beta_authorization_matches_plan( - candidate, - candidate["beta_authorization"], - record, - ) - ) - - def test_aggregate_rc_plan_uses_its_exact_candidate_foundation(self) -> None: - candidate = plan("rc") - for identity in candidate["components"].values(): - identity["version"] = "2.0.0-rc.5" - candidate["foundation"] = { - "tag": f"beta-candidate/rc-{candidate['plan']}", - "commit": "e" * 40, - } - candidate["beta_authorization"] = None - - validate_plan(candidate) - - def test_aggregate_rc_plan_rejects_a_moved_candidate_foundation_tag(self) -> None: - candidate = plan("rc") - for identity in candidate["components"].values(): - identity["version"] = "2.0.0-rc.5" - candidate["foundation"] = { - "tag": f"beta-candidate/rc-{candidate['plan']}", - "commit": "e" * 40, - } - candidate["beta_authorization"] = None - - with ( - mock.patch( - "scripts.component_release_recovery.resolve_tag", - return_value="d" * 40, - ), - mock.patch("scripts.component_release_recovery.read_record") as read_record, - self.assertRaisesRegex(RecoveryError, "tag does not match its pinned commit"), - ): - verify_plan_authority(mock.Mock(), candidate) - read_record.assert_not_called() - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_current_plan_publication.py b/tests/test_current_plan_publication.py deleted file mode 100644 index 93aacc9..0000000 --- a/tests/test_current_plan_publication.py +++ /dev/null @@ -1,619 +0,0 @@ -from __future__ import annotations - -import base64 -import contextlib -import copy -import hashlib -import io -import json -import unittest -import urllib.parse -from pathlib import Path -from typing import Any - -import yaml - -from scripts.current_plan_publication import ( - AUTHORITY_REF, - BETA_AUTHORIZATION_ENVIRONMENT, - CONTROL_REPOSITORY, - CURRENT_PLAN_RUN_EVENTS, - CURRENT_PLAN_WORKFLOW, - CURRENT_PLAN_WORKFLOW_PATH, - CURRENT_PLAN_WORKFLOW_REF, - OBSERVER_WORKFLOW_REF, - CurrentPlanPublicationError, - approved_writer_handoff, - reconcile_current_plan_dispatch, - validate_approved_writer_handoff, - validate_runtime_identity, -) - -ROOT = Path(__file__).resolve().parents[1] -OBSERVER_WORKFLOW = ROOT / ".github/workflows/release-plan-observer.yml" -CURRENT_WORKFLOW = ROOT / ".github/workflows/current-release-plan.yml" -CONTINUITY_WORKFLOW = ROOT / ".github/workflows/beta-continuity.yml" -CURRENT_PLAN = ROOT / "release-plans/current.json" -PLAN_REGISTRY_GROUP = "release-plan-registry" -WORKFLOW_ID = 7321 -CURRENT_SHA = "b" * 40 -OLDER_SHA = "a" * 40 - - -def canonical_json(value: Any) -> bytes: - return (json.dumps(value, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - - -def current_plan() -> dict[str, Any]: - return json.loads(CURRENT_PLAN.read_bytes()) - - -def plan_identity(plan: dict[str, Any]) -> tuple[str, str]: - return f"release-plan/{plan['plan']}", hashlib.sha256(canonical_json(plan)).hexdigest() - - -def workflow_run( - run_id: int, - *, - source_sha: str = CURRENT_SHA, - status: str = "waiting", - created_at: str = "2026-08-11T01:00:00Z", - event: str = "workflow_dispatch", -) -> dict[str, Any]: - return { - "conclusion": None, - "created_at": created_at, - "event": event, - "head_branch": AUTHORITY_REF, - "head_repository": {"full_name": CONTROL_REPOSITORY}, - "head_sha": source_sha, - "html_url": f"https://github.com/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - "id": run_id, - "path": f"{CURRENT_PLAN_WORKFLOW_PATH}@{AUTHORITY_REF}", - "repository": {"full_name": CONTROL_REPOSITORY}, - "run_attempt": 1, - "status": status, - "url": f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/runs/{run_id}", - "workflow_id": WORKFLOW_ID, - } - - -def pending_authorization() -> list[dict[str, Any]]: - return [ - { - "current_user_can_approve": False, - "environment": { - "id": 19, - "name": BETA_AUTHORIZATION_ENVIRONMENT, - "url": ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/environments/" - f"{BETA_AUTHORIZATION_ENVIRONMENT}" - ), - }, - "reviewers": [{"type": "Team", "reviewer": {"id": 71}}], - } - ] - - -class FakeActionsClient: - def __init__( - self, - runs: list[dict[str, Any]], - *, - current_sha: str = CURRENT_SHA, - pending: dict[int, Any] | None = None, - historical_plans: dict[str, dict[str, Any]] | None = None, - comparisons: dict[str, Any] | None = None, - workflow_metadata: dict[str, Any] | None = None, - ) -> None: - self.runs = runs - self.current_sha = current_sha - self.pending = pending or {} - self.historical_plans = historical_plans or {} - self.comparisons = comparisons or {} - self.workflow_metadata = ( - workflow_metadata - if workflow_metadata is not None - else { - "html_url": ( - f"https://github.com/{CONTROL_REPOSITORY}/blob/{AUTHORITY_REF}/" - f"{CURRENT_PLAN_WORKFLOW_PATH}" - ), - "id": WORKFLOW_ID, - "name": "Current release plan", - "path": CURRENT_PLAN_WORKFLOW_PATH, - "state": "active", - "url": ( - f"https://api.github.com/repos/{CONTROL_REPOSITORY}/actions/workflows/" - f"{WORKFLOW_ID}" - ), - } - ) - self.posts: list[tuple[str, Any | None]] = [] - self.gets: list[str] = [] - - def get(self, path: str) -> Any: - self.gets.append(path) - if path == f"/repos/{CONTROL_REPOSITORY}/actions/workflows/{CURRENT_PLAN_WORKFLOW}": - return self.workflow_metadata - if path == f"/repos/{CONTROL_REPOSITORY}/git/ref/heads/{AUTHORITY_REF}": - return { - "object": {"sha": self.current_sha, "type": "commit"}, - "ref": f"refs/heads/{AUTHORITY_REF}", - } - if path.startswith(f"/repos/{CONTROL_REPOSITORY}/actions/workflows/{WORKFLOW_ID}/runs?"): - query = urllib.parse.parse_qs(urllib.parse.urlsplit(path).query) - status = query["status"][0] - event = query.get("event", [None])[0] - page = int(query["page"][0]) - matching = [ - run - for run in self.runs - if run.get("status") == status and (event is None or run.get("event") == event) - ] - start = (page - 1) * 100 - return {"total_count": len(matching), "workflow_runs": matching[start : start + 100]} - suffix = "/pending_deployments" - if path.endswith(suffix) and "/actions/runs/" in path: - run_id = int(path.removesuffix(suffix).rsplit("/", 1)[-1]) - return self.pending.get(run_id, []) - contents_prefix = f"/repos/{CONTROL_REPOSITORY}/contents/release-plans/current.json?ref=" - if path.startswith(contents_prefix): - revision = path.removeprefix(contents_prefix) - plan = self.historical_plans[revision] - return { - "content": base64.encodebytes(canonical_json(plan)).decode(), - "encoding": "base64", - "path": "release-plans/current.json", - "type": "file", - } - compare_prefix = f"/repos/{CONTROL_REPOSITORY}/compare/" - if path.startswith(compare_prefix): - comparison = path.removeprefix(compare_prefix) - return self.comparisons[comparison] - raise AssertionError(f"unexpected GET {path}") - - def post(self, path: str, payload: Any | None = None) -> None: - self.posts.append((path, payload)) - - -def reconcile(client: FakeActionsClient, source_sha: str = CURRENT_SHA): - tag, digest = plan_identity(current_plan()) - return reconcile_current_plan_dispatch( - client, - plan_path=CURRENT_PLAN, - repository=CONTROL_REPOSITORY, - ref=AUTHORITY_REF, - workflow=CURRENT_PLAN_WORKFLOW, - source_sha=source_sha, - plan_tag=tag, - plan_sha256=digest, - observer_workflow_ref=OBSERVER_WORKFLOW_REF, - ) - - -def publication_step() -> dict[str, object]: - workflow = yaml.safe_load(OBSERVER_WORKFLOW.read_text(encoding="utf-8")) - steps = workflow["jobs"]["publish-current"]["steps"] - return next( - step - for step in steps - if step.get("name") == "Publish the matching aggregate current authority" - ) - - -class CurrentPlanPublicationTest(unittest.TestCase): - def test_deduper_classifies_every_current_plan_trigger_mode(self) -> None: - workflow = yaml.load(CURRENT_WORKFLOW.read_text(encoding="utf-8"), Loader=yaml.BaseLoader) - - self.assertEqual(set(workflow["on"]), set(CURRENT_PLAN_RUN_EVENTS)) - - def test_active_push_wait_for_exact_candidate_is_retained(self) -> None: - push_wait = workflow_run(100, event="push") - client = FakeActionsClient( - [push_wait], - pending={100: pending_authorization()}, - ) - - result = reconcile(client) - - self.assertEqual("retained", result.outcome) - self.assertEqual(push_wait["html_url"], result.retained_run_url) - self.assertEqual((), result.cancelled_run_urls) - self.assertEqual([], client.posts) - run_queries = [path for path in client.gets if "/actions/workflows/7321/runs?" in path] - self.assertTrue(run_queries) - self.assertTrue(all("event=" not in path for path in run_queries)) - - def test_active_run_from_unconfigured_trigger_fails_closed(self) -> None: - unexpected = workflow_run(99, event="pull_request") - client = FakeActionsClient([unexpected]) - - with self.assertRaisesRegex(CurrentPlanPublicationError, "malformed or mismatched"): - reconcile(client) - - self.assertEqual([], client.posts) - - def test_workflow_metadata_requires_exact_api_and_ref_identity(self) -> None: - metadata = FakeActionsClient([]).workflow_metadata - cases = ( - ( - "mismatched workflow ref", - { - **metadata, - "html_url": metadata["html_url"].replace("/blob/main/", "/blob/feature/"), - }, - ), - ( - "mismatched API workflow", - { - **metadata, - "url": metadata["url"].replace(str(WORKFLOW_ID), str(WORKFLOW_ID + 1)), - }, - ), - ) - - for label, mismatched in cases: - client = FakeActionsClient([], workflow_metadata=mismatched) - with ( - self.subTest(label=label), - self.assertRaisesRegex(CurrentPlanPublicationError, "workflow API authority"), - ): - reconcile(client) - self.assertEqual([], client.posts) - - def test_duplicate_same_candidate_waits_are_coalesced_and_later_observations_are_noops(self) -> None: - first = workflow_run(101, created_at="2026-08-11T01:00:00Z", event="push") - duplicate = workflow_run(102, created_at="2026-08-11T02:00:00Z") - client = FakeActionsClient( - [first, duplicate], - pending={101: pending_authorization(), 102: pending_authorization()}, - ) - output = io.StringIO() - - with contextlib.redirect_stdout(output): - initial = reconcile(client) - self.assertEqual("retained", initial.outcome) - self.assertEqual(first["html_url"], initial.retained_run_url) - self.assertEqual((duplicate["html_url"],), initial.cancelled_run_urls) - self.assertEqual( - [(f"/repos/{CONTROL_REPOSITORY}/actions/runs/102/cancel", None)], - client.posts, - ) - - client.runs = [first] - with contextlib.redirect_stdout(output): - repeated = reconcile(client) - self.assertEqual("retained", repeated.outcome) - self.assertEqual(first["html_url"], repeated.retained_run_url) - self.assertEqual((), repeated.cancelled_run_urls) - self.assertEqual(1, len(client.posts)) - self.assertEqual(2, output.getvalue().count("idempotent no-op")) - self.assertIn(str(first["html_url"]), output.getvalue()) - - def test_newer_candidate_supersedes_only_verified_unapproved_ancestor(self) -> None: - prior_plan = copy.deepcopy(current_plan()) - prior_plan["plan"] = "prior-current-plan" - old = workflow_run(201, source_sha=OLDER_SHA) - comparison = { - "ahead_by": 3, - "base_commit": {"sha": OLDER_SHA}, - "behind_by": 0, - "merge_base_commit": {"sha": OLDER_SHA}, - "status": "ahead", - } - client = FakeActionsClient( - [old], - pending={201: pending_authorization()}, - historical_plans={OLDER_SHA: prior_plan}, - comparisons={f"{OLDER_SHA}...{CURRENT_SHA}": comparison}, - ) - - result = reconcile(client) - - self.assertEqual("dispatched", result.outcome) - self.assertIsNone(result.retained_run_url) - self.assertEqual((old["html_url"],), result.cancelled_run_urls) - self.assertEqual( - [ - (f"/repos/{CONTROL_REPOSITORY}/actions/runs/201/cancel", None), - ( - f"/repos/{CONTROL_REPOSITORY}/actions/workflows/{WORKFLOW_ID}/dispatches", - {"ref": AUTHORITY_REF}, - ), - ], - client.posts, - ) - - def test_ambiguous_or_malformed_api_state_fails_before_mutation(self) -> None: - cases: list[tuple[str, FakeActionsClient, str]] = [] - - mismatched_run = workflow_run(301) - mismatched_run["head_repository"] = {"full_name": "outside/fork"} - cases.append( - ( - "mismatched repository", - FakeActionsClient([mismatched_run], pending={301: pending_authorization()}), - "malformed or mismatched", - ) - ) - - malformed_timestamp = workflow_run(304, created_at="2026-99-11T01:00:00Z") - cases.append( - ( - "malformed timestamp", - FakeActionsClient([malformed_timestamp], pending={304: pending_authorization()}), - "invalid timestamp", - ) - ) - - malformed_pending = pending_authorization() - malformed_pending[0]["environment"]["name"] = "stable-authorization" - cases.append( - ( - "mismatched pending environment", - FakeActionsClient([workflow_run(302)], pending={302: malformed_pending}), - "pending authorization is mismatched", - ) - ) - - prior_plan = copy.deepcopy(current_plan()) - prior_plan["plan"] = "diverged-plan" - diverged = { - "ahead_by": 1, - "base_commit": {"sha": OLDER_SHA}, - "behind_by": 1, - "merge_base_commit": {"sha": "c" * 40}, - "status": "diverged", - } - cases.append( - ( - "diverged source", - FakeActionsClient( - [workflow_run(303, source_sha=OLDER_SHA)], - pending={303: pending_authorization()}, - historical_plans={OLDER_SHA: prior_plan}, - comparisons={f"{OLDER_SHA}...{CURRENT_SHA}": diverged}, - ), - "not a verified ancestor", - ) - ) - - for label, client, diagnostic in cases: - with ( - self.subTest(label=label), - self.assertRaisesRegex(CurrentPlanPublicationError, diagnostic), - ): - reconcile(client) - self.assertEqual([], client.posts) - - def test_already_approved_writer_is_retained_and_never_cancelled(self) -> None: - writer = workflow_run(401, status="in_progress") - duplicate_wait = workflow_run(402, created_at="2026-08-11T02:00:00Z") - client = FakeActionsClient( - [writer, duplicate_wait], - pending={402: pending_authorization()}, - ) - - result = reconcile(client) - - self.assertEqual("retained", result.outcome) - self.assertEqual(writer["html_url"], result.retained_run_url) - self.assertEqual((duplicate_wait["html_url"],), result.cancelled_run_urls) - self.assertEqual( - [(f"/repos/{CONTROL_REPOSITORY}/actions/runs/402/cancel", None)], - client.posts, - ) - self.assertNotIn( - f"/repos/{CONTROL_REPOSITORY}/actions/runs/401/pending_deployments", - client.gets, - ) - - def test_older_writer_that_may_be_approved_fails_closed(self) -> None: - prior_plan = copy.deepcopy(current_plan()) - prior_plan["plan"] = "older-approved-plan" - comparison = { - "ahead_by": 2, - "base_commit": {"sha": OLDER_SHA}, - "behind_by": 0, - "merge_base_commit": {"sha": OLDER_SHA}, - "status": "ahead", - } - writer = workflow_run(451, source_sha=OLDER_SHA, status="queued") - client = FakeActionsClient( - [writer], - historical_plans={OLDER_SHA: prior_plan}, - comparisons={f"{OLDER_SHA}...{CURRENT_SHA}": comparison}, - ) - - with self.assertRaisesRegex(CurrentPlanPublicationError, "may have passed protected authorization"): - reconcile(client) - self.assertEqual([], client.posts) - - def test_exact_repository_workflow_ref_source_and_plan_are_required(self) -> None: - tag, digest = plan_identity(current_plan()) - valid = { - "plan_path": CURRENT_PLAN, - "repository": CONTROL_REPOSITORY, - "ref": AUTHORITY_REF, - "workflow": CURRENT_PLAN_WORKFLOW, - "source_sha": CURRENT_SHA, - "plan_tag": tag, - "plan_sha256": digest, - "observer_workflow_ref": OBSERVER_WORKFLOW_REF, - } - cases = ( - ("repository", "outside/fork"), - ("ref", "feature"), - ("workflow", "other.yml"), - ("source_sha", "short"), - ("plan_tag", "release-plan/other"), - ("plan_sha256", "0" * 64), - ("observer_workflow_ref", OBSERVER_WORKFLOW_REF.replace("observer", "other")), - ) - for field, value in cases: - client = FakeActionsClient([]) - with ( - self.subTest(field=field), - self.assertRaises(CurrentPlanPublicationError), - ): - reconcile_current_plan_dispatch(client, **{**valid, field: value}) - self.assertEqual([], client.gets) - self.assertEqual([], client.posts) - - def test_authority_ref_is_rechecked_before_any_mutation(self) -> None: - class MovingRefClient(FakeActionsClient): - def __init__(self) -> None: - super().__init__([workflow_run(501)], pending={501: pending_authorization()}) - self.ref_reads = 0 - - def get(self, path: str) -> Any: - value = super().get(path) - if path.endswith(f"/git/ref/heads/{AUTHORITY_REF}"): - self.ref_reads += 1 - if self.ref_reads == 2: - value["object"]["sha"] = "c" * 40 - return value - - client = MovingRefClient() - with self.assertRaisesRegex(CurrentPlanPublicationError, "no longer resolves"): - reconcile(client) - self.assertEqual([], client.posts) - - def test_authorization_is_rechecked_immediately_before_cancellation(self) -> None: - class ApprovalRaceClient(FakeActionsClient): - def __init__(self) -> None: - super().__init__([workflow_run(551)], pending={551: pending_authorization()}) - self.pending_reads = 0 - - def get(self, path: str) -> Any: - if path.endswith("/actions/runs/551/pending_deployments"): - self.pending_reads += 1 - if self.pending_reads == 2: - self.gets.append(path) - return [] - return super().get(path) - - current = workflow_run(550, created_at="2026-08-11T00:30:00Z") - client = ApprovalRaceClient() - client.runs.insert(0, current) - client.pending[550] = pending_authorization() - - with self.assertRaisesRegex(CurrentPlanPublicationError, "no longer awaiting"): - reconcile(client) - self.assertEqual([], client.posts) - - def test_observer_and_continuity_schedules_do_not_wait_on_protected_approval(self) -> None: - current = yaml.safe_load(CURRENT_WORKFLOW.read_text(encoding="utf-8")) - observer = yaml.safe_load(OBSERVER_WORKFLOW.read_text(encoding="utf-8")) - continuity = yaml.safe_load(CONTINUITY_WORKFLOW.read_text(encoding="utf-8")) - approval = current["jobs"]["authorize"] - writer = current["jobs"]["record"] - - self.assertNotIn("concurrency", current) - self.assertEqual(BETA_AUTHORIZATION_ENVIRONMENT, approval["environment"]) - self.assertNotIn("concurrency", approval) - self.assertEqual({"contents": "read"}, approval["permissions"]) - self.assertEqual("authorize", writer["needs"]) - self.assertIn("needs.authorize.result == 'success'", writer["if"]) - self.assertEqual( - {"group": PLAN_REGISTRY_GROUP, "cancel-in-progress": False}, - writer["concurrency"], - ) - self.assertEqual( - {"group": PLAN_REGISTRY_GROUP, "cancel-in-progress": False}, - observer["concurrency"], - ) - self.assertNotEqual(PLAN_REGISTRY_GROUP, continuity["concurrency"]["group"]) - self.assertNotIn("write", observer["jobs"]["observe"]["permissions"].values()) - - def test_observer_wires_exact_candidate_outputs_to_bounded_reconciliation(self) -> None: - workflow = yaml.safe_load(OBSERVER_WORKFLOW.read_text(encoding="utf-8")) - record = workflow["jobs"]["record"] - publish = workflow["jobs"]["publish-current"] - step = publication_step() - checkout = next(item for item in publish["steps"] if "actions/checkout" in item.get("uses", "")) - - self.assertEqual("${{ steps.aggregate.outputs.current-plan-tag }}", record["outputs"]["current-plan-tag"]) - self.assertEqual( - "${{ steps.aggregate.outputs.current-plan-sha256 }}", - record["outputs"]["current-plan-sha256"], - ) - self.assertEqual({"actions": "write", "contents": "read"}, publish["permissions"]) - self.assertIs(checkout["with"]["persist-credentials"], False) - self.assertEqual("${{ github.sha }}", checkout["with"]["ref"]) - self.assertEqual( - "release-plans/current.json\nscripts/current_plan_publication.py\n", - checkout["with"]["sparse-checkout"], - ) - self.assertEqual( - { - "GH_TOKEN": "${{ github.token }}", - "CURRENT_PLAN_SHA256": "${{ needs.record.outputs.current-plan-sha256 }}", - "CURRENT_PLAN_TAG": "${{ needs.record.outputs.current-plan-tag }}", - "TARGET_REF": AUTHORITY_REF, - "TARGET_REPOSITORY": CONTROL_REPOSITORY, - "TARGET_WORKFLOW": CURRENT_PLAN_WORKFLOW, - }, - step["env"], - ) - self.assertIn("current_plan_publication.py reconcile-dispatch", step["run"]) - self.assertIn('--observer-workflow-ref "$GITHUB_WORKFLOW_REF"', step["run"]) - self.assertIn('--source-sha "$GITHUB_SHA"', step["run"]) - self.assertIn('--plan-tag "$CURRENT_PLAN_TAG"', step["run"]) - self.assertIn('--plan-sha256 "$CURRENT_PLAN_SHA256"', step["run"]) - self.assertNotIn("gh workflow run", step["run"]) - - def test_approved_writer_handoff_is_exact_and_retry_safe(self) -> None: - identity = { - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "workflow_ref": CURRENT_PLAN_WORKFLOW_REF, - "source_sha": "d" * 40, - "run_id": 123456789, - "producer_attempt": 1, - } - handoff = approved_writer_handoff(**identity) - self.assertRegex(handoff, r"^[0-9a-f]{64}$") - validate_approved_writer_handoff(handoff, **identity, current_attempt=2) - - mismatches = ( - ({"handoff": "0" * 64}, "does not match"), - ({"source_sha": "e" * 40}, "does not match"), - ({"run_id": identity["run_id"] + 1}, "does not match"), - ({"producer_attempt": 2, "current_attempt": 1}, "newer than"), - ) - for changes, diagnostic in mismatches: - arguments = {**identity, "handoff": handoff, "current_attempt": 2, **changes} - with ( - self.subTest(changes=changes), - self.assertRaisesRegex(CurrentPlanPublicationError, diagnostic), - ): - validate_approved_writer_handoff(**arguments) - - def test_protected_writer_runtime_identity_remains_exact(self) -> None: - validate_runtime_identity( - CONTROL_REPOSITORY, - f"refs/heads/{AUTHORITY_REF}", - CURRENT_PLAN_WORKFLOW_REF, - ) - mismatches = ( - (None, f"refs/heads/{AUTHORITY_REF}", CURRENT_PLAN_WORKFLOW_REF), - ("outside/fork", f"refs/heads/{AUTHORITY_REF}", CURRENT_PLAN_WORKFLOW_REF), - (CONTROL_REPOSITORY, "refs/heads/feature", CURRENT_PLAN_WORKFLOW_REF), - ( - CONTROL_REPOSITORY, - f"refs/heads/{AUTHORITY_REF}", - f"{CONTROL_REPOSITORY}/.github/workflows/other.yml@refs/heads/{AUTHORITY_REF}", - ), - ) - for repository, ref, workflow_ref in mismatches: - with ( - self.subTest(repository=repository, ref=ref, workflow_ref=workflow_ref), - self.assertRaises(CurrentPlanPublicationError), - ): - validate_runtime_identity(repository, ref, workflow_ref) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_handoff_recovery.py b/tests/test_handoff_recovery.py deleted file mode 100644 index 171be5d..0000000 --- a/tests/test_handoff_recovery.py +++ /dev/null @@ -1,272 +0,0 @@ -from __future__ import annotations - -import json -import tempfile -import unittest -import urllib.parse -from pathlib import Path - -from scripts.beta_candidate import canonical_json -from scripts.handoff_recovery import ( - KINDS, - HandoffError, - artifact_name, - create_handoff, - list_run_artifacts, - select_handoff_artifact, - validate_handoff, -) - -REPOSITORY = "durable-workflow/.github" -RUN_ID = 123456 -SOURCE_SHA = "a" * 40 -WORKFLOW_REFS = { - "candidate": "durable-workflow/.github/.github/workflows/beta-candidate.yml@refs/heads/main", - "release-plan-observation": ( - "durable-workflow/.github/.github/workflows/release-plan-observer.yml@refs/heads/main" - ), -} - - -def artifact(kind: str, attempt: int, artifact_id: int, *, expired: bool = False) -> dict[str, object]: - return { - "id": artifact_id, - "name": artifact_name(kind, RUN_ID, attempt), - "expired": expired, - "workflow_run": {"id": RUN_ID, "head_sha": SOURCE_SHA}, - } - - -class HandoffSelectionTest(unittest.TestCase): - def test_recorder_only_rerun_recovers_the_latest_prior_producing_attempt(self) -> None: - for kind in KINDS: - with self.subTest(kind=kind): - selected = select_handoff_artifact( - kind, - [artifact(kind, 1, 101)], - run_id=RUN_ID, - current_attempt=2, - producer_attempt=1, - source_sha=SOURCE_SHA, - ) - self.assertEqual(101, selected.artifact_id) - self.assertEqual(1, selected.producer_attempt) - self.assertEqual(artifact_name(kind, RUN_ID, 1), selected.name) - - def test_full_rerun_prefers_the_new_same_attempt_handoff(self) -> None: - for kind in KINDS: - with self.subTest(kind=kind): - selected = select_handoff_artifact( - kind, - [artifact(kind, 1, 101), artifact(kind, 2, 202)], - run_id=RUN_ID, - current_attempt=2, - producer_attempt=2, - source_sha=SOURCE_SHA, - ) - self.assertEqual(202, selected.artifact_id) - self.assertEqual(2, selected.producer_attempt) - - def test_missing_expired_ambiguous_and_mismatched_handoffs_fail_closed(self) -> None: - kind = "candidate" - cases = { - "missing": ([], "no verifier handoff"), - "expired": ( - [artifact(kind, 1, 101), artifact(kind, 2, 202, expired=True)], - "expired or has unknown retention state", - ), - "ambiguous": ( - [artifact(kind, 1, 101), artifact(kind, 1, 102)], - "ambiguous", - ), - "future": ([artifact(kind, 3, 303)], "future producing attempt"), - } - for label, (artifacts, message) in cases.items(): - with self.subTest(case=label), self.assertRaisesRegex(HandoffError, message): - select_handoff_artifact( - kind, - artifacts, - run_id=RUN_ID, - current_attempt=2, - producer_attempt=2 if label == "expired" else 1, - source_sha=SOURCE_SHA, - ) - - wrong_run = artifact(kind, 1, 101) - wrong_run["workflow_run"] = {"id": RUN_ID + 1, "head_sha": SOURCE_SHA} - with self.assertRaisesRegex(HandoffError, "mismatched workflow-run identity"): - select_handoff_artifact( - kind, - [wrong_run], - run_id=RUN_ID, - current_attempt=2, - producer_attempt=1, - source_sha=SOURCE_SHA, - ) - - def test_retained_attempt_must_exist_and_name_the_newest_producer(self) -> None: - kind = "release-plan-observation" - artifacts = [artifact(kind, 1, 101), artifact(kind, 2, 202)] - with self.assertRaisesRegex(HandoffError, "does not identify the newest"): - select_handoff_artifact( - kind, - artifacts, - run_id=RUN_ID, - current_attempt=3, - producer_attempt=1, - source_sha=SOURCE_SHA, - ) - with self.assertRaisesRegex(HandoffError, "producing attempt 3"): - select_handoff_artifact( - kind, - artifacts, - run_id=RUN_ID, - current_attempt=3, - producer_attempt=3, - source_sha=SOURCE_SHA, - ) - - def test_artifact_listing_is_complete_and_bounded(self) -> None: - class Client: - def json(self, url: str) -> dict[str, object]: - page = int(urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)["page"][0]) - page_artifacts = [artifact("candidate", 1, index) for index in range(1, 101)] - if page == 2: - page_artifacts = [artifact("candidate", 2, 101)] - return {"total_count": 101, "artifacts": page_artifacts} - - artifacts = list_run_artifacts(Client(), REPOSITORY, RUN_ID) # type: ignore[arg-type] - self.assertEqual(101, len(artifacts)) - - -class HandoffManifestTest(unittest.TestCase): - def _create_fixture(self, root: Path, kind: str, attempt: int = 1) -> Path: - for filename in KINDS[kind].filenames: - (root / filename).write_text(f"{kind}:{filename}\n", encoding="utf-8") - manifest = root / "handoff.json" - create_handoff( - kind, - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS[kind], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - run_attempt=attempt, - ) - return manifest - - def test_candidate_and_observer_handoffs_bind_identity_attempt_and_file_bytes(self) -> None: - for kind in KINDS: - with self.subTest(kind=kind), tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - manifest = self._create_fixture(root, kind) - result = validate_handoff( - kind, - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS[kind], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - current_attempt=2, - producer_attempt=1, - ) - self.assertEqual(1, result["producer"]["run_attempt"]) - self.assertEqual(artifact_name(kind, RUN_ID, 1), result["artifact_name"]) - - first_file = root / KINDS[kind].filenames[0] - first_file.write_bytes(b"different evidence") - with self.assertRaisesRegex(HandoffError, "digest does not match"): - validate_handoff( - kind, - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS[kind], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - current_attempt=2, - producer_attempt=1, - ) - - def test_failed_observation_handoff_omits_only_verification_evidence(self) -> None: - kind = "release-plan-observation" - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - manifest = self._create_fixture(root, kind) - failed = validate_handoff( - kind, - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS[kind], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - current_attempt=1, - producer_attempt=1, - ) - self.assertNotIn("verification.json", failed["files"]) - - (root / "verification.json").write_text("verified\n", encoding="utf-8") - create_handoff( - kind, - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS[kind], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - run_attempt=1, - ) - verified = validate_handoff( - kind, - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS[kind], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - current_attempt=1, - producer_attempt=1, - ) - self.assertIn("verification.json", verified["files"]) - - def test_identity_mismatch_and_unexpected_files_fail_before_use(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - manifest = self._create_fixture(root, "candidate") - handoff = json.loads(manifest.read_bytes()) - handoff["producer"]["run_attempt"] = 2 - manifest.write_bytes(canonical_json(handoff)) - with self.assertRaisesRegex(HandoffError, "producer identity"): - validate_handoff( - "candidate", - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS["candidate"], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - current_attempt=2, - producer_attempt=1, - ) - - manifest = self._create_fixture(root, "candidate") - (root / "unrelated.json").write_text("{}\n", encoding="utf-8") - with self.assertRaisesRegex(HandoffError, "unexpected or missing files"): - validate_handoff( - "candidate", - root, - manifest, - repository=REPOSITORY, - workflow_ref=WORKFLOW_REFS["candidate"], - source_sha=SOURCE_SHA, - run_id=RUN_ID, - current_attempt=2, - producer_attempt=1, - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_issue_authority.py b/tests/test_issue_authority.py deleted file mode 100644 index 9d36a81..0000000 --- a/tests/test_issue_authority.py +++ /dev/null @@ -1,7043 +0,0 @@ -from __future__ import annotations - -import contextlib -import copy -import hashlib -import io -import json -import os -import re -import subprocess -import tempfile -import unittest -import urllib.error -from datetime import UTC, datetime, timedelta -from pathlib import Path -from typing import Any -from unittest.mock import patch - -import yaml - -from scripts.cross_repository_lifecycle import ( - EVIDENCE_MARKER, - QUALIFICATION_LEDGER_MARKER, - declared_targets, - evaluate_lifecycle, - qualification_targets, - render_evidence, -) -from scripts.issue_authority import ( - COMPLETION_REQUIRED_LABEL, - COMPLETION_VERIFIED_LABEL, - FROZEN_LIFECYCLE_EVIDENCE_MARKER, - INTAKE_SCHEMA, - ISSUE_INTAKE_QUERY, - OWNER_LABELS, - PUBLIC_LIFECYCLE_MARKER, - PUBLIC_RETIREMENT_RECORD_MARKER, - PULL_REQUEST_METADATA_QUERY, - STATUS_LABELS, - SUPERSEDED_STATUS_LABEL, - SUPERSESSION_EVIDENCE_MARKER, - UNBLOCK_CONTEXT_END, - UNBLOCK_CONTEXT_START, - AuthorityError, - GitHubApi, - GitHubDiscovery, - LifecycleAuditError, - _audit_state_labels, - _has_trusted_public_retirement, - _item_labels, - _render_body, - _replace_kind_label, - _write_discovery_outputs, - _write_evidence, - activate_prerelease_supersessions, - apply_backlog, - assess_issue_intake, - audit_backlog, - build_public_age_audit, - discover_public_metadata, - issue_revision_digest, - load_contract, - load_legacy_cross_repository_targets, - load_public_lifecycle_projection, - parse_args, - reconcile_public_lifecycle, - reconcile_verified_releases_before_intake, - reconstruct_intake, - validate_backlog_cross_repository_targets, - validate_contract, - verify_intake_manifest, -) - -ROOT = Path(__file__).resolve().parents[1] -REVIEWED_BLOCK_CONDITION = "A separately owned public release gate must complete." - - -def mark_release_item_blocked(backlog: dict[str, Any]) -> dict[str, Any]: - item = next(item for item in backlog["items"] if item["id"] == "release-plan-versioned-changelogs") - item["status"] = "blocked" - item["unblock_condition"] = REVIEWED_BLOCK_CONDITION - return item - - -class FakeResponse: - def __init__(self, payload: bytes) -> None: - self.payload = payload - - def __enter__(self) -> FakeResponse: - return self - - def __exit__(self, *args: object) -> None: - return None - - def read(self) -> bytes: - return self.payload - - -def contract_fixture( - *, - include_supersessions: bool = False, -) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]]: - directory = ROOT / "issue-authority" - documents = tuple( - json.loads((directory / name).read_text(encoding="utf-8")) - for name in ("policy.json", "backlog.json", "policy-schema.json", "backlog-schema.json") - ) - if include_supersessions: - documents[0]["prerelease_supersessions"] = documents[0]["prerelease_supersessions"][:1] - else: - documents[0]["prerelease_supersessions"] = [] - return documents - - -def immutable_retirement_contract() -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]]: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - checked_policy = json.loads((ROOT / "issue-authority/policy.json").read_text(encoding="utf-8")) - policy["prerelease_supersessions"] = copy.deepcopy(checked_policy["prerelease_supersessions"][1:]) - return policy, backlog, policy_schema, backlog_schema - - -def immutable_product_train_files(policy: dict[str, Any]) -> dict[tuple[str, str, str], bytes]: - successor = next( - record["successor"] for record in policy["prerelease_supersessions"] if "commit" in record["successor"] - ) - historical_product_train = subprocess.run( - ["git", "show", f"{successor['commit']}:{successor['path']}"], - cwd=ROOT, - check=True, - capture_output=True, - ).stdout - return {(successor["repository"], successor["commit"], successor["path"]): historical_product_train} - - -def current_rc_retirement_contract() -> tuple[ - dict[str, Any], - dict[str, Any], - dict[str, Any], - dict[str, Any], - bytes, -]: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - raw = (ROOT / "product-train/current.json").read_bytes() - product_train = json.loads(raw) - train = product_train["current"] - commit = "a" * 40 - policy["prerelease_supersessions"] = [ - { - "activation_commit": commit, - "reason": "A later immutable supported product train replaces this prerelease authority.", - "retired": {"number": 61, "repository": ".github"}, - "successor": { - "commit": commit, - "path": "product-train/current.json", - "release_plan": copy.deepcopy(product_train["trains"][train]["release_plan"]), - "repository": ".github", - "sha256": hashlib.sha256(raw).hexdigest(), - "train": train, - }, - } - ] - return policy, backlog, policy_schema, backlog_schema, raw - - -def product_train_files( - policy: dict[str, Any], - raw: bytes, -) -> dict[tuple[str, str, str], bytes]: - successor = policy["prerelease_supersessions"][0]["successor"] - return {(successor["repository"], successor["commit"], successor["path"]): raw} - - -def qualification_fixture() -> dict[str, Any]: - return json.loads((ROOT / "qualification" / "policy.json").read_text(encoding="utf-8")) - - -def pipeline_completion_fixture() -> dict[str, Any]: - return json.loads((ROOT / "tests/fixtures/github/pipeline-completion-comment.json").read_text(encoding="utf-8")) - - -def install_pipeline_completion_evidence( - client: FakeGitHubApi, - fixture: dict[str, Any], - targets: dict[str, dict[str, Any]], - *, - issue_number: int = 99, -) -> None: - for record in fixture["targets"]: - target = targets[record["repository"]] - key = (record["repository"], record["commit"]) - client.reachable.add((*key, record["branch"])) - client.successful_workflow_runs.add( - ( - record["repository"], - record["qualification_run"], - record["commit"], - record["workflow_path"], - record["workflow_name"], - ) - ) - client.successful_check_runs[key] = { - check: record["required_check_run"] for check in target["required_checks"] - } - client.trusted_comments[(".github", issue_number)] = [ - { - "body": fixture["body"], - "id": 67, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ] - - -def legacy_target_fixture() -> dict[str, Any]: - return json.loads((ROOT / "issue-authority" / "legacy-cross-repository-targets.json").read_text(encoding="utf-8")) - - -def historical_landing_fixture(*repositories: str) -> list[dict[str, Any]]: - landings = {landing["repository"]: landing for landing in legacy_target_fixture()["protected_branch_landings"]} - return [copy.deepcopy(landings[repository]) for repository in repositories] - - -class FakeGitHubApi: - def __init__(self, policy: dict[str, Any]) -> None: - self.labels: dict[str, dict[str, dict[str, str]]] = {repository: {} for repository in policy["repositories"]} - self.milestones: dict[str, dict[str, dict[str, Any]]] = { - repository: {} for repository in policy["repositories"] - } - self.issues: dict[str, list[dict[str, Any]]] = {repository: [] for repository in policy["repositories"]} - self.label_updates: list[tuple[str, str, str]] = [] - self.milestone_updates: list[tuple[str, str, str]] = [] - self.created_issues: list[tuple[str, int]] = [] - self.replacements: list[tuple[str, int, list[str]]] = [] - self.body_updates: list[tuple[str, int, str]] = [] - self.state_updates: list[tuple[str, int, str, str]] = [] - self.timelines: dict[tuple[str, int], list[dict[str, Any]]] = {} - self.timeline_sequences: dict[tuple[str, int], list[list[dict[str, Any]]]] = {} - self.timeline_reads: list[tuple[str, int]] = [] - self.pulls: dict[tuple[str, int], dict[str, Any]] = {} - self.reviews: dict[tuple[str, int], list[dict[str, Any]]] = {} - self.reachable: set[tuple[str, str, str]] = set() - self.contained_commits: set[tuple[str, str, str]] = set() - self.successful_checks: dict[tuple[str, str], set[str]] = {} - self.successful_check_runs: dict[tuple[str, str], dict[str, int]] = {} - self.successful_workflow_runs: set[tuple[str, int, str, str, str | None]] = set() - self.successful_workflow_jobs: dict[tuple[str, int], dict[str, int]] = {} - self.reachability_requests: list[tuple[str, str, str]] = [] - self.check_requests: list[tuple[str, str]] = [] - self.workflow_run_requests: list[tuple[str, int, str, str | None, str | None]] = [] - self.comments: dict[tuple[str, int], str] = {} - self.trusted_comments: dict[tuple[str, int], list[dict[str, Any]]] = {} - self.comment_updates: list[tuple[str, int, str]] = [] - self.commit_statuses: dict[tuple[str, str], list[dict[str, Any]]] = {} - self.status_updates: list[tuple[str, str, str]] = [] - self.mutation_events: list[tuple[str, str, int]] = [] - - def ensure_labels( - self, - _organization: str, - repository: str, - desired: list[dict[str, str]], - ) -> list[str]: - changes: list[str] = [] - for label in desired: - action = "created" if label["name"] not in self.labels[repository] else "updated" - if self.labels[repository].get(label["name"]) == label: - continue - self.labels[repository][label["name"]] = copy.deepcopy(label) - changes.append(f"{action}:{label['name']}") - self.label_updates.append((repository, action, label["name"])) - return changes - - def ensure_milestone( - self, - _organization: str, - repository: str, - desired: dict[str, Any], - ) -> tuple[int, str | None]: - current = self.milestones[repository].get(desired["title"]) - if current == desired: - return 1, None - self.milestones[repository][desired["title"]] = copy.deepcopy(desired) - action = "created" if current is None else "updated" - self.milestone_updates.append((repository, action, desired["title"])) - return 1, action - - def list_issues(self, _organization: str, repository: str) -> list[dict[str, Any]]: - return list(self.issues[repository]) - - def create_issue( - self, - organization: str, - repository: str, - *, - title: str, - body: str, - labels: list[str], - milestone: int, - ) -> dict[str, Any]: - number = len(self.issues[repository]) + 1 - issue = { - "body": body, - "html_url": f"https://github.com/{organization}/{repository}/issues/{number}", - "labels": [{"name": label} for label in labels], - "milestone": {"number": milestone, "title": "2.0 beta"}, - "number": number, - "state": "open", - "state_reason": None, - "title": title, - } - self.issues[repository].append(issue) - self.created_issues.append((repository, number)) - return issue - - def replace_issue_labels( - self, - _organization: str, - repository: str, - number: int, - labels: list[str], - ) -> None: - issue = next(issue for issue in self.issues[repository] if issue["number"] == number) - issue["labels"] = [{"name": label} for label in labels] - self.replacements.append((repository, number, labels)) - - def update_issue_body( - self, - _organization: str, - repository: str, - number: int, - body: str, - ) -> None: - issue = next(issue for issue in self.issues[repository] if issue["number"] == number) - issue["body"] = body - self.body_updates.append((repository, number, body)) - - def update_issue_state( - self, - _organization: str, - repository: str, - number: int, - state: str, - *, - state_reason: str, - ) -> None: - issue = next(issue for issue in self.issues[repository] if issue["number"] == number) - issue["state"] = state - issue["state_reason"] = state_reason - self.state_updates.append((repository, number, state, state_reason)) - self.mutation_events.append(("state", repository, number)) - - def list_issue_closing_references( - self, - _organization: str, - repository: str, - number: int, - ) -> list[dict[str, Any]]: - self.timeline_reads.append((repository, number)) - sequence = self.timeline_sequences.get((repository, number)) - if sequence: - value = sequence.pop(0) if len(sequence) > 1 else sequence[0] - return copy.deepcopy(value) - return copy.deepcopy(self.timelines.get((repository, number), [])) - - def get_pull_request(self, _organization: str, repository: str, number: int) -> dict[str, Any]: - return copy.deepcopy(self.pulls[(repository, number)]) - - def list_pull_request_reviews( - self, - _organization: str, - repository: str, - number: int, - ) -> list[dict[str, Any]]: - return copy.deepcopy(self.reviews.get((repository, number), [])) - - def commit_reaches_branch( - self, - _organization: str, - repository: str, - commit: str, - branch: str, - ) -> bool: - self.reachability_requests.append((repository, commit, branch)) - return (repository, commit, branch) in self.reachable - - def commit_contains( - self, - _organization: str, - repository: str, - descendant: str, - ancestor: str, - ) -> bool: - return (repository, descendant, ancestor) in self.contained_commits - - def successful_check_names( - self, - _organization: str, - repository: str, - commit: str, - ) -> set[str]: - self.check_requests.append((repository, commit)) - return set(self.successful_checks.get((repository, commit), set())) - - def successful_check_run_ids( - self, - _organization: str, - repository: str, - commit: str, - ) -> dict[str, int]: - self.check_requests.append((repository, commit)) - return dict(self.successful_check_runs.get((repository, commit), {})) - - def successful_workflow_run( - self, - _organization: str, - repository: str, - run_id: int, - commit: str, - workflow_path: str | None, - workflow_name: str | None, - ) -> bool: - identity = (repository, run_id, commit, workflow_path, workflow_name) - self.workflow_run_requests.append(identity) - return any( - actual_repository == repository - and actual_run_id == run_id - and actual_commit == commit - and (workflow_path is None or actual_workflow_path == workflow_path) - and (workflow_name is None or actual_workflow_name == workflow_name) - for ( - actual_repository, - actual_run_id, - actual_commit, - actual_workflow_path, - actual_workflow_name, - ) in self.successful_workflow_runs - ) - - def successful_historical_workflow_run( - self, - organization: str, - repository: str, - run_id: int, - commit: str, - _branch: str, - workflow_path: str, - workflow_name: str, - ) -> bool: - return self.successful_workflow_run( - organization, - repository, - run_id, - commit, - workflow_path, - workflow_name, - ) - - def successful_historical_workflow_jobs( - self, - _organization: str, - repository: str, - run_id: int, - ) -> dict[str, int]: - return dict(self.successful_workflow_jobs.get((repository, run_id), {})) - - def list_trusted_issue_comments( - self, - _organization: str, - repository: str, - number: int, - ) -> list[dict[str, Any]]: - key = (repository, number) - records = copy.deepcopy(self.trusted_comments.get(key, [])) - if key in self.comments: - records.append( - { - "body": self.comments[key], - "id": 1, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ) - return records - - def upsert_lifecycle_comment( - self, - _organization: str, - repository: str, - number: int, - marker: str, - body: str, - ) -> bool: - self.assert_lifecycle_marker(marker, body) - key = (repository, number) - if self.comments.get(key) == body: - return False - self.comments[key] = body - self.comment_updates.append((repository, number, body)) - self.mutation_events.append(("comment", repository, number)) - return True - - def has_managed_lifecycle_comment( - self, - _organization: str, - repository: str, - number: int, - marker: str | None = None, - ) -> bool: - bodies = [record["body"] for record in self.list_trusted_issue_comments("", repository, number)] - if marker is not None: - return any(marker in body for body in bodies) - return any( - known_marker in body - for body in bodies - for known_marker in ( - EVIDENCE_MARKER, - FROZEN_LIFECYCLE_EVIDENCE_MARKER, - PUBLIC_LIFECYCLE_MARKER, - SUPERSESSION_EVIDENCE_MARKER, - ) - ) - - def managed_lifecycle_comment_body( - self, - _organization: str, - repository: str, - number: int, - marker: str, - ) -> str | None: - bodies = [ - record["body"] - for record in self.list_trusted_issue_comments("", repository, number) - if marker in record["body"] - ] - if len(bodies) > 1: - raise AuthorityError("duplicate cross-repository lifecycle evidence") - return bodies[0] if bodies else None - - def list_commit_statuses( - self, - _organization: str, - repository: str, - commit: str, - ) -> list[dict[str, Any]]: - return copy.deepcopy(self.commit_statuses.get((repository, commit), [])) - - def ensure_supersession_activation( - self, - _organization: str, - repository: str, - activation: dict[str, str], - ) -> bool: - key = (repository, activation["commit"]) - expected = activation_status(activation) - if expected in self.commit_statuses.get(key, []): - return False - self.commit_statuses.setdefault(key, []).append(expected) - self.status_updates.append((repository, activation["commit"], activation["digest"])) - return True - - @staticmethod - def assert_lifecycle_marker(marker: str, body: str) -> None: - if marker not in body: - raise AssertionError("lifecycle comment omitted its stable marker") - - -def label_names(issue: dict[str, Any]) -> set[str]: - return {label["name"] for label in issue["labels"]} - - -def activation_status(activation: dict[str, str]) -> dict[str, Any]: - return { - "context": activation["context"], - "creator": { - "id": 41_898_282, - "login": "github-actions[bot]", - "type": "Bot", - }, - "description": f"sha256:{activation['digest']}", - "state": "success", - } - - -def find_work_item(client: FakeGitHubApi, work_id: str) -> tuple[str, dict[str, Any]]: - marker = f"" - for repository, issues in client.issues.items(): - for issue in issues: - if marker in issue["body"]: - return repository, issue - raise AssertionError(f"missing work item {work_id}") - - -def replace_unblock_context(issue: dict[str, Any], replacement: str) -> None: - start = issue["body"].index(UNBLOCK_CONTEXT_START) - end = issue["body"].index(UNBLOCK_CONTEXT_END) + len(UNBLOCK_CONTEXT_END) - issue["body"] = issue["body"][:start] + replacement + issue["body"][end:] - - -def intake_issue( - *, - author: str = "external-contributor", - body: str = "Current body", - edited_at: str | None = None, - labels: list[str] | None = None, - number: int = 1, -) -> dict[str, Any]: - return { - "author": {"login": author}, - "body": body, - "created_at": "2026-07-21T10:00:00Z", - "html_url": f"https://github.com/durable-workflow/.github/issues/{number}", - "labels": [{"name": label} for label in labels or []], - "last_edited_at": edited_at, - "milestone": None, - "number": number, - "state": "open", - "state_reason": None, - "title": "Current title", - } - - -def prerelease_authority_issue( - number: int, - *, - author: str = "rmcdaniel", - body: str | None = None, - state: str = "open", - status: str = "status:ready", -) -> dict[str, Any]: - issue = intake_issue( - author=author, - body=body or f"Prerelease authority {number}", - labels=[ - "authority:github", - "beta:blocker", - COMPLETION_REQUIRED_LABEL, - "kind:release-blocker", - "priority:P0", - status, - ], - number=number, - ) - issue["milestone"] = {"title": "2.0 beta"} - issue["state"] = state - return issue - - -def label_event( - actor: str, - created_at: str, - *, - event: str = "labeled", - label: str = "intake:approved", -) -> dict[str, Any]: - return { - "actor": actor, - "created_at": created_at, - "event": event, - "label": label, - } - - -def closing_reference( - repository: str, - number: int, - *, - actor: str = "durable-workflow-ops", - created_at: str = "2026-07-24T09:00:00Z", - identifier: str | None = None, - will_close_target: bool = True, -) -> dict[str, Any]: - return { - "__typename": "CrossReferencedEvent", - "actor": {"login": actor}, - "id": identifier if identifier is not None else f"CRE_kwDOA1b2c84A{number:04d}", - "referencedAt": created_at, - "source": { - "__typename": "PullRequest", - "number": number, - "repository": {"nameWithOwner": f"durable-workflow/{repository}"}, - "url": f"https://github.com/durable-workflow/{repository}/pull/{number}", - }, - "willCloseTarget": will_close_target, - } - - -def pull_request( - repository: str, - number: int, - branch: str, - *, - author: str = "durable-workflow-ops", - author_association: str = "MEMBER", - created_at: str, - commit: str | None = None, - head_repository: str | None = None, - head_sha: str | None = None, - state: str = "open", -) -> dict[str, Any]: - resolved_head_repository = head_repository or f"durable-workflow/{repository}" - resolved_head_sha = head_sha or f"{number:040x}" - return { - "author_association": author_association, - "base": { - "ref": branch, - "repo": {"full_name": f"durable-workflow/{repository}"}, - "sha": f"{number + 1000:040x}", - }, - "created_at": created_at, - "head": { - "ref": f"seed/implementation-{number}", - "repo": {"full_name": resolved_head_repository}, - "sha": resolved_head_sha, - }, - "html_url": f"https://github.com/durable-workflow/{repository}/pull/{number}", - "merge_commit_sha": commit, - "merged_at": "2026-07-24T12:00:00Z" if commit else None, - "number": number, - "state": "closed" if commit else state, - "url": f"https://api.github.com/repos/durable-workflow/{repository}/pulls/{number}", - "user": {"login": author}, - } - - -def approving_review( - head_sha: str, - *, - association: str = "MEMBER", - identifier: int = 1, - reviewer: str = "repository-maintainer", - state: str = "APPROVED", - submitted_at: str = "2026-07-24T09:30:00Z", -) -> dict[str, Any]: - return { - "author_association": association, - "commit_id": head_sha, - "id": identifier, - "state": state, - "submitted_at": submitted_at, - "user": {"id": identifier + 100, "login": reviewer}, - } - - -def closing_reference_race_fixture() -> tuple[Any, ...]: - policy, backlog, _policy_schema, _backlog_schema = contract_fixture() - for item in backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - client = FakeGitHubApi(policy) - apply_backlog(policy, backlog, client) - parent = { - "body": "Vetted cross-repository work.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:ready"}, - ], - "milestone": None, - "number": 99, - "state": "open", - "title": "Coordinate source landings", - } - client.issues[".github"].append(parent) - targets = qualification_targets(qualification_fixture()) - selected_targets = [targets[".github"], targets["workflow"]] - source_commit = "a" * 40 - workflow_commit = "b" * 40 - external_head = "c" * 40 - source_reference = closing_reference(".github", 51, created_at="2026-07-24T08:00:00Z") - external_reference = closing_reference( - "workflow", - 70, - actor="external-contributor", - created_at="2026-07-24T09:00:00Z", - ) - client.pulls[(".github", 51)] = pull_request( - ".github", - 51, - "main", - commit=source_commit, - created_at="2026-07-24T07:30:00Z", - ) - client.pulls[("workflow", 70)] = pull_request( - "workflow", - 70, - "v2", - author="external-contributor", - author_association="NONE", - commit=workflow_commit, - created_at="2026-07-24T08:30:00Z", - head_repository="external-contributor/workflow", - head_sha=external_head, - ) - client.reviews[("workflow", 70)] = [ - approving_review( - external_head, - submitted_at="2026-07-24T09:30:00Z", - ) - ] - for target, commit in ((targets[".github"], source_commit), (targets["workflow"], workflow_commit)): - repository = target["repository"] - client.reachable.add((repository, commit, target["branch"])) - client.successful_checks[(repository, commit)] = set(target["required_checks"]) - return ( - policy, - backlog, - client, - parent, - {(".github", 99): selected_targets}, - source_reference, - external_reference, - ) - - -class FakeDiscovery: - def __init__( - self, - policy: dict[str, Any], - issues: dict[str, list[tuple[dict[str, Any], list[dict[str, Any]]]]], - *, - commit_statuses: dict[tuple[str, str], list[dict[str, Any]]] | None = None, - public_files: dict[tuple[str, str, str], bytes] | None = None, - pull_requests: dict[str, list[dict[str, Any]]] | None = None, - ) -> None: - self.policy = policy - self.issues = issues - self.commit_statuses = copy.deepcopy(commit_statuses or {}) - self.public_files = copy.deepcopy(public_files or {}) - self.pull_requests = copy.deepcopy(pull_requests or {}) - self.list_requests: list[str] = [] - self.get_requests: list[tuple[str, int]] = [] - self.file_requests: list[tuple[str, str, str]] = [] - self.pull_requests_listed: list[str] = [] - - def list_issues( - self, - _organization: str, - repository: str, - ) -> list[tuple[dict[str, Any], list[dict[str, Any]]]]: - self.list_requests.append(repository) - return copy.deepcopy(self.issues.get(repository, [])) - - def get_issue( - self, - _organization: str, - repository: str, - number: int, - ) -> tuple[dict[str, Any], list[dict[str, Any]]]: - self.get_requests.append((repository, number)) - issue, timeline = next(record for record in self.issues.get(repository, []) if record[0]["number"] == number) - return copy.deepcopy(issue), copy.deepcopy(timeline) - - def list_pull_requests(self, _organization: str, repository: str) -> list[dict[str, Any]]: - self.pull_requests_listed.append(repository) - return copy.deepcopy(self.pull_requests.get(repository, [])) - - def list_commit_statuses( - self, - _organization: str, - repository: str, - commit: str, - ) -> list[dict[str, Any]]: - return copy.deepcopy(self.commit_statuses.get((repository, commit), [])) - - def read_file( - self, - _organization: str, - repository: str, - commit: str, - path: str, - ) -> bytes: - key = (repository, commit, path) - self.file_requests.append(key) - return self.public_files[key] - - -class ContractValidationTest(unittest.TestCase): - def test_checked_in_contract_is_valid(self) -> None: - policy, backlog = load_contract( - ROOT / "issue-authority" / "policy.json", - ROOT / "issue-authority" / "backlog.json", - ) - - self.assertEqual("github-to-mirrors", policy["state_direction"]) - self.assertEqual(["rmcdaniel", "durable-workflow-ops"], policy["intake"]["trusted_actors"]) - self.assertEqual("intake:approved", policy["intake"]["approval_label"]) - self.assertEqual( - [ - { - "number": 59, - "repository": ".github", - }, - { - "number": 61, - "repository": ".github", - }, - { - "number": 65, - "repository": ".github", - }, - ], - [record["retired"] for record in policy["prerelease_supersessions"]], - ) - self.assertEqual(61, policy["prerelease_supersessions"][0]["successor"]["number"]) - immutable_successors = [record["successor"] for record in policy["prerelease_supersessions"][1:]] - self.assertEqual( - ["2.0.0-beta.17", "2.0.0-beta.17"], - [successor["train"] for successor in immutable_successors], - ) - self.assertTrue( - all( - successor["release_plan"]["tag"] == "release-plan/coherent-2-0-beta-17" - for successor in immutable_successors - ) - ) - self.assertEqual( - [ - "93fab46b8eb028d0302b0438591a8c67bd4b0d9f", - "bf822f3ba0ca4f05a00649112fad9848a2557140", - "bf822f3ba0ca4f05a00649112fad9848a2557140", - ], - [record["activation_commit"] for record in policy["prerelease_supersessions"]], - ) - self.assertIn( - SUPERSEDED_STATUS_LABEL, - {label["name"] for label in policy["labels"]}, - ) - self.assertEqual(4, len(backlog["items"])) - blocked = [item for item in backlog["items"] if item["status"] == "blocked"] - self.assertTrue(all(item["depends_on"] or item.get("unblock_condition") for item in blocked)) - validate_backlog_cross_repository_targets( - backlog, - qualification_fixture(), - organization=policy["organization"], - ) - - def test_policy_schema_accepts_an_rc_product_train_successor(self) -> None: - policy, backlog, policy_schema, backlog_schema, _raw = current_rc_retirement_contract() - - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_checked_in_legacy_target_migration_is_qualified(self) -> None: - migration = load_legacy_cross_repository_targets( - ROOT / "issue-authority" / "legacy-cross-repository-targets.json", - qualification_fixture(), - ) - - self.assertEqual("durable-workflow.legacy-cross-repository-targets/v5", migration["schema"]) - self.assertTrue(migration["authorities"]) - self.assertEqual( - [ - { - "number": 32, - "repository": "sdk-python", - "targets": [ - "durable-workflow/durable-workflow.github.io@main", - "durable-workflow/sdk-python@main", - ], - } - ], - migration["immutable_issue_targets"], - ) - self.assertEqual( - {2, 26, 32, 33, 35, 36, 37, 38, 39, 41, 46, 48}, - {completion["number"] for completion in migration["historical_completions"]}, - ) - self.assertLessEqual( - {landing["repository"] for landing in migration["protected_branch_landings"]}, - set(qualification_targets(qualification_fixture())), - ) - frozen = { - (record["repository"], record["number"]): record - for record in migration["frozen_lifecycle_migrations"] - } - self.assertEqual( - { - ("durable-workflow.github.io", 63), - ("waterline", 79), - }, - set(frozen), - ) - documentation = frozen[("durable-workflow.github.io", 63)] - self.assertEqual("complete", documentation["outcome"]) - self.assertEqual( - [30595766880, 30589737606], - [landing["qualification"]["run"] for landing in documentation["landings"]], - ) - self.assertEqual( - [91047691168, 91030116510], - [landing["qualification"]["checks"][0]["job"] for landing in documentation["landings"]], - ) - self.assertEqual( - "4da46c504298af75643744bea5e793671f4318007dac5ba9608a68dc2d160909", - documentation["approved_issue_revision_sha256"], - ) - self.assertEqual( - "5ffcae7ed560f99c217788fb62df36cfc0df75f149ae349db661862e45ccc55b", - documentation["authority_snapshot_sha256"], - ) - waterline = frozen[("waterline", 79)] - self.assertEqual("missing-evidence", waterline["outcome"]) - self.assertEqual([], waterline["landings"]) - self.assertIn("responsive opened-dialog qualification", waterline["missing_evidence"]) - self.assertEqual( - "c8f2b27b33e562350b31cd781f95270abd83d37cbd5b8f926d24d8ac60357d77", - waterline["approved_issue_revision_sha256"], - ) - self.assertEqual( - "591adaf96dfbd9f9b0f8b31e3a213f73660dee14de0507d24b7779529794928d", - waterline["authority_snapshot_sha256"], - ) - migrated_beta = { - authority["id"]: authority["targets"] - for authority in migration["authorities"] - if authority["marker"] == "beta-work-id" - } - _policy, backlog, _policy_schema, _backlog_schema = contract_fixture() - current_beta = { - item["id"]: item["required_source_targets"] - for item in backlog["items"] - if item["kind"] == "cross-repository" - } - self.assertEqual(current_beta, migrated_beta) - - def test_cross_repository_backlog_items_must_declare_qualified_targets(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - backlog["items"][0].pop("required_source_targets") - - with self.assertRaisesRegex(AuthorityError, "required_source_targets"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_public_repository_inventory_is_exact(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - expected_repositories = [ - ".github", - "workflow", - "waterline", - "server", - "cli", - "ai", - "sample-app", - "sdk-php", - "sdk-python", - "sdk-rust", - "durable-workflow.github.io", - ] - self.assertEqual(expected_repositories, policy["repositories"]) - self.assertEqual(expected_repositories, list(OWNER_LABELS)) - self.assertEqual(expected_repositories, policy["milestones"][0]["repositories"]) - - def test_prerelease_supersession_requires_a_distinct_successor(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture(include_supersessions=True) - policy["prerelease_supersessions"][0]["successor"] = copy.deepcopy( - policy["prerelease_supersessions"][0]["retired"] - ) - - with self.assertRaisesRegex(AuthorityError, "distinct successor"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_prerelease_supersession_issue_chain_cannot_cycle(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture(include_supersessions=True) - policy["prerelease_supersessions"].append( - { - "activation_commit": "a" * 40, - "reason": "A later immutable artifact mismatch requires another train.", - "retired": {"repository": ".github", "number": 61}, - "successor": {"repository": ".github", "number": 59}, - } - ) - - with self.assertRaisesRegex(AuthorityError, "contains a cycle"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_review_and_migration_sets_must_match_exactly(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - backlog["review"][3]["disposition"] = "evidence-only" - - with self.assertRaisesRegex(AuthorityError, "reviewed migration set differs"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_dependencies_must_be_topologically_ordered(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - backlog["items"][0]["depends_on"] = ["authorize-2-0-beta"] - - with self.assertRaisesRegex(AuthorityError, "dependencies must precede"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_blocked_item_must_name_dependency_or_unblock_condition(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - mark_release_item_blocked(backlog) - backlog["items"][0].pop("unblock_condition") - - with self.assertRaisesRegex(AuthorityError, "must name a dependency or explicit unblock condition"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_unblock_condition_must_be_public_safe(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - mark_release_item_blocked(backlog) - backlog["items"][0]["unblock_condition"] = "Wait for /var/private-state." - - with self.assertRaisesRegex(AuthorityError, "non-public context"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_reviewed_source_fields_reject_reserved_unblock_context_markers(self) -> None: - for marker in (UNBLOCK_CONTEXT_START, UNBLOCK_CONTEXT_END): - with self.subTest(marker=marker): - policy, backlog, policy_schema, backlog_schema = contract_fixture() - backlog["review"][0]["reason"] += f" {marker}" - - with self.assertRaisesRegex(AuthorityError, "reserved unblock condition marker"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_private_operational_context_is_rejected(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - backlog["items"][0]["body"] += "\nDetails are in /var/private-state." - - with self.assertRaisesRegex(AuthorityError, "non-public context"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_private_cloud_is_not_a_public_authority_target(self) -> None: - policy, backlog, policy_schema, backlog_schema = contract_fixture() - policy["repositories"].append("cloud") - - with self.assertRaisesRegex(AuthorityError, "repository inventory"): - validate_contract(policy, backlog, policy_schema, backlog_schema) - - def test_issue_forms_use_only_policy_labels(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - policy_labels = {label["name"] for label in policy["labels"]} - form_directory = ROOT / ".github" / "ISSUE_TEMPLATE" - expected = { - "cross_repository.yml", - "feature_request.yml", - "product_defect.yml", - "release_blocker.yml", - } - forms = {path.name: yaml.safe_load(path.read_text(encoding="utf-8")) for path in form_directory.glob("*.yml")} - - self.assertTrue(expected <= set(forms)) - self.assertFalse(forms["config.yml"]["blank_issues_enabled"]) - for name, form in forms.items(): - if name != "config.yml": - self.assertTrue(set(form["labels"]) <= policy_labels) - self.assertIn("authority:github", form["labels"]) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, form["labels"]) - self.assertIn("priority:untriaged", form["labels"]) - - def test_cross_repository_form_exposes_every_qualified_target(self) -> None: - form = yaml.safe_load( - (ROOT / ".github" / "ISSUE_TEMPLATE" / "cross_repository.yml").read_text(encoding="utf-8") - ) - qualification = qualification_fixture() - affected = next(field for field in form["body"] if field.get("id") == "affected") - expected = { - f"durable-workflow/{target['repository']}@{target['branch']}" - for target in qualification_targets(qualification).values() - } - - self.assertEqual("dropdown", affected["type"]) - self.assertTrue(affected["attributes"]["multiple"]) - self.assertEqual(expected, set(affected["attributes"]["options"])) - - def test_authority_jobs_are_limited_to_the_canonical_github_host(self) -> None: - workflow = yaml.safe_load((ROOT / ".github" / "workflows" / "issue-authority.yml").read_text(encoding="utf-8")) - conditions = { - job: " ".join(workflow["jobs"][job]["if"].split()) - for job in ("validate", "release_completion", "intake", "activate", "apply", "audit") - } - - self.assertEqual( - "${{ github.event_name != 'issues' || github.server_url == 'https://github.com' }}", - conditions["validate"], - ) - self.assertEqual( - "${{ github.ref == 'refs/heads/main' && github.server_url == 'https://github.com' && " - "github.event_name == 'repository_dispatch' }}", - conditions["release_completion"], - ) - self.assertEqual( - "${{ 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')) }}", - conditions["intake"], - ) - self.assertEqual( - "${{ github.ref == 'refs/heads/main' && github.server_url == 'https://github.com' && " - "github.event_name == 'workflow_dispatch' && inputs.mode == 'activate' }}", - conditions["activate"], - ) - self.assertEqual( - "${{ 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')) }}", - conditions["apply"], - ) - self.assertEqual( - "${{ 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')) }}", - conditions["audit"], - ) - self.assertEqual( - ["public-lifecycle"], - workflow[True]["repository_dispatch"]["types"], - ) - projection_step = next( - step - for step in workflow["jobs"]["audit"]["steps"] - if step.get("name") == "Reconcile lifecycle and audit public issue and pull-request age" - ) - self.assertEqual("${{ github.actor }}", projection_step["env"]["PROJECTION_ACTOR"]) - self.assertEqual( - "${{ toJSON(github.event.client_payload.projection) }}", - projection_step["env"]["PROJECTION_JSON"], - ) - self.assertIn("--lifecycle-projection public-lifecycle-projection.json", projection_step["run"]) - self.assertIn('--projection-actor "$PROJECTION_ACTOR"', projection_step["run"]) - release_job = workflow["jobs"]["release_completion"] - self.assertEqual("beta-product-work", release_job["environment"]) - self.assertEqual({"contents": "read", "issues": "read"}, release_job["permissions"]) - release_step = next( - step - for step in release_job["steps"] - if step.get("name") == "Close source-bound releases before intake can reserve them" - ) - self.assertEqual("${{ github.actor }}", release_step["env"]["PROJECTION_ACTOR"]) - self.assertIn("complete-before-intake", release_step["run"]) - - def test_public_runs_share_workflow_level_concurrency_before_jobs_start(self) -> None: - workflow = yaml.safe_load((ROOT / ".github" / "workflows" / "issue-authority.yml").read_text(encoding="utf-8")) - concurrency = workflow["concurrency"] - - self.assertEqual( - "${{ github.server_url == 'https://github.com' && " - "'github-issue-authority-public' || " - "format('github-issue-authority-local-{0}-{1}', " - "github.repository, github.event.issue.number) }}", - " ".join(concurrency["group"].split()), - ) - for job in workflow["jobs"].values(): - self.assertNotIn("concurrency", job) - - def test_protected_push_cancels_superseded_public_reconciliation(self) -> None: - workflow = yaml.safe_load((ROOT / ".github" / "workflows" / "issue-authority.yml").read_text(encoding="utf-8")) - concurrency = workflow["concurrency"] - - self.assertEqual( - "${{ (github.server_url == 'https://github.com' && github.event_name == 'push') || " - "(github.server_url != 'https://github.com' && github.event_name == 'issues') }}", - " ".join(concurrency["cancel-in-progress"].split()), - ) - - def test_unapproved_events_cannot_reach_a_privileged_environment(self) -> None: - workflow = yaml.safe_load((ROOT / ".github" / "workflows" / "issue-authority.yml").read_text(encoding="utf-8")) - intake = workflow["jobs"]["intake"] - - self.assertNotIn("environment", intake) - self.assertEqual( - {"contents": "read", "issues": "read", "statuses": "read"}, - intake["permissions"], - ) - self.assertNotIn("write", intake["permissions"].values()) - self.assertNotIn("BETA_PRODUCT_WORK_TOKEN", json.dumps(intake)) - discover = next(step for step in intake["steps"] if step.get("id") == "discover") - self.assertEqual("${{ github.token }}", discover["env"]["GITHUB_TOKEN"]) - for name in ("apply", "audit"): - job = workflow["jobs"][name] - self.assertEqual("beta-product-work", job["environment"]) - self.assertEqual( - { - "actions": "read", - "checks": "read", - "contents": "read", - "issues": "read", - "pull-requests": "read", - "statuses": "write", - }, - job["permissions"], - ) - self.assertIn("intake", job["needs"]) - self.assertNotIn("PUBLIC_ISSUE_DISCOVERY_TOKEN", json.dumps(job)) - self.assertIn("GITHUB_TOKEN", json.dumps(job)) - self.assertIn("BETA_PRODUCT_WORK_TOKEN", json.dumps(job)) - - activation = workflow["jobs"]["activate"] - self.assertEqual("beta-product-work", activation["environment"]) - self.assertEqual( - {"contents": "read", "issues": "read", "statuses": "write"}, - activation["permissions"], - ) - self.assertEqual("validate", activation["needs"]) - self.assertNotIn("intake", activation["needs"]) - self.assertNotIn("BETA_PRODUCT_WORK_TOKEN", json.dumps(activation)) - self.assertEqual("${{ github.token }}", activation["steps"][-2]["env"]["GITHUB_TOKEN"]) - - def test_activation_dispatch_has_no_caller_supplied_authority_payload(self) -> None: - workflow = yaml.safe_load((ROOT / ".github" / "workflows" / "issue-authority.yml").read_text(encoding="utf-8")) - dispatch_inputs = workflow[True]["workflow_dispatch"]["inputs"] - - self.assertEqual({"mode"}, set(dispatch_inputs)) - self.assertEqual(["audit", "apply", "activate"], dispatch_inputs["mode"]["options"]) - source = json.dumps(workflow["jobs"]["activate"]) - self.assertNotIn("inputs.commit", source) - self.assertNotIn("inputs.context", source) - self.assertNotIn("inputs.digest", source) - for option, value in ( - ("--commit", "a" * 40), - ("--context", "issue-authority/prerelease-supersession/.github/61"), - ("--digest", "b" * 64), - ): - with ( - self.subTest(option=option), - contextlib.redirect_stderr(io.StringIO()), - self.assertRaises(SystemExit), - ): - parse_args( - [ - "activate", - "issue-authority/policy.json", - "issue-authority/backlog.json", - option, - value, - ] - ) - - def test_oversized_intake_manifest_is_file_backed_and_outputs_stay_bounded(self) -> None: - argument_limit = os.sysconf("SC_ARG_MAX") - manifest = { - "schema": INTAKE_SCHEMA, - "issues": [{"body": "x" * (argument_limit + 1)}], - "trigger": {"approved": True}, - } - - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - manifest_path = root / "issue-intake.json" - github_output = root / "github-output" - - _write_evidence(manifest_path, manifest) - _write_discovery_outputs(github_output, manifest) - - self.assertGreater(manifest_path.stat().st_size, argument_limit) - self.assertEqual( - ["intake_ready=true", "trigger_approved=true"], - github_output.read_text(encoding="utf-8").splitlines(), - ) - - workflow_path = ROOT / ".github" / "workflows" / "issue-authority.yml" - workflow_source = workflow_path.read_text(encoding="utf-8") - workflow = yaml.safe_load(workflow_source) - intake = workflow["jobs"]["intake"] - - self.assertEqual( - { - "intake_ready", - "manifest_artifact_digest", - "manifest_artifact_id", - "source_run_attempt", - "source_run_id", - "trigger_approved", - }, - set(intake["outputs"]), - ) - self.assertNotIn("INTAKE_MANIFEST", workflow_source) - self.assertNotIn("${{ needs.intake.outputs.manifest }}", workflow_source) - self.assertNotIn("base64 --decode", workflow_source) - - for job_name in ("apply", "audit"): - with self.subTest(job=job_name): - job = workflow["jobs"][job_name] - download = job["steps"][1] - validator = job["steps"][2] - self.assertEqual( - "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c", - download["uses"], - ) - self.assertEqual( - { - "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 }}", - }, - download["with"], - ) - self.assertEqual( - "${{ needs.intake.outputs.manifest_artifact_digest }}", - validator["env"]["EXPECTED_ARTIFACT_DIGEST"], - ) - self.assertEqual( - "${{ needs.intake.outputs.source_run_attempt }}", - validator["env"]["EXPECTED_SOURCE_RUN_ATTEMPT"], - ) - evidence_upload = job["steps"][-1] - self.assertEqual("always()", evidence_upload["if"]) - self.assertEqual("ignore", evidence_upload["with"]["if-no-files-found"]) - - def test_comments_and_pull_request_content_are_not_event_inputs(self) -> None: - source = (ROOT / ".github" / "workflows" / "issue-authority.yml").read_text(encoding="utf-8") - - self.assertNotIn("issue_comment:", source) - self.assertNotIn("pull_request:", source) - self.assertNotIn("pull_request_target:", source) - self.assertIn("types: [opened, edited, closed, reopened, labeled, unlabeled, milestoned, demilestoned]", source) - - def test_every_external_action_is_immutably_pinned(self) -> None: - source = (ROOT / ".github" / "workflows" / "issue-authority.yml").read_text(encoding="utf-8") - invocations = re.findall(r"^\s+uses:\s+(actions/[^\s@]+)@([^\s]+)(?:\s+#\s+(.+))?$", source, re.MULTILINE) - - self.assertTrue(invocations) - for action, revision, version in invocations: - with self.subTest(action=action): - self.assertRegex(revision, r"^[0-9a-f]{40}$") - self.assertRegex(version or "", r"^v[0-9]+$") - - -class IssueIntakeTest(unittest.TestCase): - trusted = ["rmcdaniel", "durable-workflow-ops"] - - def assess(self, issue: dict[str, Any], timeline: list[dict[str, Any]]) -> dict[str, Any]: - return assess_issue_intake( - issue, - timeline, - approval_label="intake:approved", - trusted_actors=self.trusted, - ) - - def test_trusted_creation_is_vetted_without_a_label(self) -> None: - issue = intake_issue(author="rmcdaniel") - - assessment = self.assess(issue, []) - - self.assertTrue(assessment["approved"]) - self.assertEqual("trusted-creation", assessment["approval_mode"]) - self.assertEqual(issue_revision_digest(issue["title"], issue["body"]), assessment["revision"]) - - def test_external_creation_is_inert(self) -> None: - assessment = self.assess(intake_issue(), []) - - self.assertFalse(assessment["approved"]) - self.assertEqual("approval-label-absent", assessment["reason"]) - - def test_unapproved_body_is_never_fetched_for_revision_binding(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - discovery = FakeDiscovery(policy, {".github": [(intake_issue(), [])]}) - - manifest, inventory = reconstruct_intake(policy, discovery) - - self.assertEqual([], manifest["issues"]) - self.assertEqual([], inventory[".github"]) - self.assertEqual([], discovery.get_requests) - - def test_only_a_trusted_latest_label_actor_can_approve(self) -> None: - issue = intake_issue(labels=["intake:approved"]) - trusted = self.assess(issue, [label_event("durable-workflow-ops", "2026-07-21T10:01:00Z")]) - untrusted = self.assess(issue, [label_event("external-contributor", "2026-07-21T10:01:00Z")]) - - self.assertTrue(trusted["approved"]) - self.assertEqual("durable-workflow-ops", trusted["approval_actor"]) - self.assertFalse(untrusted["approved"]) - self.assertEqual("approval-actor-untrusted", untrusted["reason"]) - - def test_post_approval_edit_invalidates_the_revision(self) -> None: - issue = intake_issue( - body="Edited body", - edited_at="2026-07-21T10:02:00Z", - labels=["intake:approved"], - ) - - assessment = self.assess(issue, [label_event("rmcdaniel", "2026-07-21T10:01:00Z")]) - - self.assertFalse(assessment["approved"]) - self.assertEqual("approval-predates-revision", assessment["reason"]) - - def test_title_rename_after_approval_invalidates_the_revision(self) -> None: - issue = intake_issue(labels=["intake:approved"]) - timeline = [ - label_event("rmcdaniel", "2026-07-21T10:01:00Z"), - {"created_at": "2026-07-21T10:02:00Z", "event": "renamed"}, - ] - - assessment = self.assess(issue, timeline) - - self.assertFalse(assessment["approved"]) - self.assertEqual("approval-predates-revision", assessment["reason"]) - - def test_label_removal_invalidates_approval(self) -> None: - issue = intake_issue() - timeline = [ - label_event("rmcdaniel", "2026-07-21T10:01:00Z"), - label_event("rmcdaniel", "2026-07-21T10:02:00Z", event="unlabeled"), - ] - - assessment = self.assess(issue, timeline) - - self.assertFalse(assessment["approved"]) - self.assertEqual("approval-label-absent", assessment["reason"]) - - def test_reapproval_binds_the_new_revision_digest(self) -> None: - original = intake_issue(labels=["intake:approved"]) - original_assessment = self.assess(original, [label_event("rmcdaniel", "2026-07-21T10:01:00Z")]) - edited = intake_issue( - body="A newly reviewed body", - edited_at="2026-07-21T10:02:00Z", - labels=["intake:approved"], - ) - timeline = [ - label_event("rmcdaniel", "2026-07-21T10:01:00Z"), - label_event("rmcdaniel", "2026-07-21T10:02:30Z", event="unlabeled"), - label_event("rmcdaniel", "2026-07-21T10:03:00Z"), - ] - - edited_assessment = self.assess(edited, timeline) - - self.assertTrue(edited_assessment["approved"]) - self.assertNotEqual(original_assessment["revision"], edited_assessment["revision"]) - - def test_clean_machine_reconstruction_is_deterministic_and_reverified(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(author="rmcdaniel") - issues = {".github": [(issue, [])]} - - first, first_inventory = reconstruct_intake(policy, FakeDiscovery(policy, issues)) - second, _second_inventory = reconstruct_intake(policy, FakeDiscovery(policy, issues)) - - self.assertEqual(INTAKE_SCHEMA, first["schema"]) - self.assertEqual(first, second) - self.assertEqual([issue], first_inventory[".github"]) - verified = verify_intake_manifest(policy, first, FakeDiscovery(policy, issues)) - self.assertEqual(first_inventory, verified) - - def test_issue_trigger_reconstructs_every_vetted_revision(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - trigger = intake_issue(author="rmcdaniel") - other = intake_issue( - author="durable-workflow-ops", - body="A separate approved revision", - number=2, - ) - - manifest, inventory = reconstruct_intake( - policy, - FakeDiscovery( - policy, - { - ".github": [(trigger, [])], - "workflow": [(other, [])], - }, - ), - trigger_repository=".github", - trigger_number=1, - trigger_action="closed", - ) - - self.assertTrue(manifest["trigger"]["approved"]) - self.assertEqual( - {(".github", 1), ("workflow", 2)}, - {(record["repository"], record["number"]) for record in manifest["issues"]}, - ) - self.assertEqual([trigger], inventory[".github"]) - self.assertEqual([other], inventory["workflow"]) - - def test_completion_hold_is_bound_as_structured_intake_authority(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(author="rmcdaniel", labels=[COMPLETION_REQUIRED_LABEL]) - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(issue, [])]}), - ) - - self.assertTrue(manifest["issues"][0]["completion_evidence_required"]) - - current = copy.deepcopy(issue) - current["labels"] = [] - verified = verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, {".github": [(current, [])]}), - ) - - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(verified[".github"][0])) - - def test_prerelease_supersession_binds_both_trusted_issue_revisions(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture(include_supersessions=True) - retired = prerelease_authority_issue(59) - successor = prerelease_authority_issue(61, body="Coherent replacement train") - issues = {".github": [(retired, []), (successor, [])]} - manifest, inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues), - ) - - records = {(record["repository"], record["number"]): record for record in manifest["issues"]} - superseded_by = records[(".github", 59)]["superseded_by"] - self.assertEqual(".github", superseded_by["repository"]) - self.assertEqual(61, superseded_by["number"]) - self.assertEqual( - records[(".github", 61)]["revision"], - superseded_by["revision"], - ) - self.assertEqual( - policy["prerelease_supersessions"][0]["activation_commit"], - superseded_by["activation"]["commit"], - ) - self.assertRegex(superseded_by["activation"]["digest"], r"^[0-9a-f]{64}$") - self.assertIsNone(records[(".github", 61)]["superseded_by"]) - self.assertEqual( - inventory, - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, issues), - ), - ) - - def test_immutable_product_train_binds_obsolete_authorities_with_stale_completion(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = immutable_retirement_contract() - retired = [prerelease_authority_issue(number, status="status:triage") for number in (61, 65)] - for issue in retired: - issue["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - issues = {".github": [(issue, []) for issue in retired]} - discovery = FakeDiscovery( - policy, - issues, - public_files=immutable_product_train_files(policy), - ) - - manifest, inventory = reconstruct_intake(policy, discovery) - - records = {(record["repository"], record["number"]): record for record in manifest["issues"]} - for number in (61, 65): - successor = records[(".github", number)]["superseded_by"] - self.assertEqual("2.0.0-beta.17", successor["train"]) - self.assertEqual("release-plan/coherent-2-0-beta-17", successor["release_plan"]["tag"]) - self.assertRegex(successor["activation"]["digest"], r"^[0-9a-f]{64}$") - self.assertEqual(1, len(discovery.file_requests)) - self.assertEqual( - inventory, - verify_intake_manifest( - policy, - manifest, - FakeDiscovery( - policy, - issues, - public_files=immutable_product_train_files(policy), - ), - ), - ) - - def test_independently_versioned_rc_product_train_reconstructs_and_verifies(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema, raw = current_rc_retirement_contract() - retired = prerelease_authority_issue(61, status="status:triage") - issues = {".github": [(retired, [])]} - public_files = product_train_files(policy, raw) - - manifest, inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues, public_files=public_files), - ) - - successor = manifest["issues"][0]["superseded_by"] - self.assertEqual("2.0.0-rc.13", successor["train"]) - self.assertEqual("release-plan/current-2-0-20260801", successor["release_plan"]["tag"]) - self.assertEqual( - inventory, - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, issues, public_files=product_train_files(policy, raw)), - ), - ) - - def test_independently_versioned_rc_product_train_retirement_fails_closed(self) -> None: - mutations = ( - "mismatched current record", - "missing component", - "extra component", - "record channel mismatch", - "component channel mismatch", - "progression mismatch", - "unsupported status", - "release-plan mismatch", - ) - for mutation in mutations: - with self.subTest(mutation=mutation): - policy, _backlog, _policy_schema, _backlog_schema, raw = current_rc_retirement_contract() - product_train = json.loads(raw) - train = product_train["current"] - selected = product_train["trains"][train] - successor = policy["prerelease_supersessions"][0]["successor"] - if mutation == "mismatched current record": - product_train["current"] = "2.0.0-rc.12" - elif mutation == "missing component": - product_train["components"].remove("sdk-rust") - selected["versions"].pop("sdk-rust") - elif mutation == "extra component": - product_train["components"].append("sdk-js") - selected["versions"]["sdk-js"] = "2.0.0-rc.1" - elif mutation == "record channel mismatch": - selected["channel"] = "beta" - elif mutation == "component channel mismatch": - selected["versions"]["workflow"] = "2.0.0-beta.12" - elif mutation == "progression mismatch": - product_train["progression"]["prerelease"] = "synchronized_prerelease_increment" - elif mutation == "unsupported status": - selected["status"] = "historical" - elif mutation == "release-plan mismatch": - successor["release_plan"]["tag"] = "release-plan/different-successor" - - changed_raw = (json.dumps(product_train, indent=2) + "\n").encode() - successor["sha256"] = hashlib.sha256(changed_raw).hexdigest() - retired = prerelease_authority_issue(61, status="status:triage") - - with self.assertRaisesRegex(AuthorityError, "not one coherent supported public product train"): - reconstruct_intake( - policy, - FakeDiscovery( - policy, - {".github": [(retired, [])]}, - public_files=product_train_files(policy, changed_raw), - ), - ) - - def test_shared_product_train_revalidates_each_release_plan_reference(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema, raw = current_rc_retirement_contract() - second_supersession = copy.deepcopy(policy["prerelease_supersessions"][0]) - second_supersession["retired"]["number"] = 65 - policy["prerelease_supersessions"].append(second_supersession) - issues = { - ".github": [ - (prerelease_authority_issue(number, status="status:triage"), []) - for number in (61, 65) - ] - } - mismatched_policy = copy.deepcopy(policy) - mismatched_policy["prerelease_supersessions"][1]["successor"]["release_plan"]["tag"] = ( - "release-plan/different-successor" - ) - with patch("scripts.issue_authority._validate_immutable_product_train_successor"): - mismatched_manifest, _inventory = reconstruct_intake( - mismatched_policy, - FakeDiscovery( - mismatched_policy, - issues, - public_files=product_train_files(mismatched_policy, raw), - ), - ) - - with self.assertRaisesRegex(AuthorityError, "not one coherent supported public product train"): - reconstruct_intake( - mismatched_policy, - FakeDiscovery( - mismatched_policy, - issues, - public_files=product_train_files(mismatched_policy, raw), - ), - ) - with self.assertRaisesRegex(AuthorityError, "not one coherent supported public product train"): - verify_intake_manifest( - mismatched_policy, - mismatched_manifest, - FakeDiscovery( - mismatched_policy, - issues, - public_files=product_train_files(mismatched_policy, raw), - ), - ) - - def test_immutable_product_train_retirement_fails_closed_on_changed_bytes(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = immutable_retirement_contract() - retired = [prerelease_authority_issue(number, status="status:triage") for number in (61, 65)] - issues = {".github": [(issue, []) for issue in retired]} - public_files = immutable_product_train_files(policy) - key = next(iter(public_files)) - public_files[key] += b"\n" - - with self.assertRaisesRegex(AuthorityError, "product-train digest changed"): - reconstruct_intake( - policy, - FakeDiscovery(policy, issues, public_files=public_files), - ) - - def test_activated_issue_successor_can_chain_into_an_immutable_retirement(self) -> None: - old_policy, _backlog, _policy_schema, _backlog_schema = contract_fixture(include_supersessions=True) - issue_59 = prerelease_authority_issue(59) - issue_61 = prerelease_authority_issue(61) - old_manifest, _inventory = reconstruct_intake( - old_policy, - FakeDiscovery(old_policy, {".github": [(issue_59, []), (issue_61, [])]}), - ) - old_activation = old_manifest["issues"][0]["superseded_by"]["activation"] - - policy = json.loads((ROOT / "issue-authority/policy.json").read_text(encoding="utf-8")) - issue_59["state"] = "closed" - issue_59["state_reason"] = "not_planned" - issue_59["labels"] = [ - {"name": SUPERSEDED_STATUS_LABEL if label["name"] in STATUS_LABELS else label["name"]} - for label in issue_59["labels"] - ] - issue_65 = prerelease_authority_issue(65, status="status:triage") - issue_61["labels"] = [ - {"name": "status:triage" if label["name"] in STATUS_LABELS else label["name"]} - for label in issue_61["labels"] - ] - for issue in (issue_61, issue_65): - issue["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - issues = {".github": [(issue, []) for issue in (issue_59, issue_61, issue_65)]} - statuses = { - (".github", old_activation["commit"]): [ - activation_status(old_activation), - ] - } - - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery( - policy, - issues, - commit_statuses=statuses, - public_files=immutable_product_train_files(policy), - ), - ) - - records = {record["number"]: record for record in manifest["issues"]} - self.assertEqual(61, records[59]["superseded_by"]["number"]) - self.assertEqual("2.0.0-beta.17", records[61]["superseded_by"]["train"]) - self.assertEqual("2.0.0-beta.17", records[65]["superseded_by"]["train"]) - - def test_untrusted_retired_issue_cannot_activate_prerelease_supersession(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture(include_supersessions=True) - retired = prerelease_authority_issue(59, author="external-contributor") - successor = prerelease_authority_issue(61) - - with self.assertRaisesRegex(AuthorityError, "does not have trusted current-revision intake"): - reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(retired, []), (successor, [])]}), - ) - - def test_manifest_cannot_redirect_a_bound_prerelease_successor(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture(include_supersessions=True) - retired = prerelease_authority_issue(59) - successor = prerelease_authority_issue(61) - issues = {".github": [(retired, []), (successor, [])]} - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues), - ) - retired_record = next(record for record in manifest["issues"] if record["number"] == 59) - retired_record["superseded_by"]["number"] = 62 - - with self.assertRaisesRegex(AuthorityError, "changed after read-only discovery"): - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, issues), - ) - - def test_closed_successor_and_mutable_retired_state_cannot_manufacture_activation(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture(include_supersessions=True) - retired = prerelease_authority_issue( - 59, - state="closed", - status=SUPERSEDED_STATUS_LABEL, - ) - successor = prerelease_authority_issue( - 61, - state="closed", - status="status:done", - ) - successor["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - - with self.assertRaisesRegex(AuthorityError, "has no immutable active-successor activation"): - reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(retired, []), (successor, [])]}), - ) - - def test_completed_successor_must_match_the_activated_issue_revisions(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture(include_supersessions=True) - retired = prerelease_authority_issue(59) - successor = prerelease_authority_issue(61, body="Coherent replacement train") - active_issues = {".github": [(retired, []), (successor, [])]} - active_manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, active_issues), - ) - retired_record = next(record for record in active_manifest["issues"] if record["number"] == 59) - activation = retired_record["superseded_by"]["activation"] - statuses = { - (".github", activation["commit"]): [ - activation_status(activation), - ] - } - retired["state"] = "closed" - retired["labels"] = [ - {"name": SUPERSEDED_STATUS_LABEL if label["name"] in STATUS_LABELS else label["name"]} - for label in retired["labels"] - ] - successor["state"] = "closed" - successor["labels"] = [ - {"name": "status:done" if label["name"] in STATUS_LABELS else label["name"]} - for label in successor["labels"] - ] - successor["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - completed_issues = {".github": [(retired, []), (successor, [])]} - - completed_manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, completed_issues, commit_statuses=statuses), - ) - - self.assertEqual(activation, completed_manifest["issues"][0]["superseded_by"]["activation"]) - - changed_successor = copy.deepcopy(successor) - changed_successor["body"] = "A redirected replacement train" - with self.assertRaisesRegex(AuthorityError, "conflicting immutable activation authority"): - reconstruct_intake( - policy, - FakeDiscovery( - policy, - {".github": [(retired, []), (changed_successor, [])]}, - commit_statuses=statuses, - ), - ) - - def test_activation_bootstraps_completed_retirements_without_issue_or_contract_mutation(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = immutable_retirement_contract() - issues = { - ".github": [ - ( - prerelease_authority_issue( - number, - state="closed", - status=SUPERSEDED_STATUS_LABEL, - ), - [], - ) - for number in (61, 65) - ] - } - public_files = immutable_product_train_files(policy) - original_policy = copy.deepcopy(policy) - original_issues = copy.deepcopy(issues) - discovery = FakeDiscovery(policy, issues, public_files=public_files) - client = FakeGitHubApi(policy) - - with self.assertRaisesRegex(AuthorityError, "has no immutable active-successor activation"): - reconstruct_intake( - policy, - FakeDiscovery(policy, issues, public_files=public_files), - ) - - evidence = activate_prerelease_supersessions(policy, discovery, client) - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery( - policy, - issues, - commit_statuses=client.commit_statuses, - public_files=public_files, - ), - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual(2, len([record for record in manifest["issues"] if record["superseded_by"] is not None])) - self.assertEqual( - { - "issue-authority/prerelease-supersession/.github/61", - "issue-authority/prerelease-supersession/.github/65", - }, - {activation["context"] for activation in evidence["activations"]}, - ) - self.assertEqual({"created"}, {activation["result"] for activation in evidence["activations"]}) - self.assertEqual( - { - "id": 41_898_282, - "login": "github-actions[bot]", - "type": "Bot", - }, - evidence["activations"][0]["creator"], - ) - self.assertEqual(2, len(client.status_updates)) - self.assertEqual([], client.label_updates) - self.assertEqual([], client.milestone_updates) - self.assertEqual([], client.created_issues) - self.assertEqual([], client.replacements) - self.assertEqual([], client.body_updates) - self.assertEqual([], client.state_updates) - self.assertEqual([], client.comment_updates) - self.assertEqual(original_policy, policy) - self.assertEqual(original_issues, issues) - self.assertEqual(public_files, discovery.public_files) - - def test_activation_is_idempotent_after_statuses_exist(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = immutable_retirement_contract() - issues = { - ".github": [ - ( - prerelease_authority_issue( - number, - state="closed", - status=SUPERSEDED_STATUS_LABEL, - ), - [], - ) - for number in (61, 65) - ] - } - discovery = FakeDiscovery( - policy, - issues, - public_files=immutable_product_train_files(policy), - ) - client = FakeGitHubApi(policy) - activate_prerelease_supersessions(policy, discovery, client) - first_updates = list(client.status_updates) - - evidence = activate_prerelease_supersessions(policy, discovery, client) - - self.assertEqual(first_updates, client.status_updates) - self.assertEqual({"existing"}, {activation["result"] for activation in evidence["activations"]}) - - def test_activation_rejects_all_writes_when_any_trusted_status_conflicts(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = immutable_retirement_contract() - issues = { - ".github": [ - ( - prerelease_authority_issue( - number, - state="closed", - status=SUPERSEDED_STATUS_LABEL, - ), - [], - ) - for number in (61, 65) - ] - } - discovery = FakeDiscovery( - policy, - issues, - public_files=immutable_product_train_files(policy), - ) - derived_client = FakeGitHubApi(policy) - derived = activate_prerelease_supersessions(policy, discovery, derived_client) - conflicting = activation_status(derived["activations"][1]) - conflicting["description"] = f"sha256:{'0' * 64}" - client = FakeGitHubApi(policy) - activation_commit = derived["activations"][1]["commit"] - client.commit_statuses[(".github", activation_commit)] = [conflicting] - - with self.assertRaisesRegex(AuthorityError, "conflicting immutable activation authority"): - activate_prerelease_supersessions(policy, discovery, client) - - self.assertEqual([], client.status_updates) - self.assertEqual([conflicting], client.commit_statuses[(".github", activation_commit)]) - - def test_cross_repository_targets_are_bound_from_the_vetted_form_section(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - qualification = qualification_fixture() - issue = intake_issue( - author="rmcdaniel", - body=( - "### Required source targets\n\n" - "durable-workflow/.github@main\n" - "durable-workflow/workflow@v2\n\n" - "### Repository roles\n\nShared authority and consumer.\n" - ), - labels=["kind:cross-repository"], - ) - discovery = FakeDiscovery(policy, {".github": [(issue, [])]}) - - manifest, inventory = reconstruct_intake( - policy, - discovery, - target_qualification=qualification, - ) - targets = manifest["issues"][0]["cross_repository_targets"] - - self.assertEqual([".github", "workflow"], [target["repository"] for target in targets]) - self.assertEqual(["main", "v2"], [target["branch"] for target in targets]) - self.assertTrue(all(target["required_checks"] for target in targets)) - self.assertEqual( - inventory, - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, {".github": [(issue, [])]}), - target_qualification=qualification, - ), - ) - - def test_api_and_issue_form_target_headings_use_the_same_contract(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - qualification = qualification_fixture() - - for heading in ("## Required source targets", "### Required source targets"): - with self.subTest(heading=heading): - issue = intake_issue( - author="rmcdaniel", - body=( - f"{heading}\n\n" - "durable-workflow/.github@main\n" - "durable-workflow/workflow@v2\n\n" - "## Repository roles\n\nShared authority and consumer.\n" - ), - labels=["kind:cross-repository"], - ) - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(issue, [])]}), - target_qualification=qualification, - ) - - self.assertEqual( - [".github", "workflow"], - [target["repository"] for target in manifest["issues"][0]["cross_repository_targets"]], - ) - - def test_cross_repository_intake_rejects_invalid_target_sets(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - qualification = qualification_fixture() - invalid_bodies = { - "missing": ("Current body", "must declare its required source targets"), - "empty": ( - "### Required source targets\n\n### Repository roles\n\nShared authority.", - "at least two public targets", - ), - "single": ( - "### Required source targets\n\ndurable-workflow/.github@main", - "at least two public targets", - ), - "duplicate": ( - "### Required source targets\n\ndurable-workflow/.github@main\ndurable-workflow/.github@main", - "duplicate target", - ), - "unqualified": ( - "### Required source targets\n\ndurable-workflow/.github@main\ndurable-workflow/workflow@main", - "not a qualified target", - ), - "repeated-section": ( - "## Required source targets\n\n" - "durable-workflow/.github@main\n" - "durable-workflow/workflow@v2\n\n" - "### Required source targets\n\n" - "durable-workflow/.github@main\n" - "durable-workflow/server@main", - "repeats its affected public repositories section", - ), - } - - for name, (body, message) in invalid_bodies.items(): - with self.subTest(name=name): - issue = intake_issue( - author="rmcdaniel", - body=body, - labels=["kind:cross-repository"], - ) - discovery = FakeDiscovery(policy, {".github": [(issue, [])]}) - - manifest, inventory = reconstruct_intake( - policy, - discovery, - target_qualification=qualification, - ) - - self.assertEqual([], manifest["issues"]) - self.assertRegex(manifest["rejected_issues"][0]["reason"], message) - self.assertEqual([], inventory[".github"]) - - def test_malformed_target_record_is_reported_without_suppressing_valid_intake(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - malformed = intake_issue( - author="rmcdaniel", - labels=["kind:cross-repository"], - ) - valid = intake_issue( - author="durable-workflow-ops", - body="Independent valid authority.", - number=2, - ) - issues = {".github": [(malformed, []), (valid, [])]} - - manifest, inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues), - target_qualification=qualification_fixture(), - trigger_repository=".github", - trigger_number=2, - trigger_action="closed", - ) - - self.assertTrue(manifest["trigger"]["approved"]) - self.assertEqual([2], [record["number"] for record in manifest["issues"]]) - self.assertEqual([1], [record["number"] for record in manifest["rejected_issues"]]) - self.assertIn("must declare its required source targets", manifest["rejected_issues"][0]["reason"]) - self.assertEqual([valid], inventory[".github"]) - self.assertEqual( - inventory, - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, issues), - target_qualification=qualification_fixture(), - ), - ) - - affected, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues), - target_qualification=qualification_fixture(), - trigger_repository=".github", - trigger_number=1, - trigger_action="labeled", - trigger_actor="rmcdaniel", - trigger_label="kind:cross-repository", - ) - - self.assertFalse(affected["trigger"]["approved"]) - self.assertEqual("source-targets-invalid", affected["trigger"]["reason"]) - self.assertEqual([2], [record["number"] for record in affected["issues"]]) - with tempfile.TemporaryDirectory() as directory: - output = Path(directory) / "github-output" - _write_discovery_outputs(output, affected) - self.assertEqual( - ["intake_ready=true", "trigger_approved=false"], - output.read_text(encoding="utf-8").splitlines(), - ) - - def test_exact_python_and_documentation_targets_are_immutably_bound(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue( - author="rmcdaniel", - body="Approved package and quickstart authority.", - labels=["kind:release-blocker"], - number=32, - ) - issues = {"sdk-python": [(issue, [])]} - - manifest, inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ) - - self.assertEqual([], manifest["rejected_issues"]) - self.assertEqual( - ["durable-workflow.github.io", "sdk-python"], - [target["repository"] for target in manifest["issues"][0]["cross_repository_targets"]], - ) - self.assertEqual( - inventory, - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, issues), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ), - ) - - def test_immutable_issue_binding_rejects_forged_and_changed_target_declarations(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue( - author="rmcdaniel", - body="Approved package and quickstart authority.", - labels=["kind:release-blocker"], - number=32, - ) - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {"sdk-python": [(issue, [])]}), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ) - changed = copy.deepcopy(issue) - changed["body"] = ( - "### Required source targets\n\n" - "durable-workflow/durable-workflow.github.io@main\n" - "durable-workflow/sdk-python@main\n" - "durable-workflow/workflow@v2" - ) - changed["last_edited_at"] = "2026-07-21T10:02:00Z" - changed["labels"].append({"name": "intake:approved"}) - timeline = [label_event("rmcdaniel", "2026-07-21T10:03:00Z")] - - forged, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {"sdk-python": [(changed, timeline)]}), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ) - - self.assertEqual([], forged["issues"]) - self.assertIn("differ from its immutable target binding", forged["rejected_issues"][0]["reason"]) - with self.assertRaisesRegex(AuthorityError, "differ from its immutable target binding"): - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, {"sdk-python": [(changed, timeline)]}), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ) - - changed_binding = legacy_target_fixture() - changed_binding["immutable_issue_targets"][0]["targets"].append("durable-workflow/workflow@v2") - with self.assertRaisesRegex(AuthorityError, "changed after read-only discovery"): - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, {"sdk-python": [(issue, [])]}), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=changed_binding, - ) - - def test_non_cross_repository_intake_rejects_multiple_targets(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue( - author="rmcdaniel", - body=("### Required source targets\n\ndurable-workflow/.github@main\ndurable-workflow/workflow@v2"), - labels=["kind:release-blocker"], - ) - - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(issue, [])]}), - target_qualification=qualification_fixture(), - ) - - self.assertEqual([], manifest["issues"]) - self.assertIn("without cross-repository authority", manifest["rejected_issues"][0]["reason"]) - - def test_frozen_lifecycle_migrations_bind_runtime_revisions_to_exact_trusted_creation(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - migration = legacy_target_fixture() - issues: dict[str, list[tuple[dict[str, Any], list[dict[str, Any]]]]] = {} - expected: dict[tuple[str, int], str] = {} - for frozen in migration["frozen_lifecycle_migrations"]: - approval_actor = next( - actor - for actor in policy["intake"]["trusted_actors"] - if hashlib.sha256(actor.casefold().encode("utf-8")).hexdigest() - == frozen["approval_actor_sha256"] - ) - body = ( - "Reviewed historical authority.\n\n" - f"{EVIDENCE_MARKER}\n\n" - "### Required source targets\n\n" - + "\n".join(frozen["declared_targets"]) - ) - issue = intake_issue( - author=approval_actor, - body=body, - labels=["authority:github", "kind:cross-repository", "priority:P2", "status:done"], - number=frozen["number"], - ) - issue["created_at"] = frozen["approval_at"] - issue["state"] = "closed" - issue["state_reason"] = "completed" - issues.setdefault(frozen["repository"], []).append((issue, [])) - revision = issue_revision_digest(issue["title"], issue["body"]) - frozen["approved_issue_revision_sha256"] = revision - expected[(frozen["repository"], frozen["number"])] = revision - - manifest, inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=migration, - ) - - records = { - (record["repository"], record["number"]): record for record in manifest["issues"] - } - self.assertEqual(set(expected), set(records)) - for identity, revision in expected.items(): - frozen = records[identity]["frozen_cross_repository_lifecycle"] - self.assertIsNotNone(frozen) - self.assertEqual(revision, frozen["approved_issue_revision_sha256"]) - self.assertEqual( - frozen["approval_actor_sha256"], - hashlib.sha256( - records[identity]["approval_actor"].casefold().encode("utf-8") - ).hexdigest(), - ) - verified = verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, issues), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=migration, - ) - self.assertEqual(inventory, verified) - - for identity in expected: - repository, number = identity - for field in ("title", "body"): - with self.subTest(identity=identity, drifted_field=field): - drifted = copy.deepcopy(issues) - drifted_issue = drifted[repository][0][0] - if field == "title": - drifted_issue[field] += " after review" - else: - drifted_issue[field] = "Changed after review.\n\n" + drifted_issue[field] - drifted_manifest, _drifted_inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, drifted), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=migration, - ) - self.assertNotIn( - identity, - { - (record["repository"], record["number"]) - for record in drifted_manifest["issues"] - }, - ) - self.assertIn( - "current revision differs from reviewed frozen authority", - next( - record["reason"] - for record in drifted_manifest["rejected_issues"] - if (record["repository"], record["number"]) == identity - ), - ) - with self.assertRaisesRegex( - AuthorityError, - "current revision differs from reviewed frozen authority", - ): - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, drifted), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=migration, - ) - - changed = copy.deepcopy(issues) - changed_issue = changed["durable-workflow.github.io"][0][0] - changed_issue["last_edited_at"] = "2026-07-30T20:38:00Z" - with self.assertRaisesRegex(AuthorityError, "changed after read-only discovery"): - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, changed), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=migration, - ) - - def test_legacy_cross_repository_authority_requires_edit_and_reapproval(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - legacy = intake_issue( - author="rmcdaniel", - labels=["kind:cross-repository"], - ) - - rejected, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(legacy, [])]}), - target_qualification=qualification_fixture(), - ) - - self.assertEqual([], rejected["issues"]) - self.assertIn("must declare its required source targets", rejected["rejected_issues"][0]["reason"]) - - migrated = intake_issue( - author="rmcdaniel", - body=("### Required source targets\n\ndurable-workflow/.github@main\ndurable-workflow/workflow@v2"), - edited_at="2026-07-21T10:02:00Z", - labels=["intake:approved", "kind:cross-repository"], - ) - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery( - policy, - { - ".github": [ - ( - migrated, - [ - label_event( - "durable-workflow-ops", - "2026-07-21T10:02:30Z", - event="unlabeled", - ), - label_event("durable-workflow-ops", "2026-07-21T10:03:00Z"), - ], - ) - ] - }, - ), - target_qualification=qualification_fixture(), - ) - - self.assertEqual(2, len(manifest["issues"][0]["cross_repository_targets"])) - - def test_captured_legacy_active_and_closed_revisions_use_exact_migrated_targets(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - active = intake_issue( - author="rmcdaniel", - body=( - "Approved API-created authority.\n\n" - "" - ), - labels=["authority:github", "kind:cross-repository", "priority:P1", "status:ready"], - number=40, - ) - active_hygiene = intake_issue( - author="durable-workflow-ops", - body=( - "## Required source targets\n\n" - "durable-workflow/.github@main\n" - "durable-workflow/cli@main\n" - "durable-workflow/durable-workflow.github.io@main\n" - "durable-workflow/sample-app@main\n" - "durable-workflow/sdk-php@main\n" - "durable-workflow/sdk-python@main\n" - "durable-workflow/sdk-rust@main\n" - "durable-workflow/server@main\n" - "durable-workflow/waterline@v2\n" - "durable-workflow/workflow@v2\n\n" - "### Repository roles\n\nActive public source hygiene." - ), - edited_at="2026-07-25T02:05:00Z", - labels=[ - "authority:github", - "intake:approved", - "kind:cross-repository", - "priority:P2", - "status:ready", - ], - number=42, - ) - closed = intake_issue( - author="rmcdaniel", - body=( - "Completed trusted-created authority.\n\n" - "" - ), - labels=["authority:github", "kind:cross-repository", "priority:P1", "status:done"], - number=39, - ) - closed["state"] = "closed" - migration = legacy_target_fixture() - closed_targets = next( - authority["targets"] - for authority in migration["authorities"] - if authority["id"] == "github-actions-trust-boundary" - ) - migration["historical_completions"] = [ - { - "number": 39, - "repository": ".github", - "revision": issue_revision_digest(closed["title"], closed["body"]), - "targets": closed_targets, - } - ] - evolved_qualification = qualification_fixture() - control_plane = next( - target for target in evolved_qualification["targets"].values() if target["repository"] == ".github" - ) - control_plane["workflows"][0]["required_check"] = "Renamed control-plane qualification" - control_plane["workflows"].append( - { - "matrix_independent": True, - "path": "future-qualification.yml", - "required_check": "Additional control-plane qualification", - } - ) - issues = { - ".github": [ - (closed, []), - (active, []), - (active_hygiene, [label_event("durable-workflow-ops", "2026-07-25T02:06:00Z")]), - ] - } - - manifest, inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, issues), - target_qualification=evolved_qualification, - legacy_cross_repository_targets=migration, - ) - - records = {record["number"]: record for record in manifest["issues"]} - self.assertEqual(8, len(records[40]["cross_repository_targets"])) - self.assertEqual(10, len(records[42]["cross_repository_targets"])) - self.assertEqual(10, len(records[39]["cross_repository_targets"])) - self.assertEqual(10, len(records[39]["historical_cross_repository_completion"])) - self.assertEqual([], records[40]["historical_cross_repository_completion"]) - self.assertEqual([], records[42]["historical_cross_repository_completion"]) - self.assertEqual("trusted-creation", records[40]["approval_mode"]) - self.assertEqual("trusted-label", records[42]["approval_mode"]) - current_control_plane = next( - target for target in records[40]["cross_repository_targets"] if target["repository"] == ".github" - ) - archived_control_plane = next( - target - for target in records[39]["historical_cross_repository_completion"] - if target["repository"] == ".github" - ) - self.assertEqual( - ["Additional control-plane qualification", "Renamed control-plane qualification"], - current_control_plane["required_checks"], - ) - self.assertEqual(["Control-plane source qualification"], archived_control_plane["required_checks"]) - self.assertEqual( - inventory, - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, issues), - target_qualification=evolved_qualification, - legacy_cross_repository_targets=migration, - ), - ) - - def test_edited_api_created_revision_uses_its_declared_targets_not_legacy_migration(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue( - author="rmcdaniel", - body=( - "## Required source targets\n\n" - "durable-workflow/.github@main\n" - "durable-workflow/workflow@v2\n" - "durable-workflow/waterline@v2\n" - "durable-workflow/server@main\n" - "durable-workflow/cli@main\n" - "durable-workflow/sdk-php@main\n" - "durable-workflow/sdk-python@main\n" - "durable-workflow/sdk-rust@main\n\n" - "### Repository roles\n\nActive source and consumers." - ), - edited_at="2026-07-25T01:59:00Z", - labels=["intake:approved", "kind:cross-repository"], - number=52, - ) - timeline = [label_event("rmcdaniel", "2026-07-25T02:00:00Z")] - - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(issue, timeline)]}), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ) - - record = manifest["issues"][0] - self.assertEqual("trusted-label", record["approval_mode"]) - self.assertEqual(8, len(record["cross_repository_targets"])) - self.assertEqual([], record["historical_cross_repository_completion"]) - - def test_legacy_issue_form_revision_migrates_its_bounded_repository_set(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue( - author="rmcdaniel", - body=( - "### Affected public repositories\n\n" - "- `durable-workflow/.github` owns the decision.\n" - "- `durable-workflow/workflow` consumes it.\n\n" - "### Shared public contract\n\nLegacy form revision." - ), - labels=["kind:cross-repository"], - ) - - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(issue, [])]}), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ) - - self.assertEqual( - [".github", "workflow"], - [target["repository"] for target in manifest["issues"][0]["cross_repository_targets"]], - ) - - def test_legacy_target_migration_cannot_authorize_later_revisions_or_relabels(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - body = ( - "Legacy authority.\n\n" - "" - ) - cases: dict[str, tuple[dict[str, Any], list[dict[str, Any]]]] = {} - - created_late = intake_issue( - author="rmcdaniel", - body=body, - labels=["kind:cross-repository"], - ) - created_late["created_at"] = "2026-07-25T00:02:00Z" - cases["created-after-cutoff"] = (created_late, []) - - edited = intake_issue( - author="rmcdaniel", - body=body, - edited_at="2026-07-25T00:02:00Z", - labels=["intake:approved", "kind:cross-repository"], - ) - cases["edited-and-reapproved"] = ( - edited, - [label_event("rmcdaniel", "2026-07-25T00:03:00Z")], - ) - - relabeled = intake_issue( - author="rmcdaniel", - body=body, - labels=["kind:cross-repository"], - ) - cases["relabeled-after-cutoff"] = ( - relabeled, - [ - label_event( - "rmcdaniel", - "2026-07-25T00:03:00Z", - label="kind:cross-repository", - ) - ], - ) - - for name, (issue, timeline) in cases.items(): - with self.subTest(name=name): - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(issue, timeline)]}), - target_qualification=qualification_fixture(), - legacy_cross_repository_targets=legacy_target_fixture(), - ) - - self.assertEqual([], manifest["issues"]) - self.assertIn( - "must declare its required source targets", - manifest["rejected_issues"][0]["reason"], - ) - - def test_manifest_revalidation_rejects_cross_repository_label_without_targets(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(author="rmcdaniel") - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(issue, [])]}), - target_qualification=qualification_fixture(), - ) - relabeled = copy.deepcopy(issue) - relabeled["labels"] = [{"name": "kind:cross-repository"}] - - with self.assertRaisesRegex(AuthorityError, "must declare its required source targets"): - verify_intake_manifest( - policy, - manifest, - FakeDiscovery(policy, {".github": [(relabeled, [])]}), - target_qualification=qualification_fixture(), - ) - - def test_completion_hold_manifest_field_must_be_boolean(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(author="rmcdaniel") - discovery = FakeDiscovery(policy, {".github": [(issue, [])]}) - manifest, _inventory = reconstruct_intake(policy, discovery) - manifest["issues"][0]["completion_evidence_required"] = "false" - - with self.assertRaisesRegex(AuthorityError, "invalid issue authority"): - verify_intake_manifest(policy, manifest, discovery) - - def test_historical_completion_manifest_field_must_be_a_list(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(author="rmcdaniel") - discovery = FakeDiscovery(policy, {".github": [(issue, [])]}) - manifest, _inventory = reconstruct_intake(policy, discovery) - manifest["issues"][0]["historical_cross_repository_completion"] = "complete" - - with self.assertRaisesRegex(AuthorityError, "invalid issue authority"): - verify_intake_manifest(policy, manifest, discovery) - - def test_concurrent_unselected_trusted_issue_remains_inert_during_revalidation(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - selected = intake_issue(author="rmcdaniel") - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(selected, [])]}), - ) - concurrent = intake_issue(author="durable-workflow-ops", body="Later issue", number=2) - discovery = FakeDiscovery(policy, {".github": [(selected, []), (concurrent, [])]}) - - verified = verify_intake_manifest(policy, manifest, discovery) - - self.assertEqual([selected], verified[".github"]) - self.assertEqual([], discovery.list_requests) - self.assertEqual([(".github", 1)], discovery.get_requests) - - def test_selected_issue_revision_change_fails_manifest_revalidation(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - selected = intake_issue(author="rmcdaniel") - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(selected, [])]}), - ) - changed = {".github": [(copy.deepcopy(selected), [])]} - changed[".github"][0][0]["body"] = "Changed after discovery" - changed[".github"][0][0]["last_edited_at"] = "2026-07-21T10:05:00Z" - with self.assertRaisesRegex(AuthorityError, "changed after read-only discovery"): - verify_intake_manifest(policy, manifest, FakeDiscovery(policy, changed)) - - def test_edited_trigger_fails_closed_during_api_convergence(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(author="rmcdaniel") - issues = {".github": [(issue, [])]} - discovery = FakeDiscovery(policy, issues) - - manifest, _inventory = reconstruct_intake( - policy, - discovery, - trigger_repository=".github", - trigger_number=1, - trigger_action="edited", - ) - - self.assertFalse(manifest["trigger"]["approved"]) - self.assertEqual("revision-edited", manifest["trigger"]["reason"]) - self.assertEqual([], discovery.get_requests) - - def test_approval_label_removal_fails_closed_during_api_convergence(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(labels=["intake:approved"]) - issues = {".github": [(issue, [label_event("rmcdaniel", "2026-07-21T10:01:00Z")])]} - discovery = FakeDiscovery(policy, issues) - - manifest, _inventory = reconstruct_intake( - policy, - discovery, - trigger_repository=".github", - trigger_number=1, - trigger_action="unlabeled", - trigger_actor="external-contributor", - trigger_label="intake:approved", - ) - - self.assertFalse(manifest["trigger"]["approved"]) - self.assertEqual("approval-label-removed", manifest["trigger"]["reason"]) - self.assertEqual([], discovery.get_requests) - - def test_untrusted_approval_actor_fails_closed_during_api_convergence(self) -> None: - policy, _backlog, _policy_schema, _backlog_schema = contract_fixture() - issue = intake_issue(labels=["intake:approved"]) - issues = {".github": [(issue, [label_event("rmcdaniel", "2026-07-21T10:01:00Z")])]} - discovery = FakeDiscovery(policy, issues) - - manifest, _inventory = reconstruct_intake( - policy, - discovery, - trigger_repository=".github", - trigger_number=1, - trigger_action="labeled", - trigger_actor="external-contributor", - trigger_label="intake:approved", - ) - - self.assertFalse(manifest["trigger"]["approved"]) - self.assertEqual("approval-actor-untrusted", manifest["trigger"]["reason"]) - self.assertEqual([], discovery.get_requests) - - -class GitHubApiTest(unittest.TestCase): - def test_discovery_uses_the_read_only_job_token_and_reads_state_reason(self) -> None: - client = GitHubDiscovery("job-token") - node = { - "author": {"login": "rmcdaniel"}, - "body": "Retired prerelease authority.", - "createdAt": "2026-07-25T09:00:00Z", - "labels": {"nodes": [], "pageInfo": {"hasNextPage": False}}, - "lastEditedAt": None, - "milestone": None, - "number": 59, - "state": "CLOSED", - "stateReason": "NOT_PLANNED", - "timelineItems": {"nodes": []}, - "title": "Retired authority", - "url": "https://github.com/durable-workflow/.github/issues/59", - } - response = FakeResponse( - json.dumps( - { - "data": { - "repository": { - "issues": { - "nodes": [node], - "pageInfo": {"hasNextPage": False}, - } - } - } - } - ).encode() - ) - - with patch("urllib.request.urlopen", return_value=response) as urlopen: - issues = client.list_issues("durable-workflow", ".github") - - request = urlopen.call_args.args[0] - self.assertEqual("not_planned", issues[0][0]["state_reason"]) - self.assertEqual("Bearer job-token", request.get_header("Authorization")) - self.assertEqual("POST", request.method) - self.assertIn("stateReason", json.loads(request.data)["query"]) - - def test_discovery_reads_append_only_activation_statuses_with_the_job_token(self) -> None: - client = GitHubDiscovery("job-token") - status = { - "context": "issue-authority/prerelease-supersession/.github/59", - "description": f"sha256:{'b' * 64}", - "state": "success", - } - - with patch( - "urllib.request.urlopen", - return_value=FakeResponse(json.dumps([status]).encode()), - ) as urlopen: - self.assertEqual( - [status], - client.list_commit_statuses("durable-workflow", ".github", "a" * 40), - ) - - request = urlopen.call_args.args[0] - self.assertEqual("Bearer job-token", request.get_header("Authorization")) - self.assertEqual("GET", request.method) - - def test_discovery_reads_immutable_product_train_bytes_with_the_job_token(self) -> None: - client = GitHubDiscovery("job-token") - response = FakeResponse(b'{"schema":"durable-workflow.product-train/v2"}') - - with patch("urllib.request.urlopen", return_value=response) as urlopen: - payload = client.read_file( - "durable-workflow", - ".github", - "a" * 40, - "product-train/current.json", - ) - - request = urlopen.call_args.args[0] - self.assertEqual(response.payload, payload) - self.assertEqual("GET", request.method) - self.assertEqual("application/vnd.github.raw+json", request.get_header("Accept")) - self.assertEqual("Bearer job-token", request.get_header("Authorization")) - self.assertTrue(request.full_url.endswith(f"/contents/product-train/current.json?ref={'a' * 40}")) - - def test_lifecycle_reads_captured_graphql_closing_reference_shape(self) -> None: - client = GitHubApi( - "writer-token", - read_token="job-token", - graphql_url="https://api.github.test/graphql", - ) - payload = (ROOT / "tests/fixtures/github/cross-referenced-events.json").read_bytes() - - with patch("urllib.request.urlopen", return_value=FakeResponse(payload)) as urlopen: - references = client.list_issue_closing_references("durable-workflow", ".github", 99) - - self.assertEqual(["CRE_kwDOA1b2c84AAZ9x", "CRE_kwDOA1b2c84AAaB7"], [event["id"] for event in references]) - request = urlopen.call_args.args[0] - body = json.loads(request.data) - self.assertEqual("https://api.github.test/graphql", request.full_url) - self.assertEqual("Bearer job-token", request.get_header("Authorization")) - self.assertEqual( - {"cursor": None, "number": 99, "owner": "durable-workflow", "repository": ".github"}, - body["variables"], - ) - self.assertIn("... on CrossReferencedEvent", body["query"]) - self.assertIn("willCloseTarget", body["query"]) - - def test_create_request_is_not_repeated_after_an_ambiguous_failure(self) -> None: - client = GitHubApi("secret") - responses = [ - urllib.error.URLError(ConnectionResetError("response lost")), - FakeResponse(b'{"number":1}'), - ] - - with ( - patch("urllib.request.urlopen", side_effect=responses) as urlopen, - self.assertRaisesRegex(AuthorityError, "failed after bounded retries"), - ): - client.request("POST", "/repos/durable-workflow/.github/issues", {"title": "work"}) - - self.assertEqual(1, urlopen.call_count) - - def test_lifecycle_reads_use_the_job_token_while_mutations_use_the_writer(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - responses = [FakeResponse(b'{"state":"open"}'), FakeResponse(b"")] - - with patch("urllib.request.urlopen", side_effect=responses) as urlopen: - client.request("GET", "/repos/durable-workflow/workflow") - client.request("PATCH", "/repos/durable-workflow/.github/issues/1", {"state": "open"}) - - read_request = urlopen.call_args_list[0].args[0] - write_request = urlopen.call_args_list[1].args[0] - self.assertEqual("Bearer job-token", read_request.get_header("Authorization")) - self.assertEqual("Bearer writer-token", write_request.get_header("Authorization")) - - def test_successful_check_identity_binds_the_exact_actions_run(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - payload = { - "check_runs": [ - { - "completed_at": "2026-07-27T10:00:00Z", - "conclusion": "success", - "details_url": "https://github.com/durable-workflow/workflow/actions/runs/30200000003/job/91", - "id": 91, - "name": "Target branch qualification", - "status": "completed", - }, - { - "completed_at": "2026-07-27T10:01:00Z", - "conclusion": "success", - "details_url": "https://example.com/mutable/latest", - "id": 92, - "name": "Unbound check", - "status": "completed", - }, - ] - } - - with patch("urllib.request.urlopen", return_value=FakeResponse(json.dumps(payload).encode())): - identities = client.successful_check_run_ids("durable-workflow", "workflow", "c" * 40) - - self.assertEqual({"Target branch qualification": 30200000003}, identities) - - def test_cited_workflow_run_binds_success_to_repository_workflow_and_commit(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - commit = "c" * 40 - workflow_id = 314161405 - payload = { - "conclusion": "success", - "event": "push", - "head_branch": "v2", - "head_sha": commit, - "html_url": "https://github.com/durable-workflow/workflow/actions/runs/30200000003", - "id": 30200000003, - "name": "PHP", - "path": ".github/workflows/php.yml", - "repository": {"full_name": "durable-workflow/workflow"}, - "status": "completed", - "workflow_id": workflow_id, - } - workflow = { - "id": workflow_id, - "name": "PHP", - "path": ".github/workflows/php.yml", - } - - with patch( - "urllib.request.urlopen", - side_effect=[ - FakeResponse(json.dumps(payload).encode()), - FakeResponse(json.dumps(workflow).encode()), - ], - ): - successful = client.successful_workflow_run( - "durable-workflow", - "workflow", - 30200000003, - commit, - "php.yml", - "PHP", - ) - - self.assertTrue(successful) - - with patch( - "urllib.request.urlopen", - side_effect=[ - FakeResponse(json.dumps(payload).encode()), - FakeResponse(json.dumps(workflow).encode()), - ], - ): - legacy_generic = client.successful_workflow_run( - "durable-workflow", - "workflow", - 30200000003, - commit, - None, - None, - ) - - self.assertTrue(legacy_generic) - - custom_run_name = {**payload, "name": "Candidate main", "path": ".github/workflows/beta-candidate.yml"} - beta_definition = { - "id": workflow_id, - "name": "Beta candidate", - "path": ".github/workflows/beta-candidate.yml", - } - with patch( - "urllib.request.urlopen", - side_effect=[ - FakeResponse(json.dumps(custom_run_name).encode()), - FakeResponse(json.dumps(beta_definition).encode()), - ], - ): - custom_named_run = client.successful_workflow_run( - "durable-workflow", - "workflow", - 30200000003, - commit, - "beta-candidate.yml", - "Beta candidate", - ) - - self.assertTrue(custom_named_run) - - for field, value in ( - ("head_sha", "d" * 40), - ("html_url", "https://github.com/durable-workflow/workflow/actions/runs/latest"), - ("path", ".github/workflows/unrelated.yml"), - ("repository", {"full_name": "external/workflow"}), - ("conclusion", "failure"), - ): - with self.subTest(field=field): - rejected = dict(payload) - rejected[field] = value - with patch("urllib.request.urlopen", return_value=FakeResponse(json.dumps(rejected).encode())): - self.assertFalse( - client.successful_workflow_run( - "durable-workflow", - "workflow", - 30200000003, - commit, - "php.yml", - "PHP", - ) - ) - - for field, value in ( - ("id", workflow_id + 1), - ("name", "Unrelated workflow"), - ("path", ".github/workflows/unrelated.yml"), - ): - with self.subTest(workflow_definition_field=field): - rejected_workflow = {**workflow, field: value} - with patch( - "urllib.request.urlopen", - side_effect=[ - FakeResponse(json.dumps(payload).encode()), - FakeResponse(json.dumps(rejected_workflow).encode()), - ], - ): - self.assertFalse( - client.successful_workflow_run( - "durable-workflow", - "workflow", - 30200000003, - commit, - "php.yml", - "PHP", - ) - ) - - for field, value in ( - ("name", ""), - ("path", ".github/workflows/README.md"), - ("workflow_id", None), - ): - with self.subTest(generic_workflow_field=field): - rejected = dict(payload) - rejected[field] = value - with patch("urllib.request.urlopen", return_value=FakeResponse(json.dumps(rejected).encode())): - self.assertFalse( - client.successful_workflow_run( - "durable-workflow", - "workflow", - 30200000003, - commit, - None, - None, - ) - ) - - def test_historical_workflow_evidence_does_not_depend_on_current_definition_name(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - commit = "c" * 40 - run_id = 30200000003 - payload = { - "conclusion": "success", - "event": "push", - "head_branch": "v2", - "head_sha": commit, - "html_url": f"https://github.com/durable-workflow/workflow/actions/runs/{run_id}", - "id": run_id, - "name": "Archived qualification", - "path": ".github/workflows/archived.yml", - "repository": {"full_name": "durable-workflow/workflow"}, - "status": "completed", - } - - with patch("urllib.request.urlopen", return_value=FakeResponse(json.dumps(payload).encode())) as urlopen: - successful = client.successful_historical_workflow_run( - "durable-workflow", - "workflow", - run_id, - commit, - "v2", - "archived.yml", - "Archived qualification", - ) - - self.assertTrue(successful) - self.assertEqual(1, urlopen.call_count) - - for field, value in (("event", "pull_request"), ("head_branch", "main")): - with self.subTest(field=field): - rejected = {**payload, field: value} - with patch( - "urllib.request.urlopen", - return_value=FakeResponse(json.dumps(rejected).encode()), - ): - self.assertFalse( - client.successful_historical_workflow_run( - "durable-workflow", - "workflow", - run_id, - commit, - "v2", - "archived.yml", - "Archived qualification", - ) - ) - - jobs = { - "jobs": [ - { - "conclusion": "success", - "html_url": f"https://github.com/durable-workflow/workflow/actions/runs/{run_id}/job/91", - "id": 91, - "name": "Target branch qualification", - "status": "completed", - }, - { - "conclusion": "failure", - "html_url": f"https://github.com/durable-workflow/workflow/actions/runs/{run_id}/job/92", - "id": 92, - "name": "Failed check", - "status": "completed", - }, - ], - "total_count": 2, - } - with patch("urllib.request.urlopen", return_value=FakeResponse(json.dumps(jobs).encode())): - successful_jobs = client.successful_historical_workflow_jobs( - "durable-workflow", - "workflow", - run_id, - ) - - self.assertEqual({"Target branch qualification": 91}, successful_jobs) - - def test_issue_state_mutations_send_the_explicit_terminal_reason(self) -> None: - client = GitHubApi("writer-token") - - for state, state_reason in ( - ("closed", "not_planned"), - ("closed", "completed"), - ("open", "reopened"), - ): - with self.subTest(state=state, state_reason=state_reason): - with patch("urllib.request.urlopen", return_value=FakeResponse(b"")) as urlopen: - client.update_issue_state( - "durable-workflow", - ".github", - 59, - state, - state_reason=state_reason, - ) - - request = urlopen.call_args.args[0] - self.assertEqual("PATCH", request.method) - self.assertEqual( - {"state": state, "state_reason": state_reason}, - json.loads(request.data), - ) - - def test_supersession_activation_uses_the_narrow_status_token(self) -> None: - client = GitHubApi( - "writer-token", - activation_token="status-token", - read_token="job-token", - ) - activation = { - "commit": "a" * 40, - "context": "issue-authority/prerelease-supersession/.github/59", - "digest": "b" * 64, - } - created = activation_status(activation) - responses = [ - FakeResponse(b"[]"), - FakeResponse(json.dumps(created).encode()), - ] - - with patch("urllib.request.urlopen", side_effect=responses) as urlopen: - client.ensure_supersession_activation( - "durable-workflow", - ".github", - activation, - ) - - status_read, status_create = [call.args[0] for call in urlopen.call_args_list] - self.assertEqual("Bearer job-token", status_read.get_header("Authorization")) - self.assertEqual("Bearer status-token", status_create.get_header("Authorization")) - self.assertEqual("POST", status_create.method) - self.assertEqual( - { - "context": activation["context"], - "description": f"sha256:{activation['digest']}", - "state": "success", - }, - json.loads(status_create.data), - ) - - def test_lifecycle_upsert_ignores_external_marker_copies_before_and_after_generated_comment(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - generated_body = f"{EVIDENCE_MARKER}\nCurrent lifecycle evidence.\n" - comments = [ - { - "body": f"{EVIDENCE_MARKER}\nExternal copy before.\n", - "id": 10, - "user": {"id": 100, "login": "external-before"}, - }, - { - "body": f"{EVIDENCE_MARKER}\nOutdated lifecycle evidence.\n", - "id": 11, - "user": {"id": 7, "login": "durable-workflow-ops"}, - }, - { - "body": f"{EVIDENCE_MARKER}\nExternal copy after.\n", - "id": 12, - "user": {"id": 101, "login": "external-after"}, - }, - ] - responses = [ - FakeResponse(json.dumps(comments).encode()), - FakeResponse(b'{"id":7,"login":"durable-workflow-ops"}'), - FakeResponse(b""), - ] - - with patch("urllib.request.urlopen", side_effect=responses) as urlopen: - client.upsert_lifecycle_comment( - "durable-workflow", - ".github", - 47, - EVIDENCE_MARKER, - generated_body, - ) - - comment_read, identity_read, update = [call.args[0] for call in urlopen.call_args_list] - self.assertEqual("Bearer job-token", comment_read.get_header("Authorization")) - self.assertEqual("Bearer writer-token", identity_read.get_header("Authorization")) - self.assertEqual("https://api.github.com/user", identity_read.full_url) - self.assertEqual("PATCH", update.method) - self.assertEqual("https://api.github.com/repos/durable-workflow/.github/issues/comments/11", update.full_url) - self.assertEqual("Bearer writer-token", update.get_header("Authorization")) - - def test_lifecycle_upsert_does_not_trust_matching_login_with_wrong_writer_id(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - generated_body = f"{EVIDENCE_MARKER}\nCurrent lifecycle evidence.\n" - comments = [ - { - "body": generated_body, - "id": 10, - "user": {"id": 8, "login": "durable-workflow-ops"}, - } - ] - responses = [ - FakeResponse(json.dumps(comments).encode()), - FakeResponse(b'{"id":7,"login":"durable-workflow-ops"}'), - FakeResponse(b'{"id":11}'), - ] - - with patch("urllib.request.urlopen", side_effect=responses) as urlopen: - client.upsert_lifecycle_comment( - "durable-workflow", - ".github", - 47, - EVIDENCE_MARKER, - generated_body, - ) - - create = urlopen.call_args_list[2].args[0] - self.assertEqual("POST", create.method) - self.assertEqual("https://api.github.com/repos/durable-workflow/.github/issues/47/comments", create.full_url) - - def test_lifecycle_upsert_rejects_multiple_generated_comments_from_authenticated_writer(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - comments = [ - { - "body": f"{EVIDENCE_MARKER}\nFirst generated comment.\n", - "id": 10, - "user": {"id": 7, "login": "durable-workflow-ops"}, - }, - { - "body": f"{EVIDENCE_MARKER}\nSecond generated comment.\n", - "id": 11, - "user": {"id": 7, "login": "durable-workflow-ops"}, - }, - ] - responses = [ - FakeResponse(json.dumps(comments).encode()), - FakeResponse(b'{"id":7,"login":"durable-workflow-ops"}'), - ] - - with ( - patch("urllib.request.urlopen", side_effect=responses) as urlopen, - self.assertRaisesRegex(AuthorityError, "duplicate cross-repository lifecycle evidence"), - ): - client.upsert_lifecycle_comment( - "durable-workflow", - ".github", - 47, - EVIDENCE_MARKER, - f"{EVIDENCE_MARKER}\nCurrent lifecycle evidence.\n", - ) - - self.assertEqual(2, urlopen.call_count) - - def test_trusted_comment_discovery_ignores_spoofed_writer_identity(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - comments = [ - { - "body": "Merge gate completion record.", - "id": 10, - "user": {"id": 100, "login": "external-contributor"}, - }, - { - "body": "Matching login with the wrong immutable identity.", - "id": 11, - "user": {"id": 8, "login": "durable-workflow-ops"}, - }, - { - "body": "Authenticated completion record.", - "id": 12, - "user": {"id": 7, "login": "durable-workflow-ops"}, - }, - ] - responses = [ - FakeResponse(json.dumps(comments).encode()), - FakeResponse(b'{"id":7,"login":"durable-workflow-ops"}'), - ] - - with patch("urllib.request.urlopen", side_effect=responses): - trusted = client.list_trusted_issue_comments("durable-workflow", ".github", 67) - - self.assertEqual([12], [comment["id"] for comment in trusted]) - - def test_spoofed_lifecycle_comment_cannot_manufacture_retirement(self) -> None: - client = GitHubApi("writer-token", read_token="job-token") - comments = [ - { - "body": ( - f"{PUBLIC_LIFECYCLE_MARKER}\n" - f"{PUBLIC_RETIREMENT_RECORD_MARKER}\n" - ), - "id": 10, - "user": {"id": 100, "login": "external-contributor"}, - } - ] - responses = [ - FakeResponse(json.dumps(comments).encode()), - FakeResponse(b'{"id":7,"login":"durable-workflow-ops"}'), - ] - - with patch("urllib.request.urlopen", side_effect=responses): - retired = _has_trusted_public_retirement( - client, - "durable-workflow", - ".github", - 87, - ) - - self.assertFalse(retired) - - def test_read_request_retries_transient_transport_failure(self) -> None: - client = GitHubApi("secret") - responses = [ - urllib.error.URLError(ConnectionResetError("connection reset")), - FakeResponse(b'{"state":"open"}'), - ] - - with ( - patch("urllib.request.urlopen", side_effect=responses) as urlopen, - patch("scripts.issue_authority.time.sleep") as sleep, - ): - result = client.request("GET", "/repos/durable-workflow/.github") - - self.assertEqual({"state": "open"}, result) - self.assertEqual(2, urlopen.call_count) - sleep.assert_called_once_with(2.0) - - -class MigrationTest(unittest.TestCase): - def setUp(self) -> None: - self.policy, self.backlog, _policy_schema, _backlog_schema = contract_fixture() - self.client = FakeGitHubApi(self.policy) - - def clear_mutation_spies(self) -> None: - self.client.label_updates.clear() - self.client.milestone_updates.clear() - self.client.created_issues.clear() - self.client.replacements.clear() - self.client.body_updates.clear() - self.client.state_updates.clear() - self.client.comment_updates.clear() - self.client.status_updates.clear() - self.client.mutation_events.clear() - - def seed_existing_reviewed_backlog(self, backlog: dict[str, Any] | None = None) -> None: - """Create a pre-existing reviewed inventory without exercising proactive routing.""" - - selected = backlog or self.backlog - dependency_urls: dict[str, str] = {} - dependency_titles = {item["id"]: item["title"] for item in selected["items"]} - for item in selected["items"]: - issue = self.client.create_issue( - self.policy["organization"], - item["repository"], - title=item["title"], - body=_render_body( - item, - {dependency: dependency_urls[dependency] for dependency in item["depends_on"]}, - dependency_titles, - ), - labels=_item_labels(item), - milestone=1, - ) - dependency_urls[item["id"]] = issue["html_url"] - self.client.created_issues.clear() - apply_backlog(self.policy, selected, self.client) - self.clear_mutation_spies() - - def assert_no_github_mutations(self) -> None: - self.assertEqual([], self.client.label_updates) - self.assertEqual([], self.client.milestone_updates) - self.assertEqual([], self.client.created_issues) - self.assertEqual([], self.client.replacements) - self.assertEqual([], self.client.body_updates) - self.assertEqual([], self.client.state_updates) - self.assertEqual([], self.client.comment_updates) - self.assertEqual([], self.client.status_updates) - - def test_apply_creates_at_most_one_reviewed_item_per_repository(self) -> None: - evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual(2, sum(len(issues) for issues in self.client.issues.values())) - expected_labels = {label["name"] for label in self.policy["labels"]} - for repository in self.policy["repositories"]: - self.assertEqual(expected_labels, set(self.client.labels[repository])) - for item in self.backlog["items"][:2]: - repository, issue = find_work_item(self.client, item["id"]) - self.assertEqual(item["repository"], repository) - self.assertEqual("open", issue["state"]) - self.assertEqual("2.0 beta", issue["milestone"]["title"]) - self.assertIn(OWNER_LABELS[repository], label_names(issue)) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(issue)) - if item["kind"] == "cross-repository": - targets = declared_targets( - issue["body"], - qualification_targets(qualification_fixture()), - organization=self.policy["organization"], - required=True, - ) - self.assertEqual( - sorted(item["required_source_targets"]), - sorted( - f"{self.policy['organization']}/{target['repository']}@{target['branch']}" for target in targets - ), - ) - - _release_repository, release = find_work_item(self.client, "release-plan-versioned-changelogs") - self.assertNotIn(UNBLOCK_CONTEXT_START, release["body"]) - self.assertNotIn("## Unblock condition", release["body"]) - self.assertEqual({"status:ready"}, label_names(release) & STATUS_LABELS) - self.assertEqual( - "retained-private-audit", - evidence["issues"]["github-only-beta-continuity-drill"]["action"], - ) - self.assertEqual( - "retained-private-audit", - evidence["issues"]["authorize-2-0-beta"]["action"], - ) - - def test_apply_suppresses_growth_at_one_and_consolidates_into_the_applicable_root(self) -> None: - now = datetime(2026, 8, 26, 12, 0, tzinfo=UTC) - existing = [ - { - "body": "Existing compatible cross-repository root.", - "created_at": (now - timedelta(days=2)).isoformat(), - "html_url": "https://github.com/durable-workflow/.github/issues/40", - "labels": [ - {"name": "authority:github"}, - {"name": "beta:compatible"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P2"}, - {"name": "repo:github-control-plane"}, - {"name": "status:ready"}, - ], - "milestone": {"number": 1, "title": "2.0 beta"}, - "number": 40, - "state": "open", - "title": "Existing root", - }, - ] - self.client.issues[".github"].extend(existing) - - evidence = apply_backlog( - self.policy, - self.backlog, - self.client, - audit_time=now, - ) - - consolidated = evidence["issues"]["release-plan-versioned-changelogs"] - self.assertEqual("consolidated", consolidated["action"]) - self.assertEqual("https://github.com/durable-workflow/.github/issues/40", consolidated["url"]) - self.assertIn( - "", - existing[0]["body"], - ) - self.assertIn("Prepare versioned changelogs without guessing release tags", existing[0]["body"]) - self.assertEqual( - "retained-private-audit", - evidence["issues"]["github-only-beta-continuity-drill"]["action"], - ) - self.assertEqual( - "retained-private-audit", - evidence["issues"]["authorize-2-0-beta"]["action"], - ) - self.assertEqual( - ["durable-workflow.github.io"], - [repository for repository, _number in self.client.created_issues], - ) - - repeated = audit_backlog( - self.policy, - self.backlog, - self.client, - audit_time=now + timedelta(minutes=10), - ) - self.assertEqual("pass", repeated["outcome"]) - self.assertEqual( - "retained-private-audit", - repeated["issues"]["authorize-2-0-beta"]["action"], - ) - - def test_apply_advances_the_existing_dependency_free_issue_without_duplication(self) -> None: - blocked_backlog = copy.deepcopy(self.backlog) - blocked_item = mark_release_item_blocked(blocked_backlog) - apply_backlog(self.policy, blocked_backlog, self.client) - repository, issue = find_work_item(self.client, blocked_item["id"]) - issue["body"] = issue["body"].replace("## Problem", "Maintainer context.\n\n## Problem") - blocker_context = ( - f"{UNBLOCK_CONTEXT_START}\n## Unblock condition\n\n{REVIEWED_BLOCK_CONDITION}\n{UNBLOCK_CONTEXT_END}" - ) - expected_body = issue["body"].replace(f"\n\n{blocker_context}", "") - issue_number = issue["number"] - - evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("transitioned-to-ready", evidence["issues"][blocked_item["id"]]["action"]) - self.assertEqual(issue_number, issue["number"]) - self.assertEqual(2, sum(len(issues) for issues in self.client.issues.values())) - self.assertEqual({"status:ready"}, label_names(issue) & STATUS_LABELS) - self.assertEqual(expected_body, issue["body"]) - self.assertNotIn(UNBLOCK_CONTEXT_START, issue["body"]) - self.assertIn("## Dependencies\n\nNone.", issue["body"]) - self.assertIn((repository, issue_number, sorted(label_names(issue))), self.client.replacements) - - replacement_count = len(self.client.replacements) - body_update_count = len(self.client.body_updates) - replay_evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("preserved", replay_evidence["issues"][blocked_item["id"]]["action"]) - self.assertEqual(replacement_count, len(self.client.replacements)) - self.assertEqual(body_update_count, len(self.client.body_updates)) - self.assertEqual(2, sum(len(issues) for issues in self.client.issues.values())) - - def test_replay_restores_machine_owned_unblock_context_without_losing_edits(self) -> None: - mark_release_item_blocked(self.backlog) - apply_backlog(self.policy, self.backlog, self.client) - repository, issue = find_work_item(self.client, "release-plan-versioned-changelogs") - replace_unblock_context(issue, "") - issue["body"] = issue["body"].replace("## Problem", "Maintainer context.\n\n## Problem") - issue["state"] = "closed" - issue["labels"] = [ - {"name": "status:done" if label["name"] in STATUS_LABELS else label["name"]} for label in issue["labels"] - ] - issue["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - expected_labels = copy.deepcopy(issue["labels"]) - - evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("updated-blocker-context", evidence["issues"]["release-plan-versioned-changelogs"]["action"]) - self.assertIn("Maintainer context.", issue["body"]) - self.assertIn(self.backlog["items"][0]["unblock_condition"], issue["body"]) - self.assertEqual("closed", issue["state"]) - self.assertEqual(expected_labels, issue["labels"]) - self.assertEqual(1, len(self.client.body_updates)) - - replay_evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("preserved", replay_evidence["issues"]["release-plan-versioned-changelogs"]["action"]) - self.assertEqual(1, len(self.client.body_updates)) - - def test_replay_replaces_one_valid_unblock_context_without_losing_edits(self) -> None: - mark_release_item_blocked(self.backlog) - apply_backlog(self.policy, self.backlog, self.client) - _repository, issue = find_work_item(self.client, "release-plan-versioned-changelogs") - stale_context = ( - f"{UNBLOCK_CONTEXT_START}\n## Unblock condition\n\nStale reviewed condition.\n{UNBLOCK_CONTEXT_END}" - ) - replace_unblock_context(issue, stale_context) - issue["body"] = issue["body"].replace("## Problem", "Maintainer context.\n\n## Problem") - issue["state"] = "closed" - issue["labels"] = [ - {"name": "status:done" if label["name"] in STATUS_LABELS else label["name"]} for label in issue["labels"] - ] - issue["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - expected_labels = copy.deepcopy(issue["labels"]) - - evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("updated-blocker-context", evidence["issues"]["release-plan-versioned-changelogs"]["action"]) - self.assertIn("Maintainer context.", issue["body"]) - self.assertNotIn("Stale reviewed condition.", issue["body"]) - self.assertIn(self.backlog["items"][0]["unblock_condition"], issue["body"]) - self.assertEqual("closed", issue["state"]) - self.assertEqual(expected_labels, issue["labels"]) - self.assertEqual(1, len(self.client.body_updates)) - - replay_evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("preserved", replay_evidence["issues"]["release-plan-versioned-changelogs"]["action"]) - self.assertEqual(1, len(self.client.body_updates)) - - def test_apply_rejects_malformed_unblock_context_before_issue_mutation(self) -> None: - mark_release_item_blocked(self.backlog) - valid_context = f"{UNBLOCK_CONTEXT_START}\n## Unblock condition\n\nReviewed condition.\n{UNBLOCK_CONTEXT_END}" - malformed_contexts = { - "reversed": f"{UNBLOCK_CONTEXT_END}\nReviewed condition.\n{UNBLOCK_CONTEXT_START}", - "repeated": f"{valid_context}\n\n{valid_context}", - "nested": ( - f"{UNBLOCK_CONTEXT_START}\n{UNBLOCK_CONTEXT_START}\nReviewed condition.\n" - f"{UNBLOCK_CONTEXT_END}\n{UNBLOCK_CONTEXT_END}" - ), - "start-only": f"{UNBLOCK_CONTEXT_START}\nReviewed condition.", - "end-only": f"Reviewed condition.\n{UNBLOCK_CONTEXT_END}", - "inline-start": f"prefix {UNBLOCK_CONTEXT_START}\nReviewed condition.\n{UNBLOCK_CONTEXT_END}", - "inline-end": f"{UNBLOCK_CONTEXT_START}\nReviewed condition.\n{UNBLOCK_CONTEXT_END} suffix", - "same-line": f"{UNBLOCK_CONTEXT_START} Reviewed condition. {UNBLOCK_CONTEXT_END}", - } - - for name, malformed_context in malformed_contexts.items(): - with self.subTest(name=name): - client = FakeGitHubApi(self.policy) - apply_backlog(self.policy, self.backlog, client) - _repository, issue = find_work_item(client, "release-plan-versioned-changelogs") - replace_unblock_context(issue, malformed_context) - client.body_updates.clear() - client.replacements.clear() - issues_before = copy.deepcopy(client.issues) - - with self.assertRaisesRegex(AuthorityError, "malformed unblock condition context"): - apply_backlog(self.policy, self.backlog, client) - - self.assertEqual(issues_before, client.issues) - self.assertEqual([], client.body_updates) - self.assertEqual([], client.replacements) - - def test_audit_rejects_malformed_unblock_context_before_issue_mutation(self) -> None: - mark_release_item_blocked(self.backlog) - apply_backlog(self.policy, self.backlog, self.client) - _repository, issue = find_work_item(self.client, "release-plan-versioned-changelogs") - replace_unblock_context( - issue, - f"{UNBLOCK_CONTEXT_END}\nReviewed condition.\n{UNBLOCK_CONTEXT_START}", - ) - self.client.body_updates.clear() - self.client.replacements.clear() - issues_before = copy.deepcopy(self.client.issues) - - with self.assertRaisesRegex(AuthorityError, "malformed unblock condition context"): - audit_backlog(self.policy, self.backlog, self.client) - - self.assertEqual(issues_before, self.client.issues) - self.assertEqual([], self.client.body_updates) - self.assertEqual([], self.client.replacements) - - def test_replay_preserves_github_edits_and_closed_state(self) -> None: - self.seed_existing_reviewed_backlog() - repository, issue = find_work_item(self.client, "github-only-beta-continuity-drill") - issue["title"] = "Maintainer refined title" - issue["body"] = issue["body"].replace("## Scope", "## Maintainer rationale\n\nDurable decision.\n\n## Scope") - issue["state"] = "closed" - issue["labels"] = [ - {"name": "status:done" if label["name"] in STATUS_LABELS else label["name"]} - for label in issue["labels"] - if label["name"] not in STATUS_LABELS or label["name"] == "status:ready" - ] - issue["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - - evidence = apply_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("preserved", evidence["issues"]["github-only-beta-continuity-drill"]["action"]) - self.assertEqual("closed", evidence["issues"]["github-only-beta-continuity-drill"]["state"]) - self.assertEqual("Maintainer refined title", issue["title"]) - self.assertIn("Durable decision.", issue["body"]) - self.assertEqual(4, sum(len(issues) for issues in self.client.issues.values())) - self.assertEqual(repository, ".github") - - def test_duplicate_marker_is_labeled_and_fails_before_creation(self) -> None: - apply_backlog(self.policy, self.backlog, self.client) - repository, issue = find_work_item(self.client, "release-plan-versioned-changelogs") - duplicate = copy.deepcopy(issue) - duplicate["number"] = 99 - duplicate["html_url"] = f"https://github.com/durable-workflow/{repository}/issues/99" - self.client.issues[repository].append(duplicate) - - with self.assertRaisesRegex(AuthorityError, "appears on 2 GitHub issues"): - apply_backlog(self.policy, self.backlog, self.client) - - self.assertIn("authority:conflict", label_names(issue)) - self.assertIn("authority:conflict", label_names(duplicate)) - self.assertEqual(3, sum(len(issues) for issues in self.client.issues.values())) - - def test_apply_rejects_distinct_markers_before_any_github_mutation(self) -> None: - self.seed_existing_reviewed_backlog() - repository, issue = find_work_item(self.client, "release-plan-versioned-changelogs") - alias_repository, alias = find_work_item(self.client, "authorize-2-0-beta") - self.assertEqual(repository, alias_repository) - self.client.issues[alias_repository].remove(alias) - issue["body"] += "\n\n" - self.backlog["items"][3]["unblock_condition"] = "A separate reviewed condition must remain independent." - self.client.labels[repository]["authority:github"]["description"] = "Stale label definition" - self.client.milestones[repository]["2.0 beta"]["description"] = "Stale milestone definition" - self.clear_mutation_spies() - issues_before = copy.deepcopy(self.client.issues) - labels_before = copy.deepcopy(self.client.labels) - milestones_before = copy.deepcopy(self.client.milestones) - - with self.assertRaises(AuthorityError) as raised: - apply_backlog(self.policy, self.backlog, self.client) - - message = str(raised.exception) - self.assertIn(f"{repository}#{issue['number']}", message) - self.assertIn("release-plan-versioned-changelogs", message) - self.assertIn("authorize-2-0-beta", message) - self.assertEqual(issues_before, self.client.issues) - self.assertEqual(labels_before, self.client.labels) - self.assertEqual(milestones_before, self.client.milestones) - self.assert_no_github_mutations() - - def test_audit_rejects_distinct_markers_before_any_github_mutation(self) -> None: - self.seed_existing_reviewed_backlog() - repository, issue = find_work_item(self.client, "release-plan-versioned-changelogs") - alias_repository, alias = find_work_item(self.client, "github-only-beta-continuity-drill") - self.assertEqual(repository, alias_repository) - self.client.issues[alias_repository].remove(alias) - issue["body"] += "\n\n" - self.client.labels[repository]["authority:github"]["description"] = "Stale label definition" - self.client.milestones[repository]["2.0 beta"]["description"] = "Stale milestone definition" - self.clear_mutation_spies() - issues_before = copy.deepcopy(self.client.issues) - labels_before = copy.deepcopy(self.client.labels) - milestones_before = copy.deepcopy(self.client.milestones) - - with self.assertRaises(AuthorityError) as raised: - audit_backlog(self.policy, self.backlog, self.client) - - message = str(raised.exception) - self.assertIn(f"{repository}#{issue['number']}", message) - self.assertIn("release-plan-versioned-changelogs", message) - self.assertIn("github-only-beta-continuity-drill", message) - self.assertEqual(issues_before, self.client.issues) - self.assertEqual(labels_before, self.client.labels) - self.assertEqual(milestones_before, self.client.milestones) - self.assert_no_github_mutations() - - def test_audit_fails_when_a_selected_issue_is_missing(self) -> None: - apply_backlog(self.policy, self.backlog, self.client) - repository, issue = find_work_item(self.client, "docs-php-conformance-public-authority") - self.client.issues[repository].remove(issue) - - with self.assertRaisesRegex(AuthorityError, "has no GitHub issue"): - audit_backlog(self.policy, self.backlog, self.client) - - def test_explicit_unverified_completion_hold_is_reopened_and_fails_visibly(self) -> None: - self.seed_existing_reviewed_backlog() - repository, issue = find_work_item(self.client, "github-only-beta-continuity-drill") - issue["labels"].append({"name": COMPLETION_REQUIRED_LABEL}) - issue["state"] = "closed" - issue["state_reason"] = "completed" - self.client.comments[(repository, issue["number"])] = ( - f"{PUBLIC_LIFECYCLE_MARKER}\nManaged lifecycle state.\n" - ) - - with self.assertRaisesRegex( - AuthorityError, - "closed before its required public completion evidence was verified", - ): - audit_backlog(self.policy, self.backlog, self.client) - - self.assertEqual({"status:ready"}, label_names(issue) & STATUS_LABELS) - self.assertEqual("open", issue["state"]) - self.assertEqual("reopened", issue["state_reason"]) - self.assertIn((repository, issue["number"], "open", "reopened"), self.client.state_updates) - - def test_trusted_retirement_precedes_incomplete_historical_targets(self) -> None: - retired = prerelease_authority_issue(59, status=SUPERSEDED_STATUS_LABEL) - retired["state"] = "closed" - retired["state_reason"] = "not_planned" - retired["labels"] = [ - {"name": "kind:cross-repository" if label["name"] == "kind:release-blocker" else label["name"]} - for label in retired["labels"] - ] - neighboring = prerelease_authority_issue(60, status=SUPERSEDED_STATUS_LABEL) - neighboring["state"] = "closed" - neighboring["state_reason"] = "not_planned" - neighboring["labels"] = [ - {"name": "kind:cross-repository" if label["name"] == "kind:release-blocker" else label["name"]} - for label in neighboring["labels"] - ] - self.client.issues[".github"].extend((retired, neighboring)) - self.client.trusted_comments[(".github", 59)] = [ - { - "body": f"{PUBLIC_LIFECYCLE_MARKER}\n{PUBLIC_RETIREMENT_RECORD_MARKER}\n", - "id": 59, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ] - self.client.trusted_comments[(".github", 60)] = [ - { - "body": f"{PUBLIC_LIFECYCLE_MARKER}\nManaged non-retirement lifecycle.\n", - "id": 60, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ] - target = qualification_targets(qualification_fixture())[".github"] - landing = historical_landing_fixture(".github")[0] - - failures = _audit_state_labels( - self.policy, - self.client, - self.client.issues, - set(), - {(".github", 59): [target]}, - {(".github", 59): [landing]}, - None, - None, - ) - - self.assertEqual("closed", retired["state"]) - self.assertEqual("not_planned", retired["state_reason"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(retired) & STATUS_LABELS) - self.assertNotIn((".github", 59, "open", "reopened"), self.client.state_updates) - self.assertEqual([], self.client.reachability_requests) - self.assertEqual("open", neighboring["state"]) - self.assertEqual({"status:triage"}, label_names(neighboring) & STATUS_LABELS) - self.assertIn((".github", 60, "open", "reopened"), self.client.state_updates) - self.assertTrue(any(".github#60" in failure for failure in failures)) - - def test_trusted_retirement_repairs_issue_event_drift_idempotently(self) -> None: - issue = prerelease_authority_issue(59, status=SUPERSEDED_STATUS_LABEL) - issue["state"] = "closed" - issue["state_reason"] = "not_planned" - self.client.issues[".github"].append(issue) - self.client.trusted_comments[(".github", 59)] = [ - { - "body": f"{PUBLIC_LIFECYCLE_MARKER}\n{PUBLIC_RETIREMENT_RECORD_MARKER}\n", - "id": 59, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ] - - for event, state, status in ( - ("closed", "closed", "status:done"), - ("labeled", "closed", "status:ready"), - ("unlabeled", "closed", None), - ("edited", "open", "status:triage"), - ("reopened", "open", "status:ready"), - ("scheduled", "open", "status:blocked"), - ("workflow_dispatch", "open", None), - ): - with self.subTest(event=event): - issue["state"] = state - issue["state_reason"] = "completed" if state == "closed" else "reopened" - issue["labels"] = [label for label in issue["labels"] if label["name"] not in STATUS_LABELS] - if status is not None: - issue["labels"].append({"name": status}) - self.clear_mutation_spies() - - failures = _audit_state_labels( - self.policy, - self.client, - self.client.issues, - set(), - None, - None, - None, - None, - ) - - self.assertEqual([], failures) - self.assertEqual("closed", issue["state"]) - self.assertEqual("not_planned", issue["state_reason"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(issue) & STATUS_LABELS) - - self.clear_mutation_spies() - self.assertEqual( - [], - _audit_state_labels( - self.policy, - self.client, - self.client.issues, - set(), - None, - None, - None, - None, - ), - ) - self.assert_no_github_mutations() - - def test_valid_prerelease_supersession_is_terminal_without_false_completion(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - retired = prerelease_authority_issue(59) - retired["labels"] = [ - {"name": "kind:cross-repository" if label["name"] == "kind:release-blocker" else label["name"]} - for label in retired["labels"] - ] - successor = prerelease_authority_issue(61, body="Coherent synchronized replacement train") - retired["state"] = "closed" - retired["state_reason"] = "completed" - self.client.issues[".github"].extend((retired, successor)) - targets = qualification_targets(qualification_fixture()) - completion = pipeline_completion_fixture() - documentation_line = next( - line for line in completion["body"].splitlines() if line.startswith("- `durable-workflow.github.io:main`") - ) - completion["body"] = completion["body"].replace(documentation_line + "\n", "") - completion["targets"] = [ - record for record in completion["targets"] if record["repository"] in {".github", "workflow"} - ] - install_pipeline_completion_evidence(self.client, completion, targets, issue_number=59) - successor_revision = issue_revision_digest(successor["title"], successor["body"]) - supersessions = { - (".github", 59): { - "activation": { - "commit": "a" * 40, - "context": "issue-authority/prerelease-supersession/.github/59", - "digest": "b" * 64, - }, - "number": 61, - "reason": "An immutable artifact mismatch requires the replacement train.", - "repository": ".github", - "revision": successor_revision, - } - } - self.client.comments[(".github", 59)] = ( - f"{SUPERSESSION_EVIDENCE_MARKER}\nManaged supersession lifecycle.\n" - ) - self.clear_mutation_spies() - - evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - approved_completion_holds={(".github", 59), (".github", 61)}, - cross_repository_targets={ - (".github", 59): [targets[".github"], targets["workflow"]], - }, - prerelease_supersessions=supersessions, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", retired["state"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(retired) & STATUS_LABELS) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(retired)) - self.assertNotIn(COMPLETION_VERIFIED_LABEL, label_names(retired)) - self.assertNotIn("status:done", label_names(retired)) - self.assertEqual("open", successor["state"]) - self.assertEqual({"status:ready"}, label_names(successor) & STATUS_LABELS) - self.assertEqual([], self.client.timeline_reads) - self.assertEqual([], self.client.workflow_run_requests) - self.assertEqual("not_planned", retired["state_reason"]) - self.assertIn((".github", 59, "closed", "not_planned"), self.client.state_updates) - comment = self.client.comments[(".github", 59)] - self.assertIn(SUPERSESSION_EVIDENCE_MARKER, comment) - self.assertIn("https://github.com/durable-workflow/.github/issues/61", comment) - self.assertEqual("superseded", evidence["prerelease_supersessions"][0]["state"]) - self.assertEqual(successor_revision, evidence["prerelease_supersessions"][0]["successor"]["revision"]) - - self.clear_mutation_spies() - repeated = audit_backlog( - self.policy, - self.backlog, - self.client, - approved_completion_holds={(".github", 59), (".github", 61)}, - cross_repository_targets={ - (".github", 59): [targets[".github"], targets["workflow"]], - }, - prerelease_supersessions=supersessions, - ) - - self.assertEqual("pass", repeated["outcome"]) - self.assertEqual("closed", retired["state"]) - self.assertEqual("not_planned", retired["state_reason"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(retired) & STATUS_LABELS) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(retired)) - self.assertNotIn(COMPLETION_VERIFIED_LABEL, label_names(retired)) - self.assertEqual([], self.client.timeline_reads) - self.assert_no_github_mutations() - - def test_obsolete_beta_authorities_retire_idempotently_on_ordinary_issue_events(self) -> None: - policy, backlog, _policy_schema, _backlog_schema = immutable_retirement_contract() - for item in backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - client = FakeGitHubApi(policy) - apply_backlog(policy, backlog, client) - retired = [prerelease_authority_issue(number, status="status:triage") for number in (61, 65)] - for issue in retired: - issue["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - client.issues[".github"].extend(retired) - issues = {".github": [(issue, []) for issue in retired]} - manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery( - policy, - issues, - public_files=immutable_product_train_files(policy), - ), - trigger_repository=".github", - trigger_number=65, - trigger_action="labeled", - trigger_actor="github-actions[bot]", - trigger_label=COMPLETION_VERIFIED_LABEL, - ) - supersessions = { - (record["repository"], record["number"]): record["superseded_by"] for record in manifest["issues"] - } - self.assertTrue(manifest["trigger"]["approved"]) - for changes in ( - client.label_updates, - client.milestone_updates, - client.created_issues, - client.replacements, - client.body_updates, - client.state_updates, - client.comment_updates, - client.status_updates, - ): - changes.clear() - - evidence = audit_backlog( - policy, - backlog, - client, - approved_completion_holds={(".github", 61), (".github", 65)}, - prerelease_supersessions=supersessions, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual([61, 65], [item["retired"]["number"] for item in evidence["prerelease_supersessions"]]) - self.assertEqual(2, len(client.status_updates)) - for issue in retired: - self.assertEqual("closed", issue["state"]) - self.assertEqual("not_planned", issue["state_reason"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(issue) & STATUS_LABELS) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(issue)) - self.assertNotIn(COMPLETION_VERIFIED_LABEL, label_names(issue)) - self.assertIn("immutable product train", client.comments[(".github", issue["number"])]) - self.assertEqual([], client.timeline_reads) - - completed_manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery( - policy, - {".github": [(issue, []) for issue in retired]}, - commit_statuses=client.commit_statuses, - public_files=immutable_product_train_files(policy), - ), - ) - completed_supersessions = { - (record["repository"], record["number"]): record["superseded_by"] for record in completed_manifest["issues"] - } - self.assertTrue(all(record["completion_evidence_required"] is False for record in completed_manifest["issues"])) - for changes in ( - client.label_updates, - client.milestone_updates, - client.created_issues, - client.replacements, - client.body_updates, - client.state_updates, - client.comment_updates, - client.status_updates, - ): - changes.clear() - - repeated = audit_backlog( - policy, - backlog, - client, - approved_completion_holds={(".github", 61), (".github", 65)}, - prerelease_supersessions=completed_supersessions, - ) - - self.assertEqual("pass", repeated["outcome"]) - self.assertEqual([], client.label_updates) - self.assertEqual([], client.milestone_updates) - self.assertEqual([], client.created_issues) - self.assertEqual([], client.replacements) - self.assertEqual([], client.body_updates) - self.assertEqual([], client.state_updates) - self.assertEqual([], client.comment_updates) - self.assertEqual([], client.status_updates) - - def test_successor_completion_survives_repeated_discovery_apply_and_audit(self) -> None: - policy, backlog, _policy_schema, _backlog_schema = contract_fixture(include_supersessions=True) - for item in backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - client = FakeGitHubApi(policy) - apply_backlog(policy, backlog, client) - retired = prerelease_authority_issue(59) - successor = prerelease_authority_issue(61, body="Coherent synchronized replacement train") - client.issues[".github"].extend((retired, successor)) - - active_manifest, _inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, {".github": [(retired, []), (successor, [])]}), - ) - active_record = next(record for record in active_manifest["issues"] if record["number"] == 59) - active_supersessions = {(".github", 59): active_record["superseded_by"]} - audit_backlog( - policy, - backlog, - client, - approved_completion_holds={(".github", 59), (".github", 61)}, - prerelease_supersessions=active_supersessions, - ) - - activation = active_record["superseded_by"]["activation"] - self.assertEqual( - [(".github", activation["commit"], activation["digest"])], - client.status_updates, - ) - successor["state"] = "closed" - successor["state_reason"] = "completed" - successor["labels"] = [ - {"name": "status:done" if label["name"] in STATUS_LABELS else label["name"]} - for label in successor["labels"] - ] - successor["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - completed_issues = {".github": [(retired, []), (successor, [])]} - - first_manifest, first_inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, completed_issues, commit_statuses=client.commit_statuses), - ) - second_manifest, _second_inventory = reconstruct_intake( - policy, - FakeDiscovery(policy, completed_issues, commit_statuses=client.commit_statuses), - ) - verified_inventory = verify_intake_manifest( - policy, - first_manifest, - FakeDiscovery(policy, completed_issues, commit_statuses=client.commit_statuses), - ) - - self.assertEqual(first_manifest, second_manifest) - self.assertEqual(first_inventory, verified_inventory) - completed_record = next(record for record in first_manifest["issues"] if record["number"] == 59) - completed_supersessions = {(".github", 59): completed_record["superseded_by"]} - client.label_updates.clear() - client.milestone_updates.clear() - client.created_issues.clear() - client.replacements.clear() - client.body_updates.clear() - client.state_updates.clear() - client.comment_updates.clear() - client.status_updates.clear() - - apply_backlog( - policy, - backlog, - client, - approved_completion_holds={(".github", 59), (".github", 61)}, - prerelease_supersessions=completed_supersessions, - ) - audit_backlog( - policy, - backlog, - client, - approved_completion_holds={(".github", 59), (".github", 61)}, - prerelease_supersessions=completed_supersessions, - ) - - self.assertEqual("closed", retired["state"]) - self.assertEqual("not_planned", retired["state_reason"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(retired) & STATUS_LABELS) - self.assertNotIn("status:done", label_names(retired)) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(retired)) - self.assertNotIn(COMPLETION_VERIFIED_LABEL, label_names(retired)) - self.assertEqual([], client.state_updates) - self.assertEqual([], client.replacements) - self.assertEqual([], client.comment_updates) - self.assertEqual([], client.status_updates) - - def test_verified_public_completion_evidence_allows_closed_state_to_win(self) -> None: - self.seed_existing_reviewed_backlog() - _repository, issue = find_work_item(self.client, "github-only-beta-continuity-drill") - issue["state"] = "closed" - issue["state_reason"] = "completed" - issue["labels"] = [ - {"name": "status:done" if label["name"] in STATUS_LABELS else label["name"]} for label in issue["labels"] - ] - issue["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - self.clear_mutation_spies() - - evidence = audit_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", evidence["issues"]["github-only-beta-continuity-drill"]["state"]) - self.assert_no_github_mutations() - - def test_completion_shaped_prose_does_not_create_an_evidence_hold(self) -> None: - self.seed_existing_reviewed_backlog() - _repository, issue = find_work_item(self.client, "github-only-beta-continuity-drill") - issue["body"] = "## Completion\n\n## Delete when\n\n## Acceptance\n\n" + issue["body"] - issue["state"] = "closed" - issue["state_reason"] = "completed" - issue["labels"] = [ - {"name": "status:done" if label["name"] in STATUS_LABELS else label["name"]} for label in issue["labels"] - ] - self.clear_mutation_spies() - - evidence = audit_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", evidence["issues"]["github-only-beta-continuity-drill"]["state"]) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(issue)) - self.assert_no_github_mutations() - - def test_removed_completion_hold_is_not_readded_by_default(self) -> None: - self.seed_existing_reviewed_backlog() - _repository, issue = find_work_item(self.client, "github-only-beta-continuity-drill") - issue["labels"].append({"name": COMPLETION_REQUIRED_LABEL}) - issue["labels"] = [label for label in issue["labels"] if label["name"] != COMPLETION_REQUIRED_LABEL] - self.clear_mutation_spies() - - evidence = audit_backlog(self.policy, self.backlog, self.client) - - self.assertEqual("pass", evidence["outcome"]) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(issue)) - self.assert_no_github_mutations() - - def test_approved_intake_completion_hold_is_restored(self) -> None: - self.seed_existing_reviewed_backlog() - repository, issue = find_work_item(self.client, "github-only-beta-continuity-drill") - self.clear_mutation_spies() - - evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - approved_completion_holds={(repository, issue["number"])}, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertIn(COMPLETION_REQUIRED_LABEL, label_names(issue)) - self.assertIn((repository, issue["number"], sorted(label_names(issue))), self.client.replacements) - self.assertEqual([], self.client.state_updates) - - def test_frozen_documentation_apply_revalidates_exact_runs_and_is_terminal_on_replay(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - migration = next( - record - for record in legacy_target_fixture()["frozen_lifecycle_migrations"] - if (record["repository"], record["number"]) == ("durable-workflow.github.io", 63) - ) - frozen = {**copy.deepcopy(migration), "approved_issue_revision_sha256": "a" * 64} - qualified = qualification_targets(qualification_fixture()) - selected = [ - copy.deepcopy(qualified[target.split("/", 1)[1].rsplit("@", 1)[0]]) - for target in migration["declared_targets"] - ] - for target in selected: - target["required_checks"] = ["Renamed after the frozen migration"] - target["required_workflows"] = [ - { - "path": "renamed-after-migration.yml", - "required_check": "Renamed after the frozen migration", - } - ] - issue = { - "body": "A plain completion sentence is not authority.", - "html_url": "https://github.com/durable-workflow/durable-workflow.github.io/issues/63", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P2"}, - {"name": "status:triage"}, - ], - "milestone": None, - "number": 63, - "state": "open", - "state_reason": None, - "title": "Archived documentation authority", - } - self.client.issues["durable-workflow.github.io"].append(issue) - self.client.trusted_comments[("durable-workflow.github.io", 63)] = [ - { - "body": "Completed in plain prose.", - "id": 9, - "user": {"id": 99, "login": "external-contributor"}, - } - ] - for landing in migration["landings"]: - repository = landing["repository"] - commit = landing["commit"] - qualification = landing["qualification"] - self.client.reachable.add((repository, commit, landing["branch"])) - self.client.successful_workflow_runs.add( - ( - repository, - qualification["run"], - commit, - qualification["workflow_path"], - qualification["workflow_name"], - ) - ) - self.client.successful_workflow_jobs[(repository, qualification["run"])] = { - check["name"]: check["job"] for check in qualification["checks"] - } - declared = {("durable-workflow.github.io", 63): selected} - frozen_records = {("durable-workflow.github.io", 63): frozen} - self.clear_mutation_spies() - - first = apply_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - frozen_cross_repository_lifecycles=frozen_records, - ) - - self.assertEqual("pass", first["outcome"]) - self.assertEqual("closed", issue["state"]) - self.assertEqual({"status:done"}, label_names(issue) & STATUS_LABELS) - self.assertIn(COMPLETION_VERIFIED_LABEL, label_names(issue)) - self.assertIn(FROZEN_LIFECYCLE_EVIDENCE_MARKER, self.client.comments[("durable-workflow.github.io", 63)]) - self.assertIn('"outcome": "complete"', self.client.comments[("durable-workflow.github.io", 63)]) - self.assertIn("30589737606", self.client.comments[("durable-workflow.github.io", 63)]) - self.clear_mutation_spies() - - second = apply_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - frozen_cross_repository_lifecycles=frozen_records, - ) - - self.assertEqual("pass", second["outcome"]) - self.assert_no_github_mutations() - - def test_frozen_waterline_apply_reopens_once_and_plain_prose_cannot_forge_it(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - migration = next( - record - for record in legacy_target_fixture()["frozen_lifecycle_migrations"] - if (record["repository"], record["number"]) == ("waterline", 79) - ) - frozen = {**copy.deepcopy(migration), "approved_issue_revision_sha256": "b" * 64} - qualified = qualification_targets(qualification_fixture()) - selected = [ - copy.deepcopy(qualified[target.split("/", 1)[1].rsplit("@", 1)[0]]) - for target in migration["declared_targets"] - ] - issue = { - "body": "Completed after the release and peer qualification passed.", - "html_url": "https://github.com/durable-workflow/waterline/issues/79", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P2"}, - {"name": "status:done"}, - ], - "milestone": None, - "number": 79, - "state": "closed", - "state_reason": "completed", - "title": "Archived Waterline authority", - } - self.client.issues["waterline"].append(issue) - self.client.trusted_comments[("waterline", 79)] = [ - { - "body": "Completed after the release and peer qualification passed.", - "id": 10, - "user": {"id": 99, "login": "external-contributor"}, - } - ] - declared = {("waterline", 79): selected} - frozen_records = {("waterline", 79): frozen} - self.client.comments[("waterline", 79)] = ( - f"{FROZEN_LIFECYCLE_EVIDENCE_MARKER}\nManaged frozen lifecycle.\n" - ) - self.clear_mutation_spies() - - first = apply_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - frozen_cross_repository_lifecycles=frozen_records, - ) - - self.assertEqual("pass", first["outcome"]) - self.assertEqual("open", issue["state"]) - self.assertEqual({"status:triage"}, label_names(issue) & STATUS_LABELS) - self.assertIn(COMPLETION_REQUIRED_LABEL, label_names(issue)) - evidence = self.client.comments[("waterline", 79)] - self.assertIn(FROZEN_LIFECYCLE_EVIDENCE_MARKER, evidence) - self.assertIn('"outcome": "missing-evidence"', evidence) - self.assertIn("responsive opened-dialog qualification", evidence) - self.clear_mutation_spies() - - second = apply_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - frozen_cross_repository_lifecycles=frozen_records, - ) - - self.assertEqual("pass", second["outcome"]) - self.assert_no_github_mutations() - - def test_corrected_apply_converges_all_mutated_archived_states(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - self.client.issues[".github"] = [ - { - "body": "Unrelated public issue.", - "html_url": f"https://github.com/durable-workflow/.github/issues/{number}", - "labels": [], - "milestone": None, - "number": number, - "state": "open", - "title": "Unrelated public issue", - } - for number in range(1, 61) - ] - apply_backlog(self.policy, self.backlog, self.client) - migration = legacy_target_fixture() - qualified = qualification_targets(qualification_fixture()) - target_sets: dict[tuple[str, int], list[dict[str, Any]]] = {} - completion_sets: dict[tuple[str, int], list[dict[str, Any]]] = {} - parents: list[dict[str, Any]] = [] - for completion in migration["historical_completions"]: - identity = (completion["repository"], completion["number"]) - repositories = [target.split("/", 1)[1].rsplit("@", 1)[0] for target in completion["targets"]] - landings = historical_landing_fixture(*repositories) - target_sets[identity] = [dict(qualified[repository]) for repository in repositories] - completion_sets[identity] = landings - parent = { - "body": "Archived cross-repository authority.", - "html_url": ( - f"https://github.com/durable-workflow/{completion['repository']}/issues/{completion['number']}" - ), - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:triage"}, - ], - "milestone": None, - "number": completion["number"], - "state": "open", - "title": "Archived public work", - } - placeholder = next( - issue - for issue in self.client.issues[completion["repository"]] - if issue["number"] == completion["number"] - ) - placeholder.clear() - placeholder.update(parent) - parents.append(placeholder) - self.client.comments[identity] = ( - f"{EVIDENCE_MARKER}\n" - "Cross-repository landing evidence:\n\n" - "The parent remains open until every declared target is complete.\n" - ) - for landing in landings: - self.client.reachable.add((landing["repository"], landing["commit"], landing["branch"])) - self.client.successful_checks[(landing["repository"], landing["commit"])] = set( - landing["required_checks"] - ) - self.clear_mutation_spies() - - evidence = apply_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=target_sets, - historical_cross_repository_completions=completion_sets, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertTrue(all(parent["state"] == "closed" for parent in parents)) - self.assertTrue(all(label_names(parent) & STATUS_LABELS == {"status:done"} for parent in parents)) - self.assertEqual( - {(repository, number, "closed", "completed") for repository, number in completion_sets}, - set(self.client.state_updates), - ) - self.assertEqual( - set(completion_sets), - {(repository, number) for repository, number, _labels in self.client.replacements}, - ) - self.assertEqual([], self.client.comment_updates) - self.assertEqual([], self.client.created_issues) - self.assertEqual([], self.client.body_updates) - self.assertEqual(10, len(self.client.reachability_requests)) - self.assertEqual(10, len(self.client.check_requests)) - - def test_corrected_apply_preserves_already_restored_archived_state(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - parent = { - "body": "Archived cross-repository authority.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:done"}, - ], - "milestone": None, - "number": 99, - "state": "closed", - "title": "Archived public work", - } - self.client.issues[".github"].append(parent) - landings = historical_landing_fixture(".github", "workflow") - for landing in landings: - self.client.reachable.add((landing["repository"], landing["commit"], landing["branch"])) - self.client.successful_checks[(landing["repository"], landing["commit"])] = set(landing["required_checks"]) - targets = [{key: value for key, value in landing.items() if key != "commit"} for landing in landings] - self.clear_mutation_spies() - - evidence = apply_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets={(".github", 99): targets}, - historical_cross_repository_completions={(".github", 99): landings}, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assert_no_github_mutations() - - def test_archived_completion_ignores_later_required_check_rename_and_addition(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - parent = { - "body": "Archived cross-repository authority.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:done"}, - ], - "milestone": None, - "number": 99, - "state": "closed", - "title": "Archived public work", - } - self.client.issues[".github"].append(parent) - landings = historical_landing_fixture(".github", "workflow") - for landing in landings: - self.client.reachable.add((landing["repository"], landing["commit"], landing["branch"])) - self.client.successful_checks[(landing["repository"], landing["commit"])] = set(landing["required_checks"]) - targets = [{key: value for key, value in landing.items() if key != "commit"} for landing in landings] - targets[0]["required_checks"] = [ - "Additional control-plane qualification", - "Renamed control-plane qualification", - ] - self.clear_mutation_spies() - - evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets={(".github", 99): targets}, - historical_cross_repository_completions={(".github", 99): landings}, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assert_no_github_mutations() - - def test_missing_frozen_historical_check_fails_without_reopening_archived_work(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - parent = { - "body": "Archived cross-repository authority.", - "html_url": "https://github.com/durable-workflow/.github/issues/39", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:done"}, - ], - "milestone": None, - "number": 39, - "state": "closed", - "title": "Archived public work", - } - self.client.issues[".github"].append(parent) - landings = historical_landing_fixture(".github", "workflow") - for landing in landings: - self.client.reachable.add((landing["repository"], landing["commit"], landing["branch"])) - self.client.successful_checks[(landings[0]["repository"], landings[0]["commit"])] = set( - landings[0]["required_checks"] - ) - targets = [{key: value for key, value in landing.items() if key != "commit"} for landing in landings] - self.clear_mutation_spies() - - with self.assertRaisesRegex(AuthorityError, "historical completion evidence failed revalidation"): - audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets={(".github", 39): targets}, - historical_cross_repository_completions={(".github", 39): landings}, - ) - - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assert_no_github_mutations() - - def test_cross_repository_parent_ignores_untrusted_noise_and_accepts_a_trusted_rebuild(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - qualification = qualification_fixture() - - def target(repository: str) -> dict[str, Any]: - value = next(value for value in qualification["targets"].values() if value["repository"] == repository) - return { - "branch": value["branch"], - "repository": repository, - "required_checks": sorted({workflow["required_check"] for workflow in value["workflows"]}), - } - - parent = { - "body": "Vetted cross-repository work.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:ready"}, - ], - "milestone": None, - "number": 99, - "state": "closed", - "title": "Coordinate source landings", - } - self.client.issues[".github"].append(parent) - source_commit = "a" * 40 - peer_commit = "b" * 40 - self.client.timelines[(".github", 99)] = [ - closing_reference("workflow", 50, actor="external-contributor"), - closing_reference(".github", 51), - closing_reference("workflow", 52), - ] - self.client.pulls[("workflow", 50)] = pull_request( - "workflow", - 50, - "v2", - author="external-contributor", - author_association="NONE", - created_at="2026-07-24T11:00:00Z", - head_repository="external-contributor/workflow", - state="closed", - ) - self.client.pulls[(".github", 51)] = pull_request( - ".github", - 51, - "main", - created_at="2026-07-24T10:00:00Z", - commit=source_commit, - ) - self.client.pulls[("workflow", 52)] = pull_request( - "workflow", - 52, - "v2", - created_at="2026-07-24T10:01:00Z", - ) - self.client.reachable.add((".github", source_commit, "main")) - self.client.successful_checks[(".github", source_commit)] = set(target(".github")["required_checks"]) - declared = {(".github", 99): [target(".github"), target("workflow")]} - self.client.comments[(".github", 99)] = ( - f"{EVIDENCE_MARKER}\nManaged cross-repository lifecycle.\n" - ) - - with self.assertRaisesRegex(AuthorityError, "before every declared target landing"): - audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - - self.assertEqual("open", parent["state"]) - self.assertEqual({"status:ready"}, label_names(parent) & STATUS_LABELS) - self.assertIn(EVIDENCE_MARKER, self.client.comments[(".github", 99)]) - self.assertIn("pending:open", self.client.comments[(".github", 99)]) - self.assertNotIn("/pull/50", self.client.comments[(".github", 99)]) - self.assertIn("trusted-author:durable-workflow-ops", self.client.comments[(".github", 99)]) - self.assertIn("reference actor `durable-workflow-ops`", self.client.comments[(".github", 99)]) - - self.client.pulls[("workflow", 52)] = pull_request( - "workflow", - 52, - "v2", - created_at="2026-07-24T10:01:00Z", - commit=peer_commit, - ) - self.client.reachable.add(("workflow", peer_commit, "v2")) - - qualification_pending = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - - self.assertEqual("pass", qualification_pending["outcome"]) - self.assertEqual("open", parent["state"]) - self.assertIn("pending:qualification", self.client.comments[(".github", 99)]) - - self.client.successful_checks[("workflow", peer_commit)] = set(target("workflow")["required_checks"]) - - evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assertIn("Every declared target landing", self.client.comments[(".github", 99)]) - - self.client.timelines[(".github", 99)].append(closing_reference("workflow", 53, actor="public-noise")) - self.client.pulls[("workflow", 53)] = pull_request( - "workflow", - 53, - "v2", - author="public-noise", - author_association="NONE", - created_at="2026-07-24T11:00:00Z", - head_repository="public-noise/workflow", - state="closed", - ) - - untrusted_evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - self.assertEqual("pass", untrusted_evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertIn("/pull/52", self.client.comments[(".github", 99)]) - self.assertNotIn("/pull/53", self.client.comments[(".github", 99)]) - - self.client.timelines[(".github", 99)].append(closing_reference("workflow", 54, will_close_target=False)) - self.client.pulls[("workflow", 54)] = pull_request( - "workflow", - 54, - "v2", - created_at="2026-07-24T12:00:00Z", - state="closed", - ) - - unrelated_evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - self.assertEqual("pass", unrelated_evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - - self.client.timelines[(".github", 99)].append(closing_reference("workflow", 55)) - self.client.pulls[("workflow", 55)] = pull_request( - "workflow", - 55, - "v2", - created_at="2026-07-24T13:00:00Z", - state="closed", - ) - - with self.assertRaisesRegex(AuthorityError, "before every declared target landing"): - audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - self.assertEqual("open", parent["state"]) - self.assertIn("pending:rejected", self.client.comments[(".github", 99)]) - self.assertIn("/pull/55", self.client.comments[(".github", 99)]) - - rejected_evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - self.assertEqual("pass", rejected_evidence["outcome"]) - self.assertEqual("open", parent["state"]) - - rebuilt_commit = "c" * 40 - self.client.timelines[(".github", 99)].append(closing_reference("workflow", 56)) - self.client.pulls[("workflow", 56)] = pull_request( - "workflow", - 56, - "v2", - created_at="2026-07-24T14:00:00Z", - commit=rebuilt_commit, - ) - self.client.reachable.add(("workflow", rebuilt_commit, "v2")) - self.client.successful_checks[("workflow", rebuilt_commit)] = set(target("workflow")["required_checks"]) - - rebuilt_evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - self.assertEqual("pass", rebuilt_evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assertNotIn(COMPLETION_REQUIRED_LABEL, label_names(parent)) - - def test_production_pipeline_completion_record_stays_closed_with_exact_aggregate_evidence(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - fixture = pipeline_completion_fixture() - targets = qualification_targets(qualification_fixture()) - selected = [targets[record["repository"]] for record in fixture["targets"]] - parent = { - "body": "Vetted cross-repository work.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:done"}, - ], - "milestone": None, - "number": 99, - "state": "closed", - "state_reason": "completed", - "title": "Coordinate direct protected-branch landings", - } - self.client.issues[".github"].append(parent) - for record in fixture["targets"]: - target = targets[record["repository"]] - key = (record["repository"], record["commit"]) - self.client.reachable.add((*key, record["branch"])) - self.client.successful_workflow_runs.add( - ( - record["repository"], - record["qualification_run"], - record["commit"], - record["workflow_path"], - record["workflow_name"], - ) - ) - self.client.successful_check_runs[key] = { - check: record["required_check_run"] for check in target["required_checks"] - } - self.client.trusted_comments[(".github", 99)] = [ - { - "body": fixture["body"], - "id": 67, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ] - self.client.comments[(".github", 99)] = ( - f"{EVIDENCE_MARKER}\nManaged cross-repository lifecycle.\n" - ) - declared = {(".github", 99): selected} - self.clear_mutation_spies() - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertTrue(assessment["complete"]) - self.assertEqual("protected-branch-record", assessment["_authority_kind"]) - - evidence = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - current = self.client.comments[(".github", 99)] - self.assertNotIn("| Target | Landing |", fixture["body"]) - self.assertIn(f"Completion source: `{fixture['completion_source']}`", current) - for record in fixture["targets"]: - if record["repository"] != ".github": - self.assertNotEqual(record["qualification_run"], record["required_check_run"]) - self.assertIn(record["commit"], current) - self.assertIn(f"cited run `{record['qualification_run']}`", current) - self.assertIn(f"check run `{record['required_check_run']}`", current) - for check in targets[record["repository"]]["required_checks"]: - self.assertIn(f"`{check}`", current) - self.assertEqual([], self.client.state_updates) - self.assertEqual([], self.client.replacements) - - self.clear_mutation_spies() - replay = audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets=declared, - ) - - self.assertEqual("pass", replay["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assert_no_github_mutations() - - def test_legacy_generic_completion_uses_the_cited_runs_authenticated_workflow(self) -> None: - fixture = pipeline_completion_fixture() - targets = qualification_targets(qualification_fixture()) - selected = [targets[record["repository"]] for record in fixture["targets"]] - source = next(record for record in fixture["targets"] if record["repository"] == ".github") - named_line = next( - line for line in fixture["body"].splitlines() if line.startswith("Required public qualification ") - ) - fixture["body"] = fixture["body"].replace( - named_line, - "Required public qualification passed: " - f"https://github.com/durable-workflow/.github/actions/runs/{source['qualification_run']}", - ) - install_pipeline_completion_evidence(self.client, fixture, targets) - self.client.successful_workflow_runs.remove( - ( - source["repository"], - source["qualification_run"], - source["commit"], - source["workflow_path"], - source["workflow_name"], - ) - ) - self.client.successful_workflow_runs.add( - ( - source["repository"], - source["qualification_run"], - source["commit"], - "public-boundary.yml", - "Public Boundary", - ) - ) - source_key = (source["repository"], source["commit"]) - self.client.successful_check_runs[source_key] = { - check: 30200000901 for check in targets[source["repository"]]["required_checks"] - } - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertTrue(assessment["complete"]) - self.assertTrue( - all( - requested_path is None and requested_name is None - for _repository, _run, _commit, requested_path, requested_name in self.client.workflow_run_requests - ) - ) - source_result = next( - target for target in assessment["targets"] if target["repository"] == source["repository"] - ) - self.assertNotIn(source["qualification_run"], source_result["required_check_runs"].values()) - - def test_descendant_qualification_advances_item_ledger_without_record_ci_replay(self) -> None: - fixture = pipeline_completion_fixture() - targets = qualification_targets(qualification_fixture()) - selected = [targets[record["repository"]] for record in fixture["targets"]] - source = next(record for record in fixture["targets"] if record["repository"] == ".github") - implementation_commit = "e" * 40 - completed_line = next(line for line in fixture["body"].splitlines() if line.startswith("Completed in ")) - fixture["body"] = fixture["body"].replace( - completed_line, - "Implemented in " - f"[`{implementation_commit[:12]}`](https://github.com/durable-workflow/.github/commit/" - f"{implementation_commit}) on `main`.\n" - "Included by completion source " - f"[`{source['commit'][:12]}`](https://github.com/durable-workflow/.github/commit/" - f"{source['commit']}) on `main`.", - ) - named_line = next( - line for line in fixture["body"].splitlines() if line.startswith("Required public qualification ") - ) - fixture["body"] = fixture["body"].replace( - named_line, - f"Required public qualification `{source['workflow_name']}` (`{source['workflow_path']}`) " - f"passed in [run {source['qualification_run']}]" - f"(https://github.com/durable-workflow/.github/actions/runs/{source['qualification_run']}).", - ).replace( - "durable-workflow-implementation-source: " + "d" * 40, - f"durable-workflow-implementation-source: {implementation_commit}", - ) - install_pipeline_completion_evidence(self.client, fixture, targets) - self.client.reachable.add((source["repository"], implementation_commit, source["branch"])) - self.client.contained_commits.add( - (source["repository"], source["commit"], implementation_commit) - ) - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertTrue(assessment["complete"]) - source_result = next( - target for target in assessment["targets"] if target["repository"] == source["repository"] - ) - self.assertEqual(implementation_commit, source_result["implementation_commit"]) - self.assertEqual(source["commit"], source_result["commit"]) - self.assertEqual( - [source["qualification_run"]], - [record["run"] for record in source_result["qualification_runs"]], - ) - self.assertEqual( - [ - { - "branch": source["branch"], - "implementation_source": implementation_commit, - "qualified_source": source["commit"], - "qualification_runs": [ - { - "name": source["workflow_name"], - "path": source["workflow_path"], - "run": source["qualification_run"], - } - ], - "repository": source["repository"], - } - ], - assessment["_qualification_ledger_advancements"], - ) - rendered = render_evidence(assessment) - self.assertIn(f"Completion source: `{source['commit']}`", rendered) - self.assertIn(f"cited run `{source['qualification_run']}`", rendered) - self.assertIn(QUALIFICATION_LEDGER_MARKER, rendered) - - parent = { - "body": "Vetted cross-repository work.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:in-progress"}, - ], - "milestone": None, - "number": 99, - "state": "open", - "state_reason": None, - "title": "Coordinate descendant source qualification", - } - self.client.issues[".github"].append(parent) - self.client.mutation_events.clear() - - failures = _audit_state_labels( - self.policy, - self.client, - self.client.issues, - set(), - {(".github", 99): selected}, - None, - None, - None, - ) - - self.assertEqual([], failures) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assertIn(QUALIFICATION_LEDGER_MARKER, self.client.comments[(".github", 99)]) - self.assertLess( - self.client.mutation_events.index(("comment", ".github", 99)), - self.client.mutation_events.index(("state", ".github", 99)), - ) - - trusted_comment = self.client.trusted_comments[(".github", 99)][0] - trusted_comment["body"] = fixture["body"].replace( - f"durable-workflow-implementation-source: {implementation_commit}", - "durable-workflow-implementation-source: " + "f" * 40, - ) - forged_marker = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - self.assertFalse(forged_marker["complete"]) - trusted_comment["body"] = fixture["body"] - - self.client.contained_commits.remove( - (source["repository"], source["commit"], implementation_commit) - ) - unrelated_completion_source = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - self.assertFalse(unrelated_completion_source["complete"]) - unrelated_by_repository = { - target["repository"]: target for target in unrelated_completion_source["targets"] - } - self.assertEqual( - "pending:completion-source-does-not-contain-implementation", - unrelated_by_repository[source["repository"]]["state"], - ) - self.client.contained_commits.add( - (source["repository"], source["commit"], implementation_commit) - ) - - self.client.reachable.remove((source["repository"], implementation_commit, source["branch"])) - rejected = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertFalse(rejected["complete"]) - by_repository = {target["repository"]: target for target in rejected["targets"]} - self.assertEqual("pending:landing-not-on-target", by_repository[source["repository"]]["state"]) - - def test_named_completion_without_a_path_authenticates_the_runs_actual_workflow(self) -> None: - targets = qualification_targets(qualification_fixture()) - for spelling in ("linked", "linked-backticked"): - with self.subTest(spelling=spelling): - fixture = pipeline_completion_fixture() - source = next(record for record in fixture["targets"] if record["repository"] == ".github") - named_line = next( - line for line in fixture["body"].splitlines() if line.startswith("Required public qualification ") - ) - if spelling == "linked": - replacement = ( - f"Required public qualification {source['workflow_name']} passed in run " - f"[{source['qualification_run']}]" - f"(https://github.com/durable-workflow/.github/actions/runs/" - f"{source['qualification_run']})." - ) - else: - replacement = ( - f"Required public qualification `{source['workflow_name']}` passed in " - f"[run {source['qualification_run']}]" - f"(https://github.com/durable-workflow/.github/actions/runs/" - f"{source['qualification_run']})." - ) - fixture["body"] = fixture["body"].replace(named_line, replacement) - client = FakeGitHubApi(self.policy) - install_pipeline_completion_evidence(client, fixture, targets) - selected = [targets[record["repository"]] for record in fixture["targets"]] - - assessment = evaluate_lifecycle( - client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertTrue(assessment["complete"]) - self.assertIn( - ( - source["repository"], - source["qualification_run"], - source["commit"], - None, - source["workflow_name"], - ), - client.workflow_run_requests, - ) - rendered = render_evidence(assessment) - self.assertIn(f"`{source['workflow_name']}` (authenticated workflow path)", rendered) - - client.successful_workflow_runs.remove( - ( - source["repository"], - source["qualification_run"], - source["commit"], - source["workflow_path"], - source["workflow_name"], - ) - ) - client.successful_workflow_runs.add( - ( - source["repository"], - source["qualification_run"], - source["commit"], - source["workflow_path"], - "Different workflow", - ) - ) - rejected = evaluate_lifecycle( - client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - by_repository = {target["repository"]: target for target in rejected["targets"]} - self.assertEqual("pending:qualification-identity", by_repository[source["repository"]]["state"]) - - def test_historical_named_workflow_is_authenticated_independently_of_current_policy(self) -> None: - fixture = pipeline_completion_fixture() - targets = qualification_targets(qualification_fixture()) - selected = [targets[record["repository"]] for record in fixture["targets"]] - source = next(record for record in fixture["targets"] if record["repository"] == ".github") - named_line = next( - line for line in fixture["body"].splitlines() if line.startswith("Required public qualification ") - ) - historical_name = "Beta Candidate" - historical_path = "beta-candidate.yml" - fixture["body"] = fixture["body"].replace( - named_line, - f"Required public qualification {historical_name} ({historical_path}) passed in run " - f"[{source['qualification_run']}]" - f"(https://github.com/durable-workflow/.github/actions/runs/{source['qualification_run']}).", - ) - install_pipeline_completion_evidence(self.client, fixture, targets) - self.client.successful_workflow_runs.remove( - ( - source["repository"], - source["qualification_run"], - source["commit"], - source["workflow_path"], - source["workflow_name"], - ) - ) - self.client.successful_workflow_runs.add( - ( - source["repository"], - source["qualification_run"], - source["commit"], - historical_path, - historical_name, - ) - ) - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertTrue(assessment["complete"]) - self.assertIn( - ( - source["repository"], - source["qualification_run"], - source["commit"], - historical_path, - historical_name, - ), - self.client.workflow_run_requests, - ) - - trusted_comment = self.client.trusted_comments[(".github", 99)][0] - trusted_comment["body"] = fixture["body"].replace(historical_path, "../beta-candidate.yml") - malformed = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - self.assertFalse(malformed["complete"]) - trusted_comment["body"] = fixture["body"] - - self.client.successful_check_runs[(source["repository"], source["commit"])] = {} - missing_checks = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - by_repository = {target["repository"]: target for target in missing_checks["targets"]} - self.assertEqual("pending:qualification", by_repository[source["repository"]]["state"]) - - def test_historical_control_plane_alias_maps_only_to_dot_github(self) -> None: - targets = qualification_targets(qualification_fixture()) - source = targets["sdk-python"] - peer = targets[".github"] - source_commit = "a" * 40 - peer_commit = "b" * 40 - source_run = 30200000011 - peer_run = 30200000012 - source_workflow = source["required_workflows"][0]["path"] - peer_workflow = peer["required_workflows"][0]["path"] - body = ( - f"Completed in [`{source_commit[:12]}`](https://github.com/durable-workflow/sdk-python/commit/" - f"{source_commit}) on `main`.\n\n" - f"Required public qualification Python Target Qualification ({source_workflow}) passed in run " - f"[{source_run}](https://github.com/durable-workflow/sdk-python/actions/runs/{source_run}).\n\n" - "Required cross-repository target qualification passed:\n" - f"- `github-control-plane:main` at [`{peer_commit[:12]}`]" - f"(https://github.com/durable-workflow/.github/commit/{peer_commit}) " - f"([qualification](https://github.com/durable-workflow/.github/actions/runs/{peer_run}))\n\n" - f"\n" - ) - for target, commit, run, workflow, name in ( - (source, source_commit, source_run, source_workflow, "Python Target Qualification"), - (peer, peer_commit, peer_run, peer_workflow, "Source Qualification"), - ): - repository = target["repository"] - self.client.reachable.add((repository, commit, target["branch"])) - self.client.successful_workflow_runs.add((repository, run, commit, workflow, name)) - self.client.successful_check_runs[(repository, commit)] = { - check: run for check in target["required_checks"] - } - self.client.trusted_comments[("sdk-python", 16)] = [ - {"body": body, "id": 67, "user": {"id": 7, "login": "durable-workflow-ops"}} - ] - selected = [source, peer] - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - "sdk-python", - {"number": 16}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertTrue(assessment["complete"]) - self.assertEqual([".github", "sdk-python"], sorted(target["repository"] for target in assessment["targets"])) - - self.client.trusted_comments[("sdk-python", 16)][0]["body"] = body.replace( - "github-control-plane:main", - "control-plane:main", - ) - arbitrary_alias = evaluate_lifecycle( - self.client, - "durable-workflow", - "sdk-python", - {"number": 16}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - self.assertFalse(arbitrary_alias["complete"]) - - def test_mixed_pull_request_and_protected_branch_completion_preserves_later_regression(self) -> None: - fixture = pipeline_completion_fixture() - targets = qualification_targets(qualification_fixture()) - selected = [targets[record["repository"]] for record in fixture["targets"]] - install_pipeline_completion_evidence(self.client, fixture, targets) - workflow = next(record for record in fixture["targets"] if record["repository"] == "workflow") - self.client.timelines[(".github", 99)] = [closing_reference("workflow", 70)] - self.client.pulls[("workflow", 70)] = pull_request( - "workflow", - 70, - "v2", - commit=workflow["commit"], - created_at="2026-08-11T10:00:00Z", - ) - self.client.successful_checks[("workflow", workflow["commit"])] = set(targets["workflow"]["required_checks"]) - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertTrue(assessment["complete"]) - self.assertEqual("mixed-landing-record", assessment["_authority_kind"]) - by_repository = {target["repository"]: target for target in assessment["targets"]} - self.assertIn("/pull/70", by_repository["workflow"]["pull_request"]) - self.assertEqual( - "authenticated-completion-record", - by_repository[".github"]["provenance"], - ) - - self.client.pulls[("workflow", 70)] = pull_request( - "workflow", - 70, - "v2", - created_at="2026-08-11T10:00:00Z", - state="closed", - ) - - regressed = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertFalse(regressed["complete"]) - by_repository = {target["repository"]: target for target in regressed["targets"]} - self.assertEqual("pending:rejected", by_repository["workflow"]["state"]) - self.assertIn("/pull/70", by_repository["workflow"]["pull_request"]) - - def test_later_completion_evidence_hold_reopens_direct_completion(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - fixture = pipeline_completion_fixture() - targets = qualification_targets(qualification_fixture()) - selected = [targets[record["repository"]] for record in fixture["targets"]] - parent = { - "body": "Vetted cross-repository work.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": COMPLETION_REQUIRED_LABEL}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:done"}, - ], - "milestone": None, - "number": 99, - "state": "closed", - "state_reason": "completed", - "title": "Coordinate direct protected-branch landings", - } - self.client.issues[".github"].append(parent) - install_pipeline_completion_evidence(self.client, fixture, targets) - self.client.comments[(".github", 99)] = ( - f"{EVIDENCE_MARKER}\nManaged cross-repository lifecycle.\n" - ) - - with self.assertRaisesRegex( - AuthorityError, - "closed before its required public completion evidence was verified", - ): - audit_backlog( - self.policy, - self.backlog, - self.client, - approved_completion_holds={(".github", 99)}, - cross_repository_targets={(".github", 99): selected}, - ) - - self.assertEqual("open", parent["state"]) - self.assertEqual("reopened", parent["state_reason"]) - self.assertIn("Every declared target landing", self.client.comments[(".github", 99)]) - - def test_pipeline_completion_rejects_spoofed_or_incomplete_records(self) -> None: - fixture = pipeline_completion_fixture() - targets = qualification_targets(qualification_fixture()) - selected = [targets[record["repository"]] for record in fixture["targets"]] - workflow = next(record for record in fixture["targets"] if record["repository"] == "workflow") - workflow_line = next(line for line in fixture["body"].splitlines() if line.startswith("- `workflow:v2`")) - server_line = workflow_line.replace("workflow:v2", "server:main").replace( - "/workflow/", - "/server/", - ) - source_marker = f"durable-workflow-completion-source: {fixture['completion_source']}" - cases = { - "wrong branch": fixture["body"].replace("workflow:v2", "workflow:main"), - "wrong commit": fixture["body"].replace("c" * 40, "e" * 40).replace("c" * 12, "e" * 12), - "wrong qualification run": fixture["body"].replace( - str(workflow["qualification_run"]), - "30200000999", - ), - "mutable commit reference": fixture["body"].replace("c" * 40, "v2"), - "incomplete target set": fixture["body"].replace(workflow_line + "\n", ""), - "duplicate target": fixture["body"].replace(workflow_line, f"{workflow_line}\n{workflow_line}"), - "extra target": fixture["body"].replace(workflow_line, f"{workflow_line}\n{server_line}"), - "mutable completion source": fixture["body"].replace( - source_marker, - "durable-workflow-completion-source: main", - ), - "wrong completion source": fixture["body"].replace( - source_marker, - f"durable-workflow-completion-source: {'d' * 40}", - ), - } - for name, body in cases.items(): - with self.subTest(name=name): - client = FakeGitHubApi(self.policy) - for record in fixture["targets"]: - target = targets[record["repository"]] - key = (record["repository"], record["commit"]) - client.reachable.add((*key, record["branch"])) - client.successful_workflow_runs.add( - ( - record["repository"], - record["qualification_run"], - record["commit"], - record["workflow_path"], - record["workflow_name"], - ) - ) - client.successful_check_runs[key] = { - check: record["required_check_run"] for check in target["required_checks"] - } - client.trusted_comments[(".github", 99)] = [ - { - "body": body, - "id": 67, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ] - - assessment = evaluate_lifecycle( - client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertFalse(assessment["complete"]) - if name == "wrong qualification run": - by_repository = {target["repository"]: target for target in assessment["targets"]} - self.assertEqual("pending:qualification-identity", by_repository["workflow"]["state"]) - - client = FakeGitHubApi(self.policy) - for record in fixture["targets"]: - target = targets[record["repository"]] - key = (record["repository"], record["commit"]) - client.reachable.add((*key, record["branch"])) - client.successful_workflow_runs.add( - ( - record["repository"], - record["qualification_run"], - record["commit"], - record["workflow_path"], - record["workflow_name"], - ) - ) - client.successful_check_runs[key] = { - check: record["required_check_run"] for check in target["required_checks"] - } - client.successful_check_runs[("workflow", workflow["commit"])] = {} - client.trusted_comments[(".github", 99)] = [ - { - "body": fixture["body"], - "id": 67, - "user": {"id": 7, "login": "durable-workflow-ops"}, - } - ] - - missing_live_check = evaluate_lifecycle( - client, - "durable-workflow", - ".github", - {"number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertFalse(missing_live_check["complete"]) - by_repository = {target["repository"]: target for target in missing_live_check["targets"]} - self.assertEqual("pending:qualification", by_repository["workflow"]["state"]) - - forged_body = FakeGitHubApi(self.policy) - install_pipeline_completion_evidence(forged_body, fixture, targets) - forged_body.trusted_comments.clear() - - ignored = evaluate_lifecycle( - forged_body, - "durable-workflow", - ".github", - {"body": fixture["body"], "number": 99}, - selected, - trusted_actors=self.policy["intake"]["trusted_actors"], - ) - - self.assertFalse(ignored["complete"]) - self.assertTrue(all(target["state"] == "pending:no-linked-pull-request" for target in ignored["targets"])) - - def test_closing_reference_changes_during_assessment_remain_pending(self) -> None: - cases = { - "removed": lambda source, _external: [source], - "reintroduced": lambda source, _external: [ - source, - closing_reference( - "workflow", - 70, - actor="external-contributor", - created_at="2026-07-24T10:00:00Z", - identifier="CRE_kwDOA1b2c84A0071", - ), - ], - "retargeted": lambda source, _external: [ - source, - closing_reference( - "server", - 71, - actor="external-contributor", - created_at="2026-07-24T10:00:00Z", - identifier="CRE_kwDOA1b2c84A0072", - ), - ], - } - for name, changed_references in cases.items(): - with self.subTest(name=name): - policy, backlog, client, parent, declared, source_reference, external_reference = ( - closing_reference_race_fixture() - ) - initial = [source_reference, external_reference] - changed = changed_references(source_reference, external_reference) - client.timeline_sequences[(".github", 99)] = [initial, changed, changed] - - evidence = audit_backlog( - policy, - backlog, - client, - cross_repository_targets=declared, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("open", parent["state"]) - self.assertNotIn("status:done", label_names(parent)) - self.assertIn("The parent remains open", client.comments[(".github", 99)]) - self.assertNotIn("Every declared target landing", client.comments[(".github", 99)]) - self.assertEqual(4, client.timeline_reads.count((".github", 99))) - - def test_unchanged_external_reference_with_fresh_exact_head_approval_completes(self) -> None: - policy, backlog, client, parent, declared, source_reference, external_reference = ( - closing_reference_race_fixture() - ) - client.timelines[(".github", 99)] = [source_reference, external_reference] - - evidence = audit_backlog( - policy, - backlog, - client, - cross_repository_targets=declared, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("closed", parent["state"]) - self.assertEqual({"status:done"}, label_names(parent) & STATUS_LABELS) - self.assertIn("Every declared target landing", client.comments[(".github", 99)]) - self.assertIn("approval review `1` at `2026-07-24T09:30:00Z`", client.comments[(".github", 99)]) - self.assertEqual(4, client.timeline_reads.count((".github", 99))) - - def test_nonconvergent_reference_snapshot_stops_at_the_retry_bound(self) -> None: - policy, backlog, client, parent, declared, source_reference, external_reference = ( - closing_reference_race_fixture() - ) - unchanged = [source_reference, external_reference] - removed = [source_reference] - client.timeline_sequences[(".github", 99)] = [unchanged, removed, unchanged, removed] - - evidence = audit_backlog( - policy, - backlog, - client, - cross_repository_targets=declared, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("open", parent["state"]) - self.assertNotIn("status:done", label_names(parent)) - self.assertIn("pending:closing-reference-changed", client.comments[(".github", 99)]) - self.assertNotIn("Every declared target landing", client.comments[(".github", 99)]) - self.assertEqual(4, client.timeline_reads.count((".github", 99))) - - def test_closing_reference_change_racing_final_write_is_immediately_reconciled(self) -> None: - policy, backlog, client, parent, declared, source_reference, external_reference = ( - closing_reference_race_fixture() - ) - unchanged = [source_reference, external_reference] - removed = [source_reference] - client.timeline_sequences[(".github", 99)] = [unchanged, unchanged, unchanged, removed] - - with self.assertRaisesRegex(AuthorityError, "closing-reference authority changed during lifecycle mutation"): - audit_backlog( - policy, - backlog, - client, - cross_repository_targets=declared, - ) - - self.assertEqual("open", parent["state"]) - self.assertEqual({"status:ready"}, label_names(parent) & STATUS_LABELS) - self.assertIn("pending:closing-reference-changed", client.comments[(".github", 99)]) - self.assertIn("The parent remains open", client.comments[(".github", 99)]) - self.assertNotIn("Every declared target landing", client.comments[(".github", 99)]) - self.assertEqual(4, client.timeline_reads.count((".github", 99))) - - def test_external_attempt_requires_approval_of_its_exact_head(self) -> None: - target = qualification_targets(qualification_fixture())["workflow"] - trusted_actors = self.policy["intake"]["trusted_actors"] - issue = {"number": 99} - landed_commit = "d" * 40 - approved_head = "e" * 40 - newer_head = "f" * 40 - self.client.timelines[(".github", 99)] = [ - closing_reference("workflow", 70, actor="external-contributor"), - closing_reference("workflow", 71, actor="another-contributor"), - ] - self.client.pulls[("workflow", 70)] = pull_request( - "workflow", - 70, - "v2", - author="external-contributor", - author_association="NONE", - commit=landed_commit, - created_at="2026-07-24T10:00:00Z", - head_repository="external-contributor/workflow", - head_sha=approved_head, - ) - self.client.reviews[("workflow", 70)] = [approving_review(approved_head)] - self.client.pulls[("workflow", 71)] = pull_request( - "workflow", - 71, - "v2", - author="another-contributor", - author_association="NONE", - created_at="2026-07-24T11:00:00Z", - head_repository="another-contributor/workflow", - head_sha=newer_head, - state="closed", - ) - self.client.reviews[("workflow", 71)] = [approving_review("0" * 40)] - self.client.reachable.add(("workflow", landed_commit, "v2")) - self.client.successful_checks[("workflow", landed_commit)] = set(target["required_checks"]) - - approved = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertTrue(approved["complete"]) - self.assertEqual( - "https://github.com/durable-workflow/workflow/pull/70", - approved["targets"][0]["pull_request"], - ) - self.assertEqual(approved_head, approved["targets"][0]["head_commit"]) - self.assertEqual("durable-workflow/workflow", approved["targets"][0]["base_repository"]) - self.assertEqual("v2", approved["targets"][0]["base_ref"]) - self.assertEqual("approved:repository-maintainer", approved["targets"][0]["provenance"]) - rendered = render_evidence(approved) - self.assertIn("reference event `CRE_kwDOA1b2c84A0070` at `2026-07-24T09:00:00Z`", rendered) - self.assertIn("approval review `1` at `2026-07-24T09:30:00Z`", rendered) - - self.client.reviews[("workflow", 71)] = [approving_review(newer_head)] - - superseded = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertFalse(superseded["complete"]) - self.assertEqual("pending:rejected", superseded["targets"][0]["state"]) - self.assertEqual( - "https://github.com/durable-workflow/workflow/pull/71", - superseded["targets"][0]["pull_request"], - ) - - def test_external_attempt_requires_fresh_approval_after_latest_closing_reference(self) -> None: - target = qualification_targets(qualification_fixture())["workflow"] - trusted_actors = self.policy["intake"]["trusted_actors"] - issue = {"number": 99} - landed_commit = "a" * 40 - external_head = "b" * 40 - late_reference = closing_reference( - "workflow", - 91, - actor="external-contributor", - created_at="2026-07-24T10:00:00Z", - identifier="CRE_kwDOA1b2c84AA910", - ) - self.client.timelines[(".github", 99)] = [late_reference] - self.client.pulls[("workflow", 91)] = pull_request( - "workflow", - 91, - "v2", - author="external-contributor", - author_association="NONE", - created_at="2026-07-24T09:00:00Z", - head_repository="external-contributor/workflow", - head_sha=external_head, - state="closed", - ) - self.client.reviews[("workflow", 91)] = [ - approving_review( - external_head, - submitted_at="2026-07-24T09:30:00Z", - ) - ] - - before_landing = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertFalse(before_landing["complete"]) - self.assertEqual("pending:no-linked-pull-request", before_landing["targets"][0]["state"]) - - self.client.timelines[(".github", 99)].insert( - 0, - closing_reference( - "workflow", - 90, - created_at="2026-07-24T08:30:00Z", - identifier="CRE_kwDOA1b2c84AA900", - ), - ) - self.client.pulls[("workflow", 90)] = pull_request( - "workflow", - 90, - "v2", - commit=landed_commit, - created_at="2026-07-24T08:00:00Z", - ) - self.client.reachable.add(("workflow", landed_commit, "v2")) - self.client.successful_checks[("workflow", landed_commit)] = set(target["required_checks"]) - - completed = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertTrue(completed["complete"]) - self.assertIn("/pull/90", completed["targets"][0]["pull_request"]) - - self.client.reviews[("workflow", 91)].append( - approving_review( - external_head, - identifier=2, - submitted_at="2026-07-24T10:30:00Z", - ) - ) - - admitted = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertFalse(admitted["complete"]) - self.assertEqual("pending:rejected", admitted["targets"][0]["state"]) - self.assertIn("/pull/91", admitted["targets"][0]["pull_request"]) - - self.client.timelines[(".github", 99)].append( - closing_reference( - "workflow", - 91, - actor="external-contributor", - created_at="2026-07-24T11:00:00Z", - identifier="CRE_kwDOA1b2c84AA911", - ) - ) - - reintroduced = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertTrue(reintroduced["complete"]) - self.assertIn("/pull/90", reintroduced["targets"][0]["pull_request"]) - - self.client.reviews[("workflow", 91)].append( - approving_review( - external_head, - identifier=3, - submitted_at="2026-07-24T11:30:00Z", - ) - ) - - readmitted = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertFalse(readmitted["complete"]) - self.assertEqual("pending:rejected", readmitted["targets"][0]["state"]) - self.assertIn("/pull/91", readmitted["targets"][0]["pull_request"]) - self.assertEqual("CRE_kwDOA1b2c84AA911", readmitted["targets"][0]["reference_event"]) - self.assertEqual(3, readmitted["targets"][0]["approval_review"]) - - def test_captured_graphql_references_admit_trusted_and_freshly_approved_external_attempts(self) -> None: - targets = qualification_targets(qualification_fixture()) - selected_targets = [targets["workflow"], targets["server"]] - trusted_actors = self.policy["intake"]["trusted_actors"] - payload = json.loads((ROOT / "tests/fixtures/github/cross-referenced-events.json").read_text(encoding="utf-8")) - self.client.timelines[(".github", 99)] = payload["data"]["repository"]["issue"]["timelineItems"]["nodes"] - workflow_landing = "c" * 40 - server_landing = "d" * 40 - external_head = "e" * 40 - self.client.pulls[("workflow", 70)] = pull_request( - "workflow", - 70, - "v2", - commit=workflow_landing, - created_at="2026-07-24T08:45:00Z", - ) - self.client.pulls[("server", 71)] = pull_request( - "server", - 71, - "main", - author="external-contributor", - author_association="NONE", - commit=server_landing, - created_at="2026-07-24T08:50:00Z", - head_repository="external-contributor/server", - head_sha=external_head, - ) - self.client.reviews[("server", 71)] = [ - approving_review( - external_head, - submitted_at="2026-07-24T09:30:00Z", - ) - ] - for target, commit in ((targets["workflow"], workflow_landing), (targets["server"], server_landing)): - repository = target["repository"] - self.client.reachable.add((repository, commit, target["branch"])) - self.client.successful_checks[(repository, commit)] = set(target["required_checks"]) - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - {"number": 99}, - selected_targets, - trusted_actors=trusted_actors, - ) - - self.assertTrue(assessment["complete"]) - by_repository = {target["repository"]: target for target in assessment["targets"]} - self.assertEqual("CRE_kwDOA1b2c84AAZ9x", by_repository["workflow"]["reference_event"]) - self.assertEqual("trusted-author:durable-workflow-ops", by_repository["workflow"]["provenance"]) - self.assertEqual("CRE_kwDOA1b2c84AAaB7", by_repository["server"]["reference_event"]) - self.assertEqual("approved:repository-maintainer", by_repository["server"]["provenance"]) - self.assertEqual("2026-07-24T09:30:00Z", by_repository["server"]["approval_at"]) - - def test_trusted_attempt_requires_exact_pull_head_base_and_reference_metadata(self) -> None: - target = qualification_targets(qualification_fixture())["workflow"] - trusted_actors = self.policy["intake"]["trusted_actors"] - issue = {"number": 99} - mutations = [ - ("pull API identity", "pull", ("url",), "https://api.github.com/repos/durable-workflow/workflow/pulls/99"), - ("pull HTML identity", "pull", ("html_url",), "https://github.com/durable-workflow/workflow/pull/99"), - ("head repository", "pull", ("head", "repo", "full_name"), "public-noise/workflow"), - ("head commit", "pull", ("head", "sha"), "invalid"), - ("base repository", "pull", ("base", "repo", "full_name"), "durable-workflow/server"), - ("base commit", "pull", ("base", "sha"), "invalid"), - ("reference actor", "event", ("actor", "login"), "public-noise"), - ("reference source type", "event", ("source", "__typename"), "Issue"), - ("reference source repository", "event", ("source", "repository", "nameWithOwner"), "other/workflow"), - ("reference event identity", "event", ("id",), 80), - ("reference event timestamp", "event", ("referencedAt",), "invalid"), - ] - for label, location, path, replacement in mutations: - with self.subTest(label=label): - event = closing_reference("workflow", 80) - pull = pull_request( - "workflow", - 80, - "v2", - created_at="2026-07-24T10:00:00Z", - state="closed", - ) - record = event if location == "event" else pull - cursor = record - for field in path[:-1]: - cursor = cursor[field] - cursor[path[-1]] = replacement - self.client.timelines[(".github", 99)] = [event] - self.client.pulls[("workflow", 80)] = pull - - assessment = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertFalse(assessment["complete"]) - self.assertEqual("pending:no-linked-pull-request", assessment["targets"][0]["state"]) - - wrong_branch = pull_request( - "workflow", - 81, - "main", - created_at="2026-07-24T11:00:00Z", - state="closed", - ) - self.client.timelines[(".github", 99)] = [closing_reference("workflow", 81)] - self.client.pulls[("workflow", 81)] = wrong_branch - - fail_closed = evaluate_lifecycle( - self.client, - "durable-workflow", - ".github", - issue, - [target], - trusted_actors=trusted_actors, - ) - - self.assertFalse(fail_closed["complete"]) - self.assertEqual("pending:wrong-target", fail_closed["targets"][0]["state"]) - - def test_cross_repository_parent_without_manifest_targets_cannot_remain_done(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - parent = { - "body": "Legacy cross-repository authority.", - "html_url": "https://github.com/durable-workflow/.github/issues/99", - "labels": [ - {"name": "authority:github"}, - {"name": "kind:cross-repository"}, - {"name": "priority:P1"}, - {"name": "status:done"}, - ], - "milestone": None, - "number": 99, - "state": "closed", - "title": "Coordinate source landings", - } - self.client.issues[".github"].append(parent) - self.client.comments[(".github", 99)] = ( - f"{EVIDENCE_MARKER}\nManaged cross-repository lifecycle.\n" - ) - - with self.assertRaisesRegex(AuthorityError, "without a valid declared target set"): - audit_backlog( - self.policy, - self.backlog, - self.client, - cross_repository_targets={}, - ) - - self.assertEqual("open", parent["state"]) - self.assertEqual({"status:triage"}, label_names(parent) & STATUS_LABELS) - - def test_ambiguous_open_status_is_labeled_and_fails(self) -> None: - apply_backlog(self.policy, self.backlog, self.client) - _repository, issue = find_work_item(self.client, "docs-php-conformance-public-authority") - issue["labels"].append({"name": "status:blocked"}) - - with self.assertRaisesRegex(AuthorityError, "ambiguous open lifecycle labels"): - audit_backlog(self.policy, self.backlog, self.client) - - self.assertIn("authority:conflict", label_names(issue)) - - -class PublicLifecycleContractTest(unittest.TestCase): - def setUp(self) -> None: - self.policy, self.backlog, _policy_schema, _backlog_schema = contract_fixture() - self.client = FakeGitHubApi(self.policy) - self.now = datetime(2026, 8, 26, 12, 0, tzinfo=UTC) - - @staticmethod - def metadata( - issue: dict[str, Any], - *, - approved: bool, - transition_at: str = "2026-08-26T10:00:00Z", - ) -> dict[str, Any]: - return { - "approved": approved, - "approval_reason": "trusted-creation" if approved else "approval-label-absent", - "closed_at": None, - "created_at": "2026-08-26T09:00:00Z", - "label_transition_at": {}, - "labels": sorted(label_names(issue)), - "last_transition_at": transition_at, - "number": issue["number"], - "repository": ".github", - "specialized_lifecycle": False, - "state": issue["state"], - "type": "issue", - "updated_at": "2026-08-26T10:00:00Z", - "url": issue["html_url"], - } - - def add_issue(self, number: int, labels: list[str], *, body: str = "Public issue") -> dict[str, Any]: - issue = intake_issue(author="rmcdaniel", body=body, labels=labels, number=number) - self.client.issues[".github"].append(issue) - return issue - - def test_writer_boundary_rejects_duplicate_kinds_and_kind_transition_replaces(self) -> None: - client = GitHubApi("secret") - with self.assertRaisesRegex(AuthorityError, "exactly one kind"): - client.replace_issue_labels( - "durable-workflow", - ".github", - 7, - ["kind:defect", "kind:release-blocker", "status:done"], - ) - - transitioned = _replace_kind_label( - {"authority:github", "kind:defect", "status:ready"}, - "kind:release-blocker", - ) - self.assertEqual({"kind:release-blocker"}, {label for label in transitioned if label.startswith("kind:")}) - - def test_malformed_issue_is_isolated_while_valid_issue_updates(self) -> None: - malformed = self.add_issue( - 1, - ["kind:defect", "kind:feature", "status:ready"], - ) - valid = self.add_issue(2, ["kind:defect"]) - metadata = [self.metadata(malformed, approved=True), self.metadata(valid, approved=True)] - - failures = reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - {".github": [malformed, valid]}, - now=self.now, - ) - - self.assertEqual(1, len(failures)) - self.assertIn(".github#1", failures[0]) - self.assertNotIn((".github", 1), self.client.comments) - self.assertIn((".github", 2), self.client.comments) - self.assertEqual({"status:ready"}, label_names(valid) & STATUS_LABELS) - - def test_aggregate_audit_closes_valid_completion_despite_duplicate_kind(self) -> None: - apply_backlog(self.policy, self.backlog, self.client) - _bad_repository, malformed = find_work_item( - self.client, - "docs-php-conformance-public-authority", - ) - valid = self.add_issue( - 78, - ["authority:github", "kind:defect", "priority:P1", "status:in-progress"], - ) - malformed["labels"].append({"name": "kind:feature"}) - valid["labels"].append({"name": COMPLETION_VERIFIED_LABEL}) - metadata = [self.metadata(malformed, approved=True), self.metadata(valid, approved=True)] - metadata[0]["repository"] = "durable-workflow.github.io" - - with self.assertRaises(LifecycleAuditError) as raised: - audit_backlog( - self.policy, - self.backlog, - self.client, - public_metadata=metadata, - audit_time=self.now, - ) - - self.assertEqual("closed", valid["state"]) - self.assertIn((".github", valid["number"], "closed", "completed"), self.client.state_updates) - failures = raised.exception.evidence["age_audit"]["reconciliation_failures"] - self.assertTrue(any("durable-workflow.github.io#" in failure for failure in failures)) - sweep = raised.exception.evidence["issue_sweep"] - self.assertEqual(2, sweep["before"]["open_count"]) - self.assertEqual(1, sweep["after"]["open_count"]) - self.assertEqual(1, sweep["after"]["closed_count"]) - - def test_state_and_public_blocker_replace_one_comment_and_noop_is_quiet(self) -> None: - issue = self.add_issue(3, ["kind:defect", "status:ready"]) - metadata = [self.metadata(issue, approved=True)] - inventory = {".github": [issue]} - - self.assertEqual( - [], - reconcile_public_lifecycle(self.policy, self.client, metadata, inventory, now=self.now), - ) - first = self.client.comments[(".github", 3)] - self.assertIn(PUBLIC_LIFECYCLE_MARKER, first) - self.assertIn("Approved and queued", first) - - issue["labels"] = [{"name": "kind:defect"}, {"name": "status:blocked"}] - issue["body"] = ( - f"{UNBLOCK_CONTEXT_START}\n## Unblock condition\n\n" - f"A public compatibility decision must be recorded.\n{UNBLOCK_CONTEXT_END}" - ) - reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - now=self.now + timedelta(minutes=1), - ) - second = self.client.comments[(".github", 3)] - self.assertNotEqual(first, second) - self.assertIn("**State:** Blocked", second) - self.assertIn("A public compatibility decision must be recorded.", second) - - replacements_before = list(self.client.replacements) - comments_before = list(self.client.comment_updates) - reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - now=self.now + timedelta(minutes=2), - ) - self.assertEqual(replacements_before, self.client.replacements) - self.assertEqual(comments_before, self.client.comment_updates) - - def test_authenticated_projection_converges_execution_states_through_verified_completion(self) -> None: - issue = self.add_issue(15, ["authority:github", "kind:defect", "status:ready"]) - metadata = [self.metadata(issue, approved=True)] - inventory = {".github": [issue]} - transitions = [ - ("pending", None, "status:ready", "Approved and queued"), - ("claimed", None, "status:in-progress", "In progress"), - ("blocked", "dependency-pending", "status:blocked", "Blocked"), - ("integrated", "release-evidence-pending", "status:in-progress", "In progress"), - ("completed", None, "status:done", "Completed"), - ] - - for minute, (state, condition, status, heading) in enumerate(transitions): - record = { - "completion_evidence": "verified" if state == "completed" else None, - "public_condition": condition, - "state": state, - "transition_at": (self.now + timedelta(minutes=minute)).isoformat(), - } - failures = reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - lifecycle_projection={(".github", 15): record}, - now=self.now + timedelta(minutes=minute), - ) - self.assertEqual([], failures) - self.assertEqual({status}, label_names(issue) & STATUS_LABELS) - self.assertIn(heading, self.client.comments[(".github", 15)]) - if state == "integrated": - projected_comment = self.client.comments[(".github", 15)] - reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - now=self.now + timedelta(minutes=minute, seconds=30), - ) - self.assertEqual(projected_comment, self.client.comments[(".github", 15)]) - - self.assertEqual("closed", issue["state"]) - self.assertEqual("completed", issue["state_reason"]) - self.assertIn(COMPLETION_VERIFIED_LABEL, label_names(issue)) - self.assertIn((".github", 15, "closed", "completed"), self.client.state_updates) - - def test_verified_release_sources_close_completed_implementation(self) -> None: - issue = self.add_issue(16, ["authority:github", "kind:defect", "status:in-progress"]) - metadata = [self.metadata(issue, approved=True)] - implementation_source = "a" * 40 - projection = { - (".github", 16): { - "completion_evidence": "verified", - "implementation_source": implementation_source, - "public_condition": None, - "state": "completed", - "transition_at": self.now.isoformat(), - "verified_release": { - "repository": ".github", - "source_shas": ["b" * 40, implementation_source], - "version": "2026.08.26", - }, - } - } - - failures = reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - {".github": [issue]}, - lifecycle_projection=projection, - now=self.now, - ) - - self.assertEqual([], failures) - self.assertEqual("closed", issue["state"]) - self.assertEqual({"status:done"}, label_names(issue) & STATUS_LABELS) - self.assertIn(COMPLETION_VERIFIED_LABEL, label_names(issue)) - - def test_verified_release_closes_before_intake_and_cannot_reserve_a_successor(self) -> None: - completed = self.add_issue( - 16, - ["authority:github", "kind:defect", "status:in-progress"], - ) - active = self.add_issue( - 17, - ["authority:github", "kind:defect", "status:ready"], - ) - metadata = [ - self.metadata(completed, approved=True), - self.metadata(active, approved=True), - ] - implementation_source = "a" * 40 - projection = { - (".github", 16): { - "completion_evidence": "verified", - "implementation_source": implementation_source, - "public_condition": None, - "state": "completed", - "transition_at": self.now.isoformat(), - "verified_release": { - "repository": ".github", - "source_shas": ["b" * 40, implementation_source], - "version": "2026.08.26", - }, - } - } - - evidence = reconcile_verified_releases_before_intake( - self.policy, - self.client, - metadata, - projection, - now=self.now, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual([".github#16"], evidence["terminal_identities"]) - self.assertEqual("closed", completed["state"]) - self.assertEqual({"status:done"}, label_names(completed) & STATUS_LABELS) - self.assertIn(COMPLETION_VERIFIED_LABEL, label_names(completed)) - self.assertIn("Completed", self.client.comments[(".github", 16)]) - - discovery = FakeDiscovery( - self.policy, - {".github": [(completed, []), (active, [])]}, - ) - manifest, inventory = reconstruct_intake( - self.policy, - discovery, - pre_intake_release_completions=set(projection), - ) - - self.assertEqual({17}, {record["number"] for record in manifest["issues"]}) - self.assertEqual([(".github", 17)], discovery.get_requests) - self.assertNotIn(completed, inventory[".github"]) - terminal_metadata = next( - record for record in manifest["public_metadata"] if record["number"] == 16 - ) - self.assertTrue(terminal_metadata["specialized_lifecycle"]) - self.assertFalse(terminal_metadata.get("quarantined", False)) - - def test_verified_release_preserves_incomplete_cross_repository_evidence_gate(self) -> None: - issue = self.add_issue( - 17, - [ - "authority:github", - COMPLETION_REQUIRED_LABEL, - "kind:cross-repository", - "status:in-progress", - ], - ) - metadata = [self.metadata(issue, approved=True)] - projection = { - (".github", 17): { - "completion_evidence": "verified", - "implementation_source": "a" * 40, - "public_condition": None, - "state": "completed", - "transition_at": self.now.isoformat(), - "verified_release": { - "repository": ".github", - "source_shas": ["a" * 40], - "version": "2026.08.26", - }, - } - } - - failures = reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - {".github": [issue]}, - lifecycle_projection=projection, - now=self.now, - ) - - self.assertEqual(1, len(failures)) - self.assertIn("waiting on required target evidence", failures[0]) - self.assertEqual("open", issue["state"]) - self.assertEqual({"status:in-progress"}, label_names(issue) & STATUS_LABELS) - - def test_current_projection_clears_only_resolved_authority_conflicts(self) -> None: - resolved = self.add_issue( - 18, - ["authority:conflict", "authority:github", "kind:defect", "status:in-progress"], - ) - malformed = self.add_issue( - 19, - ["authority:conflict", "authority:github", "kind:defect", "kind:feature", "status:ready"], - ) - projection = { - (".github", number): { - "completion_evidence": None, - "public_condition": "release-evidence-pending", - "state": "integrated", - "transition_at": self.now.isoformat(), - } - for number in (18, 19) - } - - failures = reconcile_public_lifecycle( - self.policy, - self.client, - [self.metadata(resolved, approved=True), self.metadata(malformed, approved=True)], - {".github": [resolved, malformed]}, - lifecycle_projection=projection, - now=self.now, - ) - - self.assertNotIn("authority:conflict", label_names(resolved)) - self.assertIn("authority:conflict", label_names(malformed)) - self.assertEqual(1, len(failures)) - self.assertIn(".github#19 has malformed kind labels", failures[0]) - - def test_trusted_retirement_precedes_historical_targets_while_neighboring_closure_reopens(self) -> None: - for item in self.backlog["items"]: - if item["kind"] == "cross-repository": - item["kind"] = "feature" - apply_backlog(self.policy, self.backlog, self.client) - retired = self.add_issue( - 30, - ["authority:github", "kind:cross-repository", "priority:P1", "status:in-progress"], - ) - neighboring = self.add_issue( - 31, - ["authority:github", "kind:cross-repository", "priority:P1", "status:in-progress"], - ) - metadata = [self.metadata(retired, approved=True), self.metadata(neighboring, approved=True)] - inventory = self.client.issues - - for issue, state, evidence in ( - (retired, "superseded", None), - (neighboring, "completed", "verified"), - ): - failures = reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - lifecycle_projection={ - (".github", int(issue["number"])): { - "completion_evidence": evidence, - "public_condition": None, - "state": state, - "transition_at": self.now.isoformat(), - } - }, - now=self.now, - ) - self.assertEqual([], failures) - - historical_landing = historical_landing_fixture(".github")[0] - declared_target = qualification_targets(qualification_fixture())[".github"] - neighboring["labels"] = [ - {"name": SUPERSEDED_STATUS_LABEL if label["name"] == "status:done" else label["name"]} - for label in neighboring["labels"] - ] - self.client.state_updates.clear() - self.client.reachability_requests.clear() - - failures = _audit_state_labels( - self.policy, - self.client, - inventory, - set(), - {(".github", 30): [declared_target]}, - {(".github", 30): [historical_landing]}, - None, - None, - ) - - self.assertEqual("closed", retired["state"]) - self.assertEqual("not_planned", retired["state_reason"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(retired) & STATUS_LABELS) - self.assertNotIn((".github", 30, "open", "reopened"), self.client.state_updates) - self.assertEqual([], self.client.reachability_requests) - self.assertEqual("open", neighboring["state"]) - self.assertEqual({"status:triage"}, label_names(neighboring) & STATUS_LABELS) - self.assertIn((".github", 31, "open", "reopened"), self.client.state_updates) - self.assertTrue(any(".github#31" in failure for failure in failures)) - - def test_trusted_retirement_repairs_reopen_and_label_drift_then_is_quiet(self) -> None: - retired = self.add_issue( - 32, - ["authority:conflict", "authority:github", "kind:defect", "priority:P1", "status:in-progress"], - ) - metadata = [self.metadata(retired, approved=True)] - inventory = {".github": [retired]} - projection = { - (".github", 32): { - "completion_evidence": None, - "public_condition": None, - "state": "superseded", - "transition_at": self.now.isoformat(), - } - } - - self.assertEqual( - [], - reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - lifecycle_projection=projection, - now=self.now, - ), - ) - retirement_comment = self.client.comments[(".github", 32)] - - retired["state"] = "open" - retired["state_reason"] = "reopened" - retired["labels"] = [ - {"name": "status:ready" if label["name"] == SUPERSEDED_STATUS_LABEL else label["name"]} - for label in retired["labels"] - if label["name"] != "authority:github" - ] - metadata[0]["state"] = "open" - metadata[0]["labels"] = sorted(label_names(retired)) - self.client.state_updates.clear() - self.client.replacements.clear() - self.client.comment_updates.clear() - - self.assertEqual( - [], - reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - now=self.now + timedelta(minutes=1), - ), - ) - self.assertEqual("closed", retired["state"]) - self.assertEqual("not_planned", retired["state_reason"]) - self.assertEqual({SUPERSEDED_STATUS_LABEL}, label_names(retired) & STATUS_LABELS) - self.assertNotIn("authority:conflict", label_names(retired)) - self.assertIn("authority:github", label_names(retired)) - self.assertEqual(retirement_comment, self.client.comments[(".github", 32)]) - self.assertEqual([], self.client.comment_updates) - - state_updates = list(self.client.state_updates) - replacements = list(self.client.replacements) - self.assertEqual( - [], - reconcile_public_lifecycle( - self.policy, - self.client, - metadata, - inventory, - now=self.now + timedelta(minutes=2), - ), - ) - self.assertEqual(state_updates, self.client.state_updates) - self.assertEqual(replacements, self.client.replacements) - self.assertEqual([], self.client.comment_updates) - - def test_projection_loader_quarantines_one_bad_identity_and_keeps_valid_state(self) -> None: - projection = { - "generated_at": self.now.isoformat(), - "issues": [ - { - "number": 16, - "repository": ".github", - "state": "claimed", - "transition_at": (self.now - timedelta(minutes=1)).isoformat(), - }, - { - "number": 17, - "public_condition": "private-host-details", - "repository": ".github", - "state": "blocked", - "transition_at": (self.now - timedelta(minutes=1)).isoformat(), - }, - ], - "schema": "durable-workflow.public-lifecycle-projection/v1", - } - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / "projection.json" - _write_evidence(path, projection) - records, failures = load_public_lifecycle_projection( - path, - self.policy, - "durable-workflow-ops", - ) - - self.assertEqual({(".github", 16)}, set(records)) - self.assertEqual(1, len(failures)) - self.assertIn(".github#17", failures[0]) - - def test_projection_loader_binds_verified_release_to_implementation_source(self) -> None: - implementation_source = "a" * 40 - projection = { - "generated_at": self.now.isoformat(), - "issues": [ - { - "completion_evidence": "verified", - "implementation_source": implementation_source, - "number": 20, - "repository": ".github", - "state": "completed", - "transition_at": (self.now - timedelta(minutes=1)).isoformat(), - "verified_release": { - "repository": ".github", - "source_shas": ["b" * 40, implementation_source], - "version": "2026.08.26", - }, - }, - { - "completion_evidence": "verified", - "implementation_source": "c" * 40, - "number": 21, - "repository": ".github", - "state": "completed", - "transition_at": (self.now - timedelta(minutes=1)).isoformat(), - "verified_release": { - "repository": ".github", - "source_shas": ["d" * 40], - "version": "2026.08.26", - }, - }, - ], - "schema": "durable-workflow.public-lifecycle-projection/v1", - } - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / "projection.json" - _write_evidence(path, projection) - records, failures = load_public_lifecycle_projection( - path, - self.policy, - "durable-workflow-ops", - ) - - self.assertEqual({(".github", 20)}, set(records)) - self.assertEqual(implementation_source, records[(".github", 20)]["implementation_source"]) - self.assertEqual(1, len(failures)) - self.assertIn("verified release does not contain", failures[0]) - - def test_closed_historical_issue_without_managed_marker_is_read_only(self) -> None: - apply_backlog(self.policy, self.backlog, self.client) - issue = self.add_issue( - 99, - ["authority:github", "kind:defect", "status:ready"], - ) - issue["state"] = "closed" - issue["state_reason"] = "completed" - metadata = self.metadata(issue, approved=True) - metadata["state"] = "closed" - replacements_before = list(self.client.replacements) - state_updates_before = list(self.client.state_updates) - comments_before = dict(self.client.comments) - - audit_backlog( - self.policy, - self.backlog, - self.client, - public_metadata=[metadata], - audit_time=self.now, - ) - - self.assertEqual(replacements_before, self.client.replacements) - self.assertEqual(state_updates_before, self.client.state_updates) - self.assertEqual(comments_before, self.client.comments) - self.assertEqual({"status:ready"}, label_names(issue) & STATUS_LABELS) - - def test_unapproved_intake_contributes_only_metadata(self) -> None: - issue = intake_issue(labels=["kind:defect"], number=8) - discovery = FakeDiscovery(self.policy, {".github": [(issue, [])]}) - - manifest, inventory = reconstruct_intake(self.policy, discovery) - - self.assertEqual([], manifest["issues"]) - self.assertEqual([], discovery.get_requests) - self.assertEqual([], inventory[".github"]) - public = next(record for record in manifest["public_metadata"] if record["type"] == "issue") - self.assertFalse(public["approved"]) - self.assertNotIn("body", public) - self.assertNotIn("title", public) - - def test_standalone_age_discovery_never_refetches_issue_prose(self) -> None: - unapproved = intake_issue(labels=["kind:defect"], number=12) - approved = intake_issue(author="rmcdaniel", labels=["kind:feature"], number=13) - pull = { - "closed_at": None, - "created_at": "2026-08-26T09:00:00Z", - "merged_at": None, - "number": 14, - "repository": ".github", - "state": "open", - "type": "pull_request", - "updated_at": "2026-08-26T10:00:00Z", - "url": "https://github.com/durable-workflow/.github/pull/14", - } - discovery = FakeDiscovery( - self.policy, - {".github": [(unapproved, []), (approved, [])]}, - pull_requests={".github": [pull]}, - ) - - metadata = discover_public_metadata(self.policy, discovery) - - self.assertEqual([], discovery.get_requests) - self.assertEqual(self.policy["repositories"], discovery.pull_requests_listed) - self.assertEqual(2, len([record for record in metadata if record["type"] == "issue"])) - self.assertEqual(1, len([record for record in metadata if record["type"] == "pull_request"])) - self.assertTrue(all("body" not in record and "title" not in record for record in metadata)) - self.assertNotRegex(ISSUE_INTAKE_QUERY, r"(?m)^\s+(?:body|title)\s*$") - self.assertNotRegex(PULL_REQUEST_METADATA_QUERY, r"(?m)^\s+(?:body|title)\s*$") - workflow = yaml.safe_load((ROOT / ".github/workflows/issue-authority.yml").read_text(encoding="utf-8")) - job = workflow["jobs"]["metadata-age-audit"] - self.assertEqual("read", job["permissions"]["issues"]) - self.assertEqual("read", job["permissions"]["pull-requests"]) - self.assertNotIn("BETA_PRODUCT_WORK_TOKEN", json.dumps(job)) - - def test_verified_completion_closes_within_scheduled_window(self) -> None: - issue = self.add_issue( - 9, - ["authority:github", COMPLETION_VERIFIED_LABEL, "kind:defect", "status:in-progress"], - ) - verified_at = self.now - timedelta(minutes=8) - metadata = self.metadata(issue, approved=True, transition_at=verified_at.isoformat()) - metadata["label_transition_at"] = {COMPLETION_VERIFIED_LABEL: verified_at.isoformat()} - - failures = reconcile_public_lifecycle( - self.policy, - self.client, - [metadata], - {".github": [issue]}, - now=self.now, - ) - audit = build_public_age_audit(self.policy, [metadata], failures, now=self.now) - - self.assertEqual("closed", issue["state"]) - self.assertIn((".github", 9, "closed", "completed"), self.client.state_updates) - self.assertEqual({"status:done"}, label_names(issue) & STATUS_LABELS) - self.assertEqual([], audit["summary"]["issues"]["stale_identities"]) - workflow = yaml.safe_load((ROOT / ".github/workflows/issue-authority.yml").read_text(encoding="utf-8")) - self.assertEqual("*/10 * * * *", workflow[True]["schedule"][0]["cron"]) - - def test_age_audit_reports_issues_and_pull_requests_separately(self) -> None: - issue = self.add_issue(10, ["authority:github", "kind:feature", "status:in-progress"]) - issue_metadata = self.metadata( - issue, - approved=True, - transition_at=(self.now - timedelta(hours=73)).isoformat(), - ) - pull_metadata = { - "closed_at": None, - "created_at": (self.now - timedelta(hours=80)).isoformat(), - "merged_at": None, - "number": 11, - "repository": ".github", - "state": "open", - "type": "pull_request", - "updated_at": (self.now - timedelta(hours=74)).isoformat(), - "url": "https://github.com/durable-workflow/.github/pull/11", - } - - audit = build_public_age_audit(self.policy, [issue_metadata, pull_metadata], [], now=self.now) - - repository = audit["repositories"][".github"] - self.assertEqual({"in-progress": 1}, repository["issues"]["counts_by_state"]) - self.assertEqual({"open": 1}, repository["pull_requests"]["counts_by_state"]) - self.assertEqual("approved-transition-72h", repository["issues"]["stale_identities"][0]["target"]) - self.assertEqual([], repository["pull_requests"]["stale_identities"]) - self.assertEqual(74 * 3600, repository["pull_requests"]["oldest_age_seconds"]) - self.assertEqual(1, repository["issues"]["age_buckets"]["72h-to-7d"]) - self.assertEqual(1, repository["pull_requests"]["age_buckets"]["72h-to-7d"]) - - def test_pipeline_health_orders_oldest_first_escalates_age_and_exempts_external_intake(self) -> None: - ages = [timedelta(days=7, minutes=5), timedelta(days=4), timedelta(days=1)] - priorities = ["priority:P2", "priority:P2", "priority:P1"] - records: list[dict[str, Any]] = [] - for number, (age, priority) in enumerate(zip(ages, priorities, strict=True), start=20): - issue = self.add_issue( - number, - ["authority:github", "kind:defect", priority, "status:ready"], - ) - record = self.metadata(issue, approved=True) - record["created_at"] = (self.now - age).isoformat() - record["last_transition_at"] = (self.now - timedelta(hours=1)).isoformat() - records.append(record) - external = self.add_issue(23, ["kind:defect", "priority:untriaged", "status:triage"]) - external_record = self.metadata(external, approved=False) - external_record["created_at"] = (self.now - timedelta(days=30)).isoformat() - records.append(external_record) - - audit = build_public_age_audit(self.policy, records, [], now=self.now) - - health = audit["pipeline_health"] - self.assertEqual([".github#20", ".github#21", ".github#22"], [row["identity"] for row in health["claim_order"]]) - self.assertTrue(all(row["effective_priority"] == "priority:P1" for row in health["claim_order"])) - self.assertEqual(3, audit["repositories"][".github"]["issues"]["actionable_open_count"]) - self.assertTrue(audit["repositories"][".github"]["issues"]["creation_suppressed"]) - self.assertEqual( - {"identity": ".github#23", "root": ".github#20", "triage_exempt_from_creation_budget": True}, - health["deduplication_candidates"][0], - ) - self.assertTrue(health["dm_notification"]["required"]) - self.assertIn(".github", health["over_budget_repositories"]) - - later = build_public_age_audit(self.policy, records, [], now=self.now + timedelta(minutes=20)) - self.assertFalse(later["pipeline_health"]["dm_notification"]["required"]) - self.assertTrue( - any( - stale["target"] == "product-owner-alert-7d" - for stale in later["summary"]["issues"]["stale_identities"] - ) - ) - - def test_pipeline_health_flags_a_14_day_unattended_placeholder(self) -> None: - issue = self.add_issue( - 24, - ["authority:github", "kind:feature", "priority:P2", "status:ready"], - ) - record = self.metadata(issue, approved=True) - record["created_at"] = (self.now - timedelta(days=14, minutes=5)).isoformat() - - audit = build_public_age_audit(self.policy, [record], [], now=self.now) - - self.assertTrue( - any( - stale["target"] == "unattended-placeholder-14d" - for stale in audit["summary"]["issues"]["stale_identities"] - ) - ) - self.assertTrue(audit["pipeline_health"]["dm_notification"]["required"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_product_train.py b/tests/test_product_train.py deleted file mode 100644 index 3611be9..0000000 --- a/tests/test_product_train.py +++ /dev/null @@ -1,320 +0,0 @@ -from __future__ import annotations - -import copy -import hashlib -import json -import tempfile -import unittest -from pathlib import Path -from unittest import mock - -from jsonschema import Draft202012Validator -from jsonschema.exceptions import ValidationError - -from scripts.beta_candidate import CandidateError -from scripts.product_train import ( - SDK_ARTIFACTS, - SDK_SERVER_EXPERIMENTS, - download_conformance_suite, - load_product_train, - load_sdk_server_qualification, - require_current_product_train, - validate_sdk_server_qualification, -) - -ROOT = Path(__file__).resolve().parents[1] - - -class ProductTrainTest(unittest.TestCase): - def test_payload_codec_contract_is_avro_only_and_blocks_destructive_rollout(self) -> None: - schema = json.loads( - (ROOT / "product-train" / "payload-codec-contract-schema.json").read_text(encoding="utf-8") - ) - contract = json.loads( - (ROOT / "product-train" / "payload-codec-contract.json").read_text(encoding="utf-8") - ) - Draft202012Validator(schema).validate(contract) - - self.assertEqual("2.0.0-rc.30", contract["product_train"]) - self.assertEqual(["avro"], contract["public_payload_codecs"]) - self.assertEqual("json", contract["http_transport"]) - self.assertEqual("unsupported_payload_codec", contract["rejection"]["reason"]) - self.assertFalse(contract["deployment_preflight"]["delete_history"]) - self.assertEqual("prerelease", contract["release_qualification"]["channel"]) - - def test_current_authority_matches_schema_and_one_supported_tuple(self) -> None: - schema = json.loads((ROOT / "product-train" / "schema.json").read_text(encoding="utf-8")) - contract = load_product_train() - Draft202012Validator(schema).validate(contract) - - current = contract["current"] - self.assertEqual("2.0.0-rc.13", current) - supported = [name for name, train in contract["trains"].items() if train["status"] == "supported"] - self.assertEqual([current], supported) - self.assertEqual( - { - "workflow": "2.0.0-rc.12", - "waterline": "2.0.0-rc.9", - "server": "2.0.0-rc.13", - "cli": "2.0.0-rc.12", - "sdk-php": "2.0.0-rc.6", - "sdk-python": "2.0.0-rc.8", - "sdk-rust": "2.0.0-rc.7", - }, - contract["trains"][current]["versions"], - ) - self.assertEqual("2.0.0rc8", contract["trains"][current]["registry_versions"]["sdk-python"]) - - qualification, qualification_raw = load_sdk_server_qualification() - qualification_schema = json.loads( - (ROOT / "product-train" / "sdk-server-qualification-schema.json").read_text(encoding="utf-8") - ) - Draft202012Validator(qualification_schema).validate(qualification) - self.assertEqual("pass", qualification["outcome"]) - self.assertEqual(set(SDK_ARTIFACTS), set(qualification["bindings"])) - self.assertTrue( - all( - binding["experiments"] == list(SDK_SERVER_EXPERIMENTS) for binding in qualification["bindings"].values() - ) - ) - self.assertEqual( - hashlib.sha256(qualification_raw).hexdigest(), - contract["trains"][current]["sdk_server_qualification"]["sha256"], - ) - - install = contract["trains"][current]["install"] - self.assertEqual( - { - "workflow": "composer require durable-workflow/workflow:2.0.0-rc.12@RC", - "sdk-php": "composer require durable-workflow/sdk:2.0.0-rc.6@RC", - "waterline": { - "embedded": ( - "composer require " - "durable-workflow/waterline:2.0.0-rc.9@RC " - "durable-workflow/workflow:2.0.0-rc.12@RC " - "durable-workflow/sdk:2.0.0-rc.6@RC" - ), - "service": "docker pull durableworkflow/waterline:2.0.0-rc.9", - }, - "server": "docker pull durableworkflow/server:2.0.0-rc.13", - "cli": "curl -fsSL https://durable-workflow.com/install.sh | VERSION=2.0.0-rc.12 sh", - "sdk-python": "pip install durable-workflow==2.0.0rc8", - "sdk-rust": "cargo add durable-workflow@=2.0.0-rc.7", - }, - install, - ) - - incomplete = json.loads(json.dumps(contract)) - del incomplete["trains"][current]["install"]["waterline"]["service"] - with self.assertRaises(ValidationError): - Draft202012Validator(schema).validate(incomplete) - - missing_qualification = copy.deepcopy(contract) - del missing_qualification["trains"][current]["sdk_server_qualification"] - with self.assertRaises(ValidationError): - Draft202012Validator(schema).validate(missing_qualification) - - def test_mixed_component_sequences_form_one_exact_train(self) -> None: - contract = json.loads((ROOT / "product-train" / "current.json").read_text(encoding="utf-8")) - plan = json.loads((ROOT / "release-plans" / "current.json").read_text(encoding="utf-8")) - current = "2.0.0-rc.13" - plan_name = "current-2-0-20260801" - versions = { - "workflow": "2.0.0-rc.12", - "waterline": "2.0.0-rc.9", - "server": "2.0.0-rc.13", - "cli": "2.0.0-rc.12", - "sdk-php": "2.0.0-rc.6", - "sdk-python": "2.0.0-rc.8", - "sdk-rust": "2.0.0-rc.7", - } - plan["plan"] = plan_name - for name, version in versions.items(): - plan["components"][name]["version"] = version - plan_raw = (json.dumps(plan, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - qualification_raw = b"{}\n" - train = copy.deepcopy(contract["trains"][contract["current"]]) - train["versions"] = versions - train["registry_versions"] = {**versions, "sdk-python": "2.0.0rc8"} - train["install"] = { - "workflow": "composer require durable-workflow/workflow:2.0.0-rc.12@RC", - "sdk-php": "composer require durable-workflow/sdk:2.0.0-rc.6@RC", - "waterline": { - "embedded": ( - "composer require durable-workflow/waterline:2.0.0-rc.9@RC " - "durable-workflow/workflow:2.0.0-rc.12@RC " - "durable-workflow/sdk:2.0.0-rc.6@RC" - ), - "service": "docker pull durableworkflow/waterline:2.0.0-rc.9", - }, - "server": "docker pull durableworkflow/server:2.0.0-rc.13", - "cli": "curl -fsSL https://durable-workflow.com/install.sh | VERSION=2.0.0-rc.12 sh", - "sdk-python": "pip install durable-workflow==2.0.0rc8", - "sdk-rust": "cargo add durable-workflow@=2.0.0-rc.7", - } - train["release_plan"] = { - "tag": f"release-plan/{plan_name}", - "sha256": hashlib.sha256(plan_raw).hexdigest(), - } - train["sdk_server_qualification"]["sha256"] = hashlib.sha256(qualification_raw).hexdigest() - contract["current"] = current - contract["trains"] = {current: train} - contract["progression"]["prerelease"] = "independent_prerelease_components" - - with tempfile.TemporaryDirectory() as temporary: - directory = Path(temporary) - contract_path = directory / "current.json" - plan_path = directory / "plan.json" - qualification_path = directory / "qualification.json" - contract_path.write_text(json.dumps(contract), encoding="utf-8") - plan_path.write_bytes(plan_raw) - qualification_path.write_bytes(qualification_raw) - - with mock.patch("scripts.product_train.validate_sdk_server_qualification"): - self.assertEqual( - contract, - load_product_train( - contract_path, - current_plan_path=plan_path, - qualification_path=qualification_path, - ), - ) - - contract["trains"][current]["versions"]["sdk-rust"] = "2.0.0-beta.7" - contract_path.write_text(json.dumps(contract), encoding="utf-8") - with self.assertRaisesRegex(CandidateError, "selected channel"): - load_product_train( - contract_path, - current_plan_path=plan_path, - qualification_path=qualification_path, - ) - - def test_sdk_server_qualification_fails_closed(self) -> None: - contract = load_product_train() - current = contract["current"] - release_plan = json.loads((ROOT / "release-plans" / "current.json").read_text(encoding="utf-8")) - plan_reference = contract["trains"][current]["release_plan"] - qualification, _raw = load_sdk_server_qualification() - suite_raw = download_conformance_suite(qualification["evidence"]["source_url"]) - - failed = copy.deepcopy(qualification) - failed["outcome"] = "fail" - failed["bindings"]["sdk-python"]["outcome"] = "fail" - with self.assertRaisesRegex(CandidateError, "invalid or non-passing"): - validate_sdk_server_qualification(failed, release_plan, plan_reference) - - failed_evidence = copy.deepcopy(qualification) - failed_evidence["evidence"]["outcome"] = "fail" - with self.assertRaisesRegex(CandidateError, "invalid conformance evidence"): - validate_sdk_server_qualification( - failed_evidence, - release_plan, - plan_reference, - ) - - mutable_evidence = copy.deepcopy(qualification) - mutable_evidence["evidence"]["source_url"] = ( - "https://raw.githubusercontent.com/durable-workflow/.github/main/" - "product-train/sdk-server-qualification.json" - ) - with self.assertRaisesRegex(CandidateError, "invalid conformance evidence"): - validate_sdk_server_qualification( - mutable_evidence, - release_plan, - plan_reference, - ) - - failed_binding = copy.deepcopy(qualification) - failed_binding["bindings"]["sdk-python"]["outcome"] = "fail" - with self.assertRaisesRegex(CandidateError, "passing exact sdk-python binding"): - validate_sdk_server_qualification( - failed_binding, - release_plan, - plan_reference, - ) - - mismatched = copy.deepcopy(qualification) - mismatched["bindings"]["sdk-rust"]["server"]["source"]["version"] = "2.0.0-beta.18" - with self.assertRaisesRegex(CandidateError, "exact sdk-rust binding"): - validate_sdk_server_qualification(mismatched, release_plan, plan_reference) - - forged_sdk_distribution = copy.deepcopy(qualification) - forged_sdk_distribution["bindings"]["sdk-rust"]["sdk"]["distribution"]["locator"] = ( - "crates.io:durable-workflow@9.9.9" - ) - forged_sdk_distribution["bindings"]["sdk-rust"]["sdk"]["distribution"]["artifacts"][0]["sha256"] = "f" * 64 - with self.assertRaisesRegex(CandidateError, "exact sdk-rust binding"): - validate_sdk_server_qualification( - forged_sdk_distribution, - release_plan, - plan_reference, - suite_result_raw=suite_raw, - ) - - forged_server_distribution = copy.deepcopy(qualification) - forged_server_distribution["bindings"]["sdk-php"]["server"]["distribution"]["locator"] = ( - "oci:docker.io/durableworkflow/server@9.9.9" - ) - forged_server_distribution["bindings"]["sdk-php"]["server"]["distribution"]["artifacts"][0]["sha256"] = "e" * 64 - with self.assertRaisesRegex(CandidateError, "exact sdk-php binding"): - validate_sdk_server_qualification( - forged_server_distribution, - release_plan, - plan_reference, - suite_result_raw=suite_raw, - ) - - missing_experiment_binding = copy.deepcopy(qualification) - missing_experiment_binding["bindings"]["sdk-python"]["experiments"].remove("signals-queries") - with self.assertRaisesRegex(CandidateError, "exact sdk-python binding"): - validate_sdk_server_qualification( - missing_experiment_binding, - release_plan, - plan_reference, - suite_result_raw=suite_raw, - ) - - with self.assertRaisesRegex(CandidateError, "pinned SHA-256"): - validate_sdk_server_qualification( - qualification, - release_plan, - plan_reference, - suite_result_raw=suite_raw + b"\n", - ) - - suite_without_rust = json.loads(suite_raw) - suite_without_rust["experiments"]["replay"]["required_clients"].remove("sdk-rust") - suite_without_rust_raw = ( - json.dumps(suite_without_rust, indent=2, sort_keys=True, ensure_ascii=True) + "\n" - ).encode() - weak_qualification = copy.deepcopy(qualification) - weak_qualification["evidence"]["sha256"] = hashlib.sha256(suite_without_rust_raw).hexdigest() - with self.assertRaisesRegex(CandidateError, "experiment replay"): - validate_sdk_server_qualification( - weak_qualification, - release_plan, - plan_reference, - suite_result_raw=suite_without_rust_raw, - ) - - stale_suite = json.loads(suite_raw) - stale_suite["artifact_tuple"]["sdk-python"]["version"] = "2.0.0-beta.18" - stale_suite_raw = (json.dumps(stale_suite, indent=2, sort_keys=True, ensure_ascii=True) + "\n").encode() - stale_qualification = copy.deepcopy(qualification) - stale_qualification["evidence"]["sha256"] = hashlib.sha256(stale_suite_raw).hexdigest() - with self.assertRaisesRegex(CandidateError, "exact release-plan source tuple"): - validate_sdk_server_qualification( - stale_qualification, - release_plan, - plan_reference, - suite_result_raw=stale_suite_raw, - ) - - def test_new_prerelease_tuple_must_match_current_train(self) -> None: - versions = load_product_train()["trains"]["2.0.0-rc.13"]["versions"] - components = {name: {"version": version, "commit": "a" * 40} for name, version in versions.items()} - self.assertEqual("2.0.0-rc.13", require_current_product_train(components)) - - components["cli"]["version"] = "0.1.95" - with self.assertRaisesRegex(CandidateError, "supported product train 2.0.0-rc.13"): - require_current_product_train(components) diff --git a/tests/test_protected_release_plan_writer.py b/tests/test_protected_release_plan_writer.py deleted file mode 100644 index e3a3285..0000000 --- a/tests/test_protected_release_plan_writer.py +++ /dev/null @@ -1,262 +0,0 @@ -from __future__ import annotations - -import unittest -from pathlib import Path -from typing import Any - -import yaml - -from scripts.protected_release_plan_writer import ( - AUTHORITY_REF, - CONTROL_REPOSITORY, - PROTECTED_WORKFLOWS, - ProtectedWriterError, - approved_writer_handoff, - expected_workflow_ref, - validate_approved_writer_handoff, -) - -ROOT = Path(__file__).resolve().parents[1] -WORKFLOW_ROOT = ROOT / ".github" / "workflows" -PLAN_REGISTRY_GROUP = "release-plan-registry" -WRITER_CONCURRENCY = { - "group": PLAN_REGISTRY_GROUP, - "cancel-in-progress": False, -} -WORKFLOW_CONTRACTS = { - "release-plan.yml": { - "approval": "authorize", - "environment": "beta-authorization", - "permissions": {"contents": "read"}, - "writer": "validate-and-record", - }, - "release-plan-supersession.yml": { - "approval": "qualify", - "environment": "release-plan-supersession", - "permissions": { - "actions": "read", - "attestations": "read", - "contents": "read", - }, - "writer": "record", - }, -} - - -def load_workflow(name: str) -> dict[str, Any]: - return yaml.safe_load((WORKFLOW_ROOT / name).read_text(encoding="utf-8")) - - -def handoff_identity(workflow: str) -> dict[str, Any]: - return { - "workflow": workflow, - "repository": CONTROL_REPOSITORY, - "ref": f"refs/heads/{AUTHORITY_REF}", - "workflow_ref": expected_workflow_ref(workflow), - "source_sha": "a" * 40, - "run_id": 123456789, - "producer_attempt": 1, - } - - -def schedule_observer( - name: str, - running: str | None, - pending: str | None, - superseded: list[str], -) -> tuple[str, str | None]: - if running is None: - return name, pending - if pending is not None: - superseded.append(pending) - return running, name - - -class ProtectedReleasePlanWriterTest(unittest.TestCase): - def test_handoff_is_exact_and_safe_for_failed_writer_retries(self) -> None: - for workflow in sorted(PROTECTED_WORKFLOWS): - with self.subTest(workflow=workflow): - identity = handoff_identity(workflow) - handoff = approved_writer_handoff(**identity) - self.assertRegex(handoff, r"^[0-9a-f]{64}$") - self.assertEqual(handoff, approved_writer_handoff(**identity)) - validate_approved_writer_handoff( - handoff, - **identity, - current_attempt=2, - ) - - mismatches = ( - ({"handoff": "0" * 64}, "does not match"), - ({"source_sha": "b" * 40}, "does not match"), - ({"run_id": identity["run_id"] + 1}, "does not match"), - ( - { - "workflow_ref": expected_workflow_ref( - next(name for name in PROTECTED_WORKFLOWS if name != workflow) - ) - }, - "workflow mismatch", - ), - ({"producer_attempt": 2}, "does not match"), - ( - {"producer_attempt": 3, "current_attempt": 2}, - "newer than", - ), - ) - for changes, diagnostic in mismatches: - arguments = { - **identity, - "handoff": handoff, - "current_attempt": 2, - **changes, - } - with ( - self.subTest(changes=changes), - self.assertRaisesRegex(ProtectedWriterError, diagnostic), - ): - validate_approved_writer_handoff(**arguments) - - def test_handoff_rejects_non_authoritative_runtime_identity(self) -> None: - identity = handoff_identity("release-plan.yml") - mismatches = ( - ({"workflow": "outside.yml"}, "not allowlisted"), - ({"repository": "outside/fork"}, "repository mismatch"), - ({"ref": "refs/heads/feature"}, "ref mismatch"), - ({"source_sha": "A" * 40}, "full lowercase commit"), - ({"run_id": 0}, "positive integer"), - ) - for changes, diagnostic in mismatches: - with ( - self.subTest(changes=changes), - self.assertRaisesRegex(ProtectedWriterError, diagnostic), - ): - approved_writer_handoff(**{**identity, **changes}) - - def test_protected_waits_are_read_only_and_outside_the_writer_lock(self) -> None: - for workflow_name, contract in WORKFLOW_CONTRACTS.items(): - with self.subTest(workflow=workflow_name): - workflow = load_workflow(workflow_name) - approval = workflow["jobs"][contract["approval"]] - writer = workflow["jobs"][contract["writer"]] - - self.assertNotIn("concurrency", workflow) - self.assertEqual(contract["environment"], approval["environment"]) - self.assertEqual(contract["permissions"], approval["permissions"]) - self.assertNotIn("write", approval["permissions"].values()) - self.assertNotIn("concurrency", approval) - approval_checkout = next( - step for step in approval["steps"] if "actions/checkout" in step.get("uses", "") - ) - self.assertIs(approval_checkout["with"]["persist-credentials"], False) - self.assertEqual("${{ github.sha }}", approval_checkout["with"]["ref"]) - - self.assertEqual(contract["approval"], writer["needs"]) - self.assertIn( - f"needs.{contract['approval']}.result == 'success'", - writer["if"], - ) - self.assertNotIn("environment", writer) - self.assertEqual(WRITER_CONCURRENCY, writer["concurrency"]) - self.assertEqual("write", writer["permissions"]["contents"]) - for job_name, job in workflow["jobs"].items(): - if job_name != contract["writer"]: - self.assertNotIn("concurrency", job) - - def test_writers_validate_the_exact_approved_job_before_mutation(self) -> None: - mutation_steps = { - "release-plan.yml": "Create or compare the immutable Git record", - "release-plan-supersession.yml": "Create or compare immutable terminal record", - } - for workflow_name, contract in WORKFLOW_CONTRACTS.items(): - with self.subTest(workflow=workflow_name): - workflow = load_workflow(workflow_name) - approval = workflow["jobs"][contract["approval"]] - writer = workflow["jobs"][contract["writer"]] - approval_step = next(step for step in approval["steps"] if step.get("id") == "approval") - validation = next( - step - for step in writer["steps"] - if "protected_release_plan_writer.py validate" in step.get("run", "") - ) - mutation = next(step for step in writer["steps"] if step.get("name") == mutation_steps[workflow_name]) - - self.assertIn("protected_release_plan_writer.py create", approval_step["run"]) - self.assertIn(f"--workflow {workflow_name}", approval_step["run"]) - for option, value in ( - ("--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"'), - ): - self.assertIn(f"{option} {value}", approval_step["run"]) - self.assertIn(f"--workflow {workflow_name}", validation["run"]) - for option in ( - "--repository", - "--ref", - "--workflow-ref", - "--source-sha", - "--run-id", - "--current-attempt", - "--producer-attempt", - "--handoff", - ): - self.assertIn(option, validation["run"]) - self.assertLess(writer["steps"].index(validation), writer["steps"].index(mutation)) - - def test_pending_reviews_do_not_expand_the_scheduled_observer_queue(self) -> None: - observer = load_workflow("release-plan-observer.yml") - self.assertEqual(WRITER_CONCURRENCY, observer["concurrency"]) - - for workflow_name in WORKFLOW_CONTRACTS: - with self.subTest(pending_workflow=workflow_name): - # The protected job is pending and owns no concurrency group. - running: str | None = None - pending: str | None = None - superseded: list[str] = [] - - for observer_run in ( - "scheduled-observer-1", - "scheduled-observer-2", - "scheduled-observer-3", - ): - running, pending = schedule_observer( - observer_run, - running, - pending, - superseded, - ) - - self.assertEqual("scheduled-observer-1", running) - self.assertEqual("scheduled-observer-3", pending) - self.assertEqual(["scheduled-observer-2"], superseded) - self.assertEqual(2, len({running, pending} - {None})) - - running, pending = pending, None - self.assertEqual("scheduled-observer-3", running) - running = None - self.assertIsNone(running) - self.assertIsNone(pending) - - def test_continuity_dispatch_consumes_only_the_successful_writer(self) -> None: - workflow = load_workflow("release-plan.yml") - dispatch = workflow["jobs"]["dispatch-accepted-continuity"] - - self.assertEqual("validate-and-record", dispatch["needs"]) - self.assertIn( - "needs.validate-and-record.outputs.channel != 'rc'", - dispatch["if"], - ) - self.assertEqual( - {"actions": "write", "contents": "read"}, - dispatch["permissions"], - ) - self.assertNotIn("environment", dispatch) - self.assertNotIn("concurrency", dispatch) - self.assertNotIn("needs.authorize", str(dispatch)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_qualification_policy.py b/tests/test_qualification_policy.py index 15d8999..1186ff8 100644 --- a/tests/test_qualification_policy.py +++ b/tests/test_qualification_policy.py @@ -1,1901 +1,141 @@ from __future__ import annotations import copy -import hashlib -import io import json -import os -import re -import subprocess import tempfile -import textwrap import unittest -import urllib.error -import urllib.parse -import urllib.request -from contextlib import redirect_stderr from pathlib import Path -from typing import Any -from unittest.mock import patch from scripts.qualification_policy import ( - EXPECTED_PUBLIC_AUDIT_TARGETS, - EXPECTED_TARGETS, - INFRASTRUCTURE_EXIT_CODE, - GitHubClient, - GitHubInfrastructureError, PolicyError, - _latest_check_runs, - audit_policy, - main, scan_workflow_sources, + validate_local_action_references, validate_policy, - verify_workflow_source, ) ROOT = Path(__file__).resolve().parents[1] -CACHE_V5_PIN = "caa296126883cff596d87d8935842f9db880ef25" -CACHE_V6_PIN = "55cc8345863c7cc4c66a329aec7e433d2d1c52a9" -CHECKOUT_PIN = "d23441a48e516b6c34aea4fa41551a30e30af803" -CHECKOUT_V7_PIN = "3d3c42e5aac5ba805825da76410c181273ba90b1" -DOCKER_LOGIN_V46_PIN = "dbcb813823bdd20940b903addbd779551569679f" -SETUP_BUILDX_V43_PIN = "37fe631027851001ddb9b187196cc803df7f5f0e" -SETUP_PYTHON_V7_PIN = "5fda3b95a4ea91299a34e894583c3862153e4b97" -class FakeResponse: - def __init__(self, payload: bytes) -> None: - self.payload = payload +def policy_fixture() -> dict[str, object]: + return json.loads((ROOT / "qualification/policy.json").read_text(encoding="utf-8")) - def __enter__(self) -> FakeResponse: - return self - def __exit__(self, *args: object) -> None: - return None - - def read(self) -> bytes: - return self.payload - - -def http_error(status: int, body: bytes = b"error", **headers: str) -> urllib.error.HTTPError: - return urllib.error.HTTPError( - "https://api.github.com/repos/durable-workflow/cli", - status, - "request failed", - headers, - io.BytesIO(body), - ) - - -def policy_fixture() -> dict[str, Any]: - return json.loads((ROOT / "qualification" / "policy.json").read_text(encoding="utf-8")) - - -class FakeGitHubClient: - def __init__(self, policy: dict[str, Any]) -> None: - self.policy = policy - self.targets_by_repository = {target["repository"]: target for target in policy["targets"].values()} - - @staticmethod - def _repository(path: str) -> str: - match = re.match(r"/repos/durable-workflow/([^/]+)", path) - if not match: - raise AssertionError(f"unexpected API path: {path}") - return urllib.parse.unquote(match.group(1)) - - def json(self, path: str) -> Any: - if re.match(r"/repos/[^/]+/[^/]+/commits/[0-9a-f]{40}$", path): - return {"sha": path.rsplit("/", 1)[1]} - repository = self._repository(path) - target = self.targets_by_repository[repository] - if path == f"/repos/durable-workflow/{repository}": - return {"default_branch": target["branch"]} - if "/contents/.github/workflows?" in path: - paths = {f".github/workflows/{workflow['path']}" for workflow in target["workflows"]} - paths.add(".github/workflows/release.yml") - if repository == "sample-app": - paths.add(".github/workflows/devcontainer-image.yml") - if repository == ".github": - paths.add(".github/workflows/beta-conformance-retention.yml") - paths.add(".github/workflows/beta-continuity-resolution.yml") - return [{"path": workflow_path, "type": "file"} for workflow_path in sorted(paths)] - if "/actions/workflows/" in path: - workflow = urllib.parse.unquote(path.rsplit("/", 1)[1]) - return {"id": len(workflow), "path": f".github/workflows/{workflow}", "state": "active"} - if "/rules/branches/" in path: - return [ - { - "type": "required_status_checks", - "parameters": { - "required_status_checks": [ - {"context": workflow["required_check"]} for workflow in target["workflows"] - ], - "strict_required_status_checks_policy": True, - }, - } - ] - if "/branches/" in path: - return {"commit": {"sha": "a" * 40}} - raise AssertionError(f"unexpected API path: {path}") - - def bytes(self, path: str) -> bytes: - if re.match(r"/repos/[^/]+/[^/]+/contents/action\.ya?ml\?", path): - return b"name: checkout\nruns:\n using: node24\n main: dist/index.js\n" - if "/contents/.github/workflows/beta-continuity-resolution.yml?" in path: - return ( - ROOT / ".github" / "workflows" / "beta-continuity-resolution.yml" - ).read_bytes() - if "/contents/.github/workflows/beta-conformance-retention.yml?" in path: - return f"""name: Beta conformance retention -on: - workflow_run: - workflows: [Beta conformance] - types: [completed] -permissions: - contents: read -jobs: - bind: - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - outputs: - source_run_id: ${{{{ github.event.workflow_run.id }}}} - source_run_attempt: ${{{{ github.event.workflow_run.run_attempt }}}} - steps: - - uses: actions/checkout@{CHECKOUT_PIN} # v6 - with: - persist-credentials: false - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - env: - GH_TOKEN: ${{{{ github.token }}}} - REQUESTED_RUN_ID: ${{{{ inputs.source_run_id || github.event.workflow_run.id }}}} - REQUESTED_RUN_ATTEMPT: ${{{{ inputs.source_run_attempt || github.event.workflow_run.run_attempt }}}} - 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: - needs: bind - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - environment: beta-conformance - permissions: - contents: write - steps: - - uses: actions/checkout@{CHECKOUT_PIN} # v6 - with: - persist-credentials: false - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - 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 - - 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 - - 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" -""".encode() - repository = self._repository(path) - target = self.targets_by_repository[repository] - branch = target["branch"] - workflow_contract = next( - (workflow for workflow in target["workflows"] if f"/{workflow['path']}?" in path), - None, - ) - required_check = workflow_contract["required_check"] if workflow_contract else "Fixture qualification" - checkout_pin, checkout_version = ( - (CHECKOUT_V7_PIN, "v7.0.1") if repository == "sample-app" else (CHECKOUT_PIN, "v6") - ) - source = f"""name: qualification +def workflow(*steps: str, trigger: str = "pull_request", permissions: str = "contents: read") -> str: + rendered_steps = "\n".join(f" {line}" for step in steps for line in step.splitlines()) + return f"""name: test on: - push: - branches: [{branch}] - pull_request: - branches: [{branch}] - workflow_dispatch: + {trigger}: permissions: - contents: read + {permissions} jobs: -""" - if workflow_contract and workflow_contract.get("action_policy_preflight") is True: - target_name = next( - name for name, candidate in self.policy["targets"].items() if candidate is target - ) - source += f""" action-policy: - name: Central action policy preflight - if: ${{{{ github.server_url == 'https://github.com' }}}} - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Check out candidate source - uses: actions/checkout@{checkout_pin} # {checkout_version} - with: - persist-credentials: false - - name: Check out central action policy - uses: actions/checkout@{checkout_pin} # {checkout_version} - with: - repository: durable-workflow/.github - ref: main - path: .central-action-policy - persist-credentials: false - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.13" - - name: Install policy dependencies - run: python -m pip install PyYAML==6.0.2 - - name: Require centrally approved immutable action commits - run: >- - python .central-action-policy/scripts/qualification_policy.py validate - --policy .central-action-policy/qualification/policy.json - --target {target_name} - --workflow-directory .github/workflows test: - name: Workload - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - fail-fast: false - steps: - - uses: actions/checkout@{checkout_pin} # {checkout_version} -""" - else: - source += f""" test: - name: {json.dumps(required_check)} - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - fail-fast: false - steps: - - uses: actions/checkout@{checkout_pin} # {checkout_version} -""" - if repository == "sample-app" and "/contents/.github/workflows/ci.yml?" in path: - source += f""" - uses: actions/cache@{CACHE_V6_PIN} # v6.1.0 - with: - path: vendor - key: ${{{{ github.event_name }}}}-dependencies -""" - if repository == "sample-app" and "/contents/.github/workflows/devcontainer-image.yml?" in path: - source += f""" - uses: docker/login-action@{DOCKER_LOGIN_V46_PIN} # v4.6.0 - with: - registry: ghcr.io -""" - if workflow_contract and workflow_contract.get("action_policy_preflight") is True: - source += f""" qualification: - name: {json.dumps(required_check)} - needs: [action-policy, test] - if: ${{{{ always() }}}} runs-on: ubuntu-latest timeout-minutes: 5 steps: - - name: Require central action policy preflight - if: ${{{{ github.server_url == 'https://github.com' }}}} - env: - ACTION_POLICY_RESULT: ${{{{ needs.action-policy.result }}}} - run: test "$ACTION_POLICY_RESULT" = success +{rendered_steps} """ - return source.encode() - - def collection(self, path: str, key: str) -> list[dict[str, Any]]: - self._repository(path) - if key != "check_runs": - raise AssertionError(f"unexpected collection: {key}") - repository = self._repository(path) - return [ - { - "conclusion": "success", - "id": index + 1, - "name": workflow["required_check"], - "status": "completed", - } - for index, workflow in enumerate(self.targets_by_repository[repository]["workflows"]) - ] - - def list_collection(self, path: str) -> list[dict[str, Any]]: - result = self.json(path) - if not isinstance(result, list): - raise AssertionError(f"unexpected non-list collection: {path}") - return result class QualificationPolicyTest(unittest.TestCase): - @staticmethod - def trusted_pull_request_source() -> str: - return f"""name: trust fixture -on: - pull_request: -permissions: - contents: read -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@{CHECKOUT_PIN} # v6 - - uses: actions/cache@{CACHE_V5_PIN} # v5 - with: - path: vendor - key: ${{{{ github.event_name }}}}-dependencies - restore-keys: ${{{{ github.event_name }}}}- -""" - - @staticmethod - def trusted_privileged_artifact_source() -> str: - validator = textwrap.indent( - policy_fixture()["workflow_trust"]["privileged_artifact_handoffs"]["validator_command"].rstrip(), - " ", - ) - return f"""name: trusted publication -on: - workflow_dispatch: -permissions: - contents: read -jobs: - package: - runs-on: ubuntu-latest - outputs: - artifact_id: ${{{{ steps.release.outputs.artifact-id }}}} - artifact_digest: ${{{{ steps.release.outputs.artifact-digest }}}} - source_run_id: ${{{{ github.run_id }}}} - source_run_attempt: ${{{{ github.run_attempt }}}} - steps: - - id: release - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - archive: false - if-no-files-found: error - path: release.tar - publish: - needs: package - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@{CHECKOUT_PIN} # v6 - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{{{ github.sha }}}} - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - artifact-ids: ${{{{ needs.package.outputs.artifact_id }}}} - digest-mismatch: error - github-token: ${{{{ github.token }}}} - path: isolated-release - repository: ${{{{ github.repository }}}} - run-id: ${{{{ needs.package.outputs.source_run_id }}}} - - name: Validate the exact producer artifact before use - env: - ARTIFACT_DIRECTORY: isolated-release - EXPECTED_ARTIFACT_DIGEST: ${{{{ needs.package.outputs.artifact_digest }}}} - EXPECTED_ARTIFACT_ID: ${{{{ needs.package.outputs.artifact_id }}}} - EXPECTED_SOURCE_RUN_ATTEMPT: ${{{{ needs.package.outputs.source_run_attempt }}}} - EXPECTED_SOURCE_RUN_ID: ${{{{ needs.package.outputs.source_run_id }}}} - run: | -{validator} -""" + def setUp(self) -> None: + self.policy = policy_fixture() - def test_workflow_trust_accepts_partitioned_unprivileged_pull_requests(self) -> None: - evidence = scan_workflow_sources( - policy_fixture(), - "cli", - {".github/workflows/fixture.yml": self.trusted_pull_request_source()}, - ) - self.assertEqual([], evidence[".github/workflows/fixture.yml"]["privileged_jobs"]) - - def test_workflow_trust_admits_only_the_reviewed_checkout_v7_release(self) -> None: - policy = policy_fixture() - source = self.trusted_pull_request_source().replace( - f"actions/checkout@{CHECKOUT_PIN} # v6", - f"actions/checkout@{CHECKOUT_V7_PIN} # v7.0.1", + def scan(self, source: str) -> dict[str, dict[str, object]]: + return scan_workflow_sources( + self.policy, + "github-control-plane", + {".github/workflows/test.yml": source}, ) - evidence = scan_workflow_sources(policy, "cli", {"fixture.yml": source}) + def approved_action(self, repository: str) -> tuple[str, str]: + releases = self.policy["action_runtime"]["allowed_releases"][repository] + commit, version = next(iter(releases.items())) + return commit, version + def test_checked_in_policy_is_valid_and_has_no_retired_consumers(self) -> None: + validate_policy(self.policy) self.assertEqual( - { - "11d5960a326750d5838078e36cf38b85af677262": "v4", - CHECKOUT_PIN: "v6", - CHECKOUT_V7_PIN: "v7.0.1", - }, - policy["action_runtime"]["allowed_releases"]["actions/checkout"], - ) - self.assertIn( - f"actions/checkout@{CHECKOUT_V7_PIN}", - evidence["fixture.yml"]["external_actions"], + {}, + self.policy["workflow_trust"]["privileged_workflow_run_consumers"], ) - - rejected = { - "floating v7 tag": ( - source.replace(f"@{CHECKOUT_V7_PIN}", "@v7"), - "not pinned to a full commit SHA", - ), - "arbitrary v7 commit": ( - source.replace(CHECKOUT_V7_PIN, "f" * 40), - "is not centrally approved", - ), - } - for name, (candidate, message) in rejected.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, message): - scan_workflow_sources(policy, "cli", {"fixture.yml": candidate}) - - def test_workflow_trust_admits_only_the_exact_cache_v610_release(self) -> None: - policy = policy_fixture() - source = self.trusted_pull_request_source().replace( - f"actions/cache@{CACHE_V5_PIN} # v5", - f"actions/cache@{CACHE_V6_PIN} # v6.1.0", - ) - - evidence = scan_workflow_sources(policy, "cli", {"fixture.yml": source}) - self.assertEqual( - { - CACHE_V5_PIN: "v5", - CACHE_V6_PIN: "v6.1.0", - }, - policy["action_runtime"]["allowed_releases"]["actions/cache"], - ) - self.assertIn( - f"actions/cache@{CACHE_V6_PIN}", - evidence["fixture.yml"]["external_actions"], - ) - - rejected = { - "floating v6 tag": ( - source.replace(f"@{CACHE_V6_PIN}", "@v6"), - "not pinned to a full commit SHA", - ), - "unknown cache commit": ( - source.replace(CACHE_V6_PIN, "f" * 40), - "is not centrally approved", - ), - } - for name, (candidate, message) in rejected.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, message): - scan_workflow_sources(policy, "cli", {"fixture.yml": candidate}) - - def test_workflow_trust_admits_only_the_reviewed_sample_app_action_releases(self) -> None: - policy = policy_fixture() - source = ( - self.trusted_pull_request_source() - + f""" - uses: actions/setup-python@{SETUP_PYTHON_V7_PIN} # v7 - with: - python-version: "3.13" - - uses: docker/setup-buildx-action@{SETUP_BUILDX_V43_PIN} # v4 -""" + [{ + "matrix_independent": False, + "path": "ci.yml", + "required_check": "Shared contract tests", + }], + self.policy["targets"]["github-control-plane"]["workflows"], + ) + + def test_current_control_repository_workflows_satisfy_policy(self) -> None: + actions = validate_local_action_references( + self.policy, + ROOT / ".github/workflows", + "github-control-plane", ) + self.assertTrue(actions) + self.assertTrue(all("@" in action for action in actions)) - evidence = scan_workflow_sources(policy, "sample-app", {"fixture.yml": source}) - + def test_approved_immutable_action_with_version_comment_is_accepted(self) -> None: + commit, version = self.approved_action("actions/checkout") + evidence = self.scan(workflow(f"- uses: actions/checkout@{commit} # {version}")) self.assertEqual( - "v7", - policy["action_runtime"]["allowed_releases"]["actions/setup-python"][SETUP_PYTHON_V7_PIN], - ) - self.assertEqual( - "v4", - policy["action_runtime"]["allowed_releases"]["docker/setup-buildx-action"][SETUP_BUILDX_V43_PIN], - ) - self.assertIn(f"actions/setup-python@{SETUP_PYTHON_V7_PIN}", evidence["fixture.yml"]["external_actions"]) - self.assertIn( - f"docker/setup-buildx-action@{SETUP_BUILDX_V43_PIN}", - evidence["fixture.yml"]["external_actions"], + [f"actions/checkout@{commit}"], + evidence[".github/workflows/test.yml"]["external_actions"], ) - rejected = { - "floating setup-python v7 tag": ( - source.replace(f"@{SETUP_PYTHON_V7_PIN}", "@v7"), - "not pinned to a full commit SHA", - ), - "arbitrary setup-python commit": ( - source.replace(SETUP_PYTHON_V7_PIN, "f" * 40), - "is not centrally approved", - ), - "floating setup-buildx v4 tag": ( - source.replace(f"@{SETUP_BUILDX_V43_PIN}", "@v4"), - "not pinned to a full commit SHA", - ), - "arbitrary setup-buildx commit": ( - source.replace(SETUP_BUILDX_V43_PIN, "f" * 40), - "is not centrally approved", - ), - } - for name, (candidate, message) in rejected.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, message): - scan_workflow_sources(policy, "sample-app", {"fixture.yml": candidate}) - - def test_workflow_trust_rejects_mutable_actions_and_missing_version_comments(self) -> None: - source = self.trusted_pull_request_source() - cases = { - "mutable": ( - source.replace(f"@{CHECKOUT_PIN} # v6", "@v6 # v6"), - "not pinned to a full commit SHA", - ), - "unlabeled": (source.replace(" # v6", "", 1), "readable version comment"), - } - for name, (candidate, message) in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, message): - scan_workflow_sources(policy_fixture(), "cli", {"fixture.yml": candidate}) - - def test_workflow_trust_distinguishes_local_actions_and_pins_container_actions(self) -> None: - source = self.trusted_pull_request_source().replace( - f"- uses: actions/checkout@{CHECKOUT_PIN} # v6", - "- uses: ./actions/verify # local", - ) - evidence = scan_workflow_sources(policy_fixture(), "cli", {"fixture.yml": source}) - self.assertEqual(["./actions/verify"], evidence["fixture.yml"]["local_actions"]) + def test_mutable_and_unknown_action_references_are_rejected(self) -> None: + for reference in ("main", "a" * 40): + with self.subTest(reference=reference), self.assertRaises(PolicyError): + self.scan(workflow(f"- uses: actions/checkout@{reference} # unknown")) + def test_action_pin_requires_its_reviewed_version_comment(self) -> None: + commit, _version = self.approved_action("actions/checkout") with self.assertRaisesRegex(PolicyError, "readable version comment"): - scan_workflow_sources( - policy_fixture(), - "cli", - {"fixture.yml": source.replace(" # local", "")}, - ) - - mutable_container = source.replace( - "- uses: ./actions/verify # local", - "- uses: docker://lycheeverse/lychee:0.24.2 # 0.24.2", - ) - with self.assertRaisesRegex(PolicyError, "immutable sha256 digest"): - scan_workflow_sources(policy_fixture(), "cli", {"fixture.yml": mutable_container}) - - def test_workflow_trust_rejects_top_level_write_and_pull_request_target(self) -> None: - source = self.trusted_pull_request_source() - cases = { - "top-write": (source.replace("contents: read", "contents: write", 1), "top-level write"), - "target-event": ( - source.replace("pull_request:", "pull_request_target:"), - "forbidden pull_request_target", - ), - } - for name, (candidate, message) in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, message): - scan_workflow_sources(policy_fixture(), "cli", {"fixture.yml": candidate}) - - def test_workflow_trust_rejects_pull_request_credentials_and_shared_caches(self) -> None: - source = self.trusted_pull_request_source() - cases = { - "environment": ( - source.replace("runs-on: ubuntu-latest", "runs-on: ubuntu-latest\n environment: release\n"), - "requests an environment", - ), - "secret": ( - source.replace( - "runs-on: ubuntu-latest", - "runs-on: ubuntu-latest\n env:\n TOKEN: ${{ secrets['RELEASE_TOKEN'] }}", - ), - "references a secret", - ), - "shared-cache": ( - source.replace("${{ github.event_name }}-dependencies", "shared-dependencies"), - "must partition trusted and untrusted events", - ), - "constant-boolean-cache-key": ( - source.replace( - "${{ github.event_name }}-dependencies", - "shared-${{ github.event_name != '' }}", - ), - "must partition trusted and untrusted events", - ), - "constant-boolean-cache-restore": ( - source.replace( - "${{ github.event_name }}-", - "shared-${{ github.event_name != '' }}", - ), - "must partition trusted and untrusted events", - ), - "partially-shared-cache-restore": ( - source.replace( - "restore-keys: ${{ github.event_name }}-", - "restore-keys: |\n ${{ github.event_name }}-\n shared-", - ), - "must partition trusted and untrusted events", - ), - "workspace-root-cache": ( - source.replace("path: vendor", "path: /"), - "unsafe cache path", - ), - } - for name, (candidate, message) in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, message): - scan_workflow_sources(policy_fixture(), "cli", {"fixture.yml": candidate}) - - def test_workflow_trust_rejects_whole_workspace_and_home_cache_aliases(self) -> None: - source = self.trusted_pull_request_source() - unsafe_paths = ( - "${{ github.workspace }}", - "${{ github.workspace }}/**", - "${{ env.GITHUB_WORKSPACE }}", - "$GITHUB_WORKSPACE", - "${GITHUB_WORKSPACE}", - "${{ env.HOME }}", - "$HOME", - "${HOME}", - "**", - "~/**", - "$HOME/**", - ) - for path in unsafe_paths: - with self.subTest(path=path), self.assertRaisesRegex(PolicyError, "unsafe cache path"): - scan_workflow_sources( - policy_fixture(), - "cli", - {"fixture.yml": source.replace("path: vendor", f"path: '{path}'")}, - ) - - narrow = source.replace( - "path: vendor", - "path: |\n vendor\n $HOME/.cache/composer\n ~/.cache/pip", - ) - scan_workflow_sources(policy_fixture(), "cli", {"fixture.yml": narrow}) - - def test_privileged_manual_dispatch_requires_a_protected_ref(self) -> None: - source = self.trusted_privileged_artifact_source() - cases = { - "missing": source.replace(" if: github.ref == 'refs/heads/main'\n", ""), - "wrong ref": source.replace("refs/heads/main", "refs/heads/topic"), - "bypass": source.replace( - "github.ref == 'refs/heads/main'", - "github.ref == 'refs/heads/main' || inputs.force", - ), - } - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, "outside refs/heads/main"): - scan_workflow_sources(policy_fixture(), "cli", {"unsafe.yml": candidate}) - - tag_or_dispatch = source.replace( - "github.ref == 'refs/heads/main'", - "(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || " - "(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')", - ) - scan_workflow_sources(policy_fixture(), "cli", {"trusted.yml": tag_or_dispatch}) - - def test_reusable_workflow_credentials_require_a_protected_dispatch_ref(self) -> None: - local_call = "uses: ./.github/workflows/release.yml # local" - external_call = ( - "uses: actions/checkout/.github/workflows/release.yml@" - f"{CHECKOUT_PIN} # v6" - ) - - def reusable_call(uses: str, secrets: str = "", condition: str = "") -> str: - return f"""name: reusable workflow caller -on: - workflow_dispatch: -permissions: - contents: read -jobs: - release: - {uses} -{condition}{secrets}""" - - unguarded = { - "local inherit": reusable_call(local_call, " secrets: inherit\n"), - "external inherit": reusable_call(external_call, " secrets: inherit\n"), - "explicit map": reusable_call( - external_call, - " secrets:\n package-token: ${{ github.token }}\n", - ), - "empty map": reusable_call(local_call, " secrets: {}\n"), - } - for name, source in unguarded.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, "outside refs/heads/main"): - scan_workflow_sources(policy_fixture(), "cli", {"unsafe.yml": source}) - - protected = reusable_call( - local_call, - " secrets: inherit\n", - " if: github.ref == 'refs/heads/main'\n", - ) - protected_evidence = scan_workflow_sources(policy_fixture(), "cli", {"protected.yml": protected}) - self.assertEqual(["release"], protected_evidence["protected.yml"]["privileged_jobs"]) - - credential_free = reusable_call(local_call) - credential_free_evidence = scan_workflow_sources( - policy_fixture(), - "cli", - {"credential-free.yml": credential_free}, - ) - self.assertEqual([], credential_free_evidence["credential-free.yml"]["privileged_jobs"]) - - def test_pull_request_reusable_workflows_cannot_receive_credentials(self) -> None: - source = """name: untrusted reusable workflow caller -on: - pull_request: -permissions: - contents: read -jobs: - release: - uses: ./.github/workflows/release.yml # local -""" - declarations = { - "inherit": " secrets: inherit\n", - "explicit map": " secrets:\n package-token: ${{ github.token }}\n", - } - for name, declaration in declarations.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, "references a secret"): - scan_workflow_sources(policy_fixture(), "cli", {"unsafe.yml": source + declaration}) - - def test_protected_ref_guard_covers_recovery_and_docs_deployment_shapes(self) -> None: - fixtures = { - "server": f"""name: server recovery -on: - workflow_dispatch: -permissions: - contents: read -jobs: - recover: - runs-on: ubuntu-latest - permissions: - actions: write - contents: write - steps: - - uses: actions/checkout@{CHECKOUT_PIN} # v6 -""", - "documentation": """name: docs deployment -on: - workflow_dispatch: -permissions: - contents: read -jobs: - deploy: - runs-on: ubuntu-latest - environment: github-pages - permissions: - contents: read - id-token: write - pages: write - steps: [] -""", - } - for target, source in fixtures.items(): - with self.subTest(target=target), self.assertRaisesRegex(PolicyError, "outside refs/heads/main"): - scan_workflow_sources(policy_fixture(), target, {"fixture.yml": source}) - guarded = source.replace(" runs-on:", " if: github.ref == 'refs/heads/main'\n runs-on:") - scan_workflow_sources(policy_fixture(), target, {"fixture.yml": guarded}) - - def test_workflow_trust_rejects_unreviewed_workflow_run_consumers(self) -> None: - source = f"""name: unsafe retention -on: - workflow_run: - workflows: [Build] - types: [completed] -permissions: - contents: read -jobs: - retain: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@{CHECKOUT_PIN} # v6 -""" - with self.assertRaisesRegex(PolicyError, "no reviewed privileged workflow_run trust binding"): - scan_workflow_sources(policy_fixture(), "cli", {"unsafe.yml": source}) - - def test_workflow_trust_binds_privileged_artifacts_to_their_upload_producer(self) -> None: - source = self.trusted_privileged_artifact_source() - evidence = scan_workflow_sources(policy_fixture(), "cli", {"trusted.yml": source}) - self.assertEqual(["publish"], evidence["trusted.yml"]["privileged_jobs"]) - - cases = { - "unrelated dependency": source.replace("needs: package", "needs: unrelated"), - "matching name without digest": source.replace( - " artifact-ids: ${{ needs.package.outputs.artifact_id }}\n", - " name: release.tar\n", - ), - "wrong digest": source.replace( - "artifact_digest: ${{ steps.release.outputs.artifact-digest }}", - "artifact_digest: ${{ steps.release.outputs.artifact-id }}", - ), - "digest check after first consumer": source.replace( - " - name: Validate the exact producer artifact before use\n", - " - run: tar -xf isolated-release/release.tar\n" - " - name: Validate the exact producer artifact before use\n", - ), - } - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex( - PolicyError, - "exact producer, immutable artifact identity, and pre-use digest validation", - ): - scan_workflow_sources(policy_fixture(), "cli", {"unsafe.yml": candidate}) - - def test_privileged_artifact_validation_rejects_execution_overrides(self) -> None: - source = self.trusted_privileged_artifact_source() - cases = { - "download can ignore digest mismatch": source.replace("digest-mismatch: error", "digest-mismatch: warn"), - "download changes repository": source.replace( - "repository: ${{ github.repository }}", "repository: attacker/fork" - ), - "validator can be skipped": source.replace( - " - name: Validate the exact producer artifact before use\n", - " - name: Validate the exact producer artifact before use\n if: ${{ false }}\n", - ), - "validator custom shell": source.replace( - " run: |\n set -euo pipefail", - " shell: true {0}\n run: |\n set -euo pipefail", - ), - "job container": source.replace( - " publish:\n", " publish:\n container: python:3.13\n" - ), - "job service": source.replace( - " publish:\n", " publish:\n services:\n writer:\n image: alpine:3.20\n" - ), - "download path override": source.replace("path: isolated-release", "path: scripts"), - "checkout persists credentials": source.replace("persist-credentials: false", "persist-credentials: true"), - "checkout selects a different ref": source.replace( - "ref: ${{ github.sha }}", "ref: ${{ inputs.unreviewed_ref }}" - ), - "predecessor run shadows tools": source.replace( - " - uses: actions/download-artifact@", - " - run: echo '/tmp/shadow' >> \"$GITHUB_PATH\"\n" - " - uses: actions/download-artifact@", - ), - "extra predecessor action": source.replace( - " - uses: actions/download-artifact@", - f" - uses: actions/checkout@{CHECKOUT_PIN} # v6\n" - " with:\n" - " fetch-depth: 0\n" - " persist-credentials: false\n" - " ref: ${{ github.sha }}\n" - " - uses: actions/download-artifact@", - ), - "download can be skipped": source.replace( - " - uses: actions/download-artifact@", - " - if: ${{ false }}\n uses: actions/download-artifact@", - ), - } - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex( - PolicyError, - "exact producer, immutable artifact identity, and pre-use digest validation", - ): - scan_workflow_sources(policy_fixture(), "cli", {"unsafe.yml": candidate}) - - def test_privileged_artifact_validator_compares_the_downloaded_bytes(self) -> None: - command = policy_fixture()["workflow_trust"]["privileged_artifact_handoffs"]["validator_command"] - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - artifact_directory = root / "isolated-release" - artifact_directory.mkdir() - payload = b"exact artifact bytes" - (artifact_directory / "release.tar").write_bytes(payload) - environment = { - **os.environ, - "ARTIFACT_DIRECTORY": artifact_directory.name, - "EXPECTED_ARTIFACT_DIGEST": hashlib.sha256(payload).hexdigest(), - "EXPECTED_ARTIFACT_ID": "101", - "EXPECTED_SOURCE_RUN_ATTEMPT": "2", - "EXPECTED_SOURCE_RUN_ID": "303", - } - exact = subprocess.run( - ["bash", "-c", command], - cwd=root, - env=environment, - check=False, - capture_output=True, - text=True, - ) - self.assertEqual(0, exact.returncode, exact.stderr) - - environment["EXPECTED_ARTIFACT_DIGEST"] = "0" * 64 - wrong = subprocess.run( - ["bash", "-c", command], - cwd=root, - env=environment, - check=False, - capture_output=True, - text=True, - ) - self.assertNotEqual(0, wrong.returncode) - self.assertIn("artifact digest mismatch", wrong.stderr) - - def test_workflow_run_validators_must_be_executable_steps(self) -> None: - policy = policy_fixture() - source = (ROOT / ".github/workflows/beta-conformance-retention.yml").read_text(encoding="utf-8") - resolution_source = ( - ROOT / ".github/workflows/beta-continuity-resolution.yml" - ).read_text(encoding="utf-8") - scan_workflow_sources( - policy, - "github-control-plane", - { - ".github/workflows/beta-conformance-retention.yml": source, - ".github/workflows/beta-continuity-resolution.yml": resolution_source, - }, - ) - - cases = { - "identity in comment": source.replace( - " python scripts/beta_conformance.py retention-source \\", - " # python scripts/beta_conformance.py retention-source \\", - ), - "digest in comment": source.replace( - " python scripts/beta_conformance.py aggregate \\", - " # python scripts/beta_conformance.py aggregate \\", - ), - "identity in output": source.replace( - " python scripts/beta_conformance.py retention-source \\", - " echo python scripts/beta_conformance.py retention-source \\", - ), - "identity from unreviewed path": source.replace( - " python scripts/beta_conformance.py retention-source \\", - " python shadow/scripts/beta_conformance.py retention-source \\", - ), - "identity short-circuited": source.replace( - " python scripts/beta_conformance.py retention-source \\", - " true || python scripts/beta_conformance.py retention-source \\", - ), - "identity multiline short-circuited": source.replace( - " python scripts/beta_conformance.py retention-source \\", - " true ||\n" - " python scripts/beta_conformance.py retention-source \\", - ), - "identity in unreachable shell branch": source.replace( - " python scripts/beta_conformance.py retention-source \\", - " if false; then\n" - " python scripts/beta_conformance.py retention-source \\", - ).replace( - ' --github-output "$GITHUB_OUTPUT"', - ' --github-output "$GITHUB_OUTPUT"\n fi', - 1, - ), - "digest short-circuited": source.replace( - " python scripts/beta_conformance.py aggregate \\", - " true || python scripts/beta_conformance.py aggregate \\", - ), - "identity conditionally skipped": source.replace( - " - name: Resolve and validate the completed source run\n", - " - name: Resolve and validate the completed source run\n if: ${{ false }}\n", - ), - "digest failure ignored": source.replace( - " - name: Aggregate exact-tuple evidence\n", - " - name: Aggregate exact-tuple evidence\n continue-on-error: true\n", - ), - "identity custom shell": source.replace( - " - name: Resolve and validate the completed source run\n", - " - name: Resolve and validate the completed source run\n" - " shell: true {0}\n", - ), - "digest working directory": source.replace( - " - name: Aggregate exact-tuple evidence\n", - " - name: Aggregate exact-tuple evidence\n" - " working-directory: shadow\n", - ), - "identity inherited job shell": source.replace( - " bind:\n", - " bind:\n defaults:\n run:\n shell: true {0}\n", - ), - "digest inherited job working directory": source.replace( - " retain:\n", - " retain:\n defaults:\n run:\n working-directory: shadow\n", - ), - "validators inherited workflow shell": source.replace( - "jobs:\n", - "defaults:\n run:\n shell: true {0}\n\njobs:\n", - ), - "validators inherited workflow working directory": source.replace( - "jobs:\n", - "defaults:\n run:\n working-directory: shadow\n\njobs:\n", - ), - } - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, "does not invoke its reviewed"): - scan_workflow_sources( - policy, - "github-control-plane", - {".github/workflows/beta-conformance-retention.yml": candidate}, - ) - - def test_workflow_run_validators_reject_preceding_injection_and_containers(self) -> None: - policy = policy_fixture() - source = (ROOT / ".github/workflows/beta-conformance-retention.yml").read_text(encoding="utf-8") - cases = { - "prior run writes shell startup": source.replace( - " - name: Resolve and validate the completed source run\n", - " - name: Inject shell startup\n" - " run: echo 'BASH_ENV=/tmp/injected' >> \"$GITHUB_ENV\"\n\n" - " - name: Resolve and validate the completed source run\n", - ), - "prior run shadows Python": source.replace( - " - name: Aggregate exact-tuple evidence\n", - " - name: Shadow Python\n" - " run: echo '/tmp/shadow' >> \"$GITHUB_PATH\"\n\n" - " - name: Aggregate exact-tuple evidence\n", - ), - "unreviewed preceding action": source.replace( - " - name: Resolve and validate the completed source run\n", - " - name: Extra setup action\n" - " uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6\n" - " with:\n" - " python-version: \"3.13\"\n\n" - " - name: Resolve and validate the completed source run\n", - ), - "checkout selects a shadow path": source.replace( - " persist-credentials: false\n", - " persist-credentials: false\n" - " path: shadow\n", - 1, - ), - "checkout selects a contributor fork": source.replace( - " persist-credentials: false\n", - " persist-credentials: false\n" - " repository: attacker/controller-fork\n", - 1, - ), - "setup action receives environment": source.replace( - " - name: Set up Python\n", - " - name: Set up Python\n" - " env:\n" - " BASH_ENV: /tmp/injected\n", - 1, - ), - "artifact overwrites the controller checkout": source.replace( - " path: aggregate-input\n", - " path: .\n", - ), - "artifact overwrites reviewed validators": source.replace( - " path: aggregate-input\n", - " path: scripts\n", - ), - "validator selects a different run": source.replace( - " REQUESTED_RUN_ID: ${{ inputs.source_run_id || github.event.workflow_run.id }}\n", - " REQUESTED_RUN_ID: 1\n", - ).replace( - ' --github-output "$GITHUB_OUTPUT"', - ' --github-output "$GITHUB_OUTPUT"\n' - " # github.event.workflow_run.id", - 1, - ), - "validator selects a different attempt": source.replace( - " REQUESTED_RUN_ATTEMPT: >-\n" - " ${{ inputs.source_run_attempt || github.event.workflow_run.run_attempt }}\n", - " REQUESTED_RUN_ATTEMPT: 1\n", - ).replace( - ' --github-output "$GITHUB_OUTPUT"', - ' --github-output "$GITHUB_OUTPUT"\n' - " # github.event.workflow_run.run_attempt", - 1, - ), - "binder container environment": source.replace( - " bind:\n", - " bind:\n" - " container:\n" - " image: python:3.13\n" - " env:\n" - " BASH_ENV: /tmp/injected\n", - ), - "publisher container environment": source.replace( - " retain:\n", - " retain:\n" - " container:\n" - " image: python:3.13\n" - " env:\n" - " PYTHONPATH: /tmp/injected\n", - ), - } - - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, "does not invoke its reviewed"): - scan_workflow_sources( - policy, - "github-control-plane", - {".github/workflows/beta-conformance-retention.yml": candidate}, - ) - - def test_workflow_run_validator_commands_bind_exact_run_selectors(self) -> None: - policy = policy_fixture() - source = (ROOT / ".github/workflows/beta-conformance-retention.yml").read_text(encoding="utf-8") - cases = { - "identity run ID replaced with a constant": source.replace( - '--expected-run-id "$REQUESTED_RUN_ID"', - '--expected-run-id "1"', - ), - "identity run attempt replaced with a constant": source.replace( - '--expected-run-attempt "$REQUESTED_RUN_ATTEMPT"', - '--expected-run-attempt "1"', - ), - "identity run ID replaced with shell expansion": source.replace( - '--expected-run-id "$REQUESTED_RUN_ID"', - '--expected-run-id "$(printf \'%s\' "$REQUESTED_RUN_ID")"', - ), - "artifact run ID replaced with a constant": source.replace( - '--run-id "${{ needs.bind.outputs.source_run_id }}"', - '--run-id "1"', - ), - } - - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, "does not invoke its reviewed"): - scan_workflow_sources( - policy, - "github-control-plane", - {".github/workflows/beta-conformance-retention.yml": candidate}, - ) - - def test_resolution_publisher_requires_exact_qualified_controller_revision(self) -> None: - policy = policy_fixture() - source = ( - ROOT / ".github/workflows/beta-continuity-resolution.yml" - ).read_text(encoding="utf-8") - retention_source = ( - ROOT / ".github/workflows/beta-conformance-retention.yml" - ).read_text(encoding="utf-8") - scan_workflow_sources( - policy, - "github-control-plane", - { - ".github/workflows/beta-conformance-retention.yml": retention_source, - ".github/workflows/beta-continuity-resolution.yml": source, - }, - ) - - cases = { - "controller binding absent": source.replace( - " github.ref == 'refs/heads/main' &&\n" - " github.sha == needs.bind.outputs.source_head_sha", - " github.ref == 'refs/heads/main'", - ), - "controller binding uses event source": source.replace( - "github.sha == needs.bind.outputs.source_head_sha", - "github.event.workflow_run.head_sha == needs.bind.outputs.source_head_sha", - ), - "controller binding uses another output": source.replace( - "github.sha == needs.bind.outputs.source_head_sha", - "github.sha == needs.bind.outputs.source_run_id", - ), - } - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex( - PolicyError, - "does not enforce its reviewed privilege condition", - ): - scan_workflow_sources( - policy, - "github-control-plane", - { - ".github/workflows/beta-conformance-retention.yml": retention_source, - ".github/workflows/beta-continuity-resolution.yml": candidate, - }, - ) - - def test_workflow_run_validator_jobs_reject_services_and_unreviewed_runners(self) -> None: - policy = policy_fixture() - source = (ROOT / ".github/workflows/beta-conformance-retention.yml").read_text(encoding="utf-8") - cases = { - "binder service workspace bind": source.replace( - " bind:\n", - " bind:\n" - " services:\n" - " workspace-writer:\n" - " image: alpine:3.20\n" - " volumes:\n" - ' - "${{ github.workspace }}:/workspace"\n', - ), - "publisher service workspace bind": source.replace( - " retain:\n", - " retain:\n" - " services:\n" - " workspace-writer:\n" - " image: alpine:3.20\n" - " volumes:\n" - ' - "${{ github.workspace }}:/workspace"\n', - ), - "binder self-hosted runner": source.replace( - " runs-on: ubuntu-latest\n", - " runs-on: self-hosted\n", - 1, - ), - "publisher self-hosted runner": source.replace( - " runs-on: ubuntu-latest\n timeout-minutes: 15\n", - " runs-on: self-hosted\n timeout-minutes: 15\n", - ), - } - - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex(PolicyError, "does not invoke its reviewed"): - scan_workflow_sources( - policy, - "github-control-plane", - {".github/workflows/beta-conformance-retention.yml": candidate}, - ) - - def test_workflow_run_validators_reject_unreviewed_effective_environment(self) -> None: - policy = policy_fixture() - source = (ROOT / ".github/workflows/beta-conformance-retention.yml").read_text(encoding="utf-8") - environment_names = ( - "BASH_ENV", - "ENV", - "PATH", - "PYTHONPATH", - "PYTHONHOME", - "LD_PRELOAD", - "LD_LIBRARY_PATH", - ) - - def at_workflow_scope(name: str) -> str: - return source.replace("jobs:\n", f"env:\n {name}: /tmp/injected\n\njobs:\n") - - def at_job_scope(name: str) -> str: - return source.replace(" bind:\n", f" bind:\n env:\n {name}: /tmp/injected\n") - - def at_step_scope(name: str) -> str: - return source.replace( - " id: source\n env:\n", - f" id: source\n env:\n {name}: /tmp/injected\n", - ) - - for scope, inject in ( - ("workflow", at_workflow_scope), - ("job", at_job_scope), - ("step", at_step_scope), - ): - for environment_name in environment_names: - with self.subTest(scope=scope, environment_name=environment_name), self.assertRaisesRegex( - PolicyError, - "does not invoke its reviewed source identity validator", - ): - scan_workflow_sources( - policy, - "github-control-plane", - {".github/workflows/beta-conformance-retention.yml": inject(environment_name)}, - ) - - def test_policy_rejects_execution_affecting_validator_environment_allowlist(self) -> None: - policy = policy_fixture() - consumer = policy["workflow_trust"]["privileged_workflow_run_consumers"][ - "github-control-plane/beta-conformance-retention.yml" - ] - consumer["identity_validator_environment"].append("PYTHONPATH") - - with self.assertRaisesRegex(PolicyError, "reviewed safe environment names"): - validate_policy(policy) - - def test_policy_rejects_unreviewed_validator_runner(self) -> None: - policy = policy_fixture() - consumer = policy["workflow_trust"]["privileged_workflow_run_consumers"][ - "github-control-plane/beta-conformance-retention.yml" - ] - consumer["validator_runner"] = "self-hosted" - - with self.assertRaisesRegex(PolicyError, "reviewed GitHub-hosted runner"): - validate_policy(policy) - - def test_policy_rejects_unreviewed_validator_setup_actions(self) -> None: - policy = policy_fixture() - consumer = policy["workflow_trust"]["privileged_workflow_run_consumers"][ - "github-control-plane/beta-conformance-retention.yml" - ] - consumer["identity_validator_preceding_steps"].append( - "docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0" - ) - - with self.assertRaisesRegex(PolicyError, "reviewed immutable action steps"): - validate_policy(policy) - - def test_artifact_digest_validator_rejects_unreviewed_effective_environment(self) -> None: - policy = policy_fixture() - source = (ROOT / ".github/workflows/beta-conformance-retention.yml").read_text(encoding="utf-8") - cases = { - "job shell startup": source.replace( - " retain:\n", - " retain:\n env:\n BASH_ENV: /tmp/injected\n", - ), - "step Python import path": source.replace( - " - name: Aggregate exact-tuple evidence\n", - " - name: Aggregate exact-tuple evidence\n" - " env:\n" - " PYTHONPATH: /tmp/injected\n", - ), - } - - for name, candidate in cases.items(): - with self.subTest(name=name), self.assertRaisesRegex( - PolicyError, - "does not invoke its reviewed artifact digest validator", - ): - scan_workflow_sources( - policy, - "github-control-plane", - {".github/workflows/beta-conformance-retention.yml": candidate}, - ) - - def test_github_client_falls_back_without_forwarding_authorization(self) -> None: - sleeps: list[float] = [] - client = GitHubClient("secret", max_attempts=3, retry_base_seconds=1, sleep=sleeps.append) - responses = [ - http_error(503, b"service unavailable", **{"Retry-After": "120"}), - FakeResponse(b'{"default_branch":"main"}'), - ] - - with patch.object(urllib.request, "urlopen", side_effect=responses) as urlopen: - result = client.json("/repos/durable-workflow/cli") - - self.assertEqual({"default_branch": "main"}, result) - self.assertEqual([], sleeps) - self.assertEqual(2, urlopen.call_count) - authenticated_request = urlopen.call_args_list[0].args[0] - credential_free_request = urlopen.call_args_list[1].args[0] - self.assertEqual("Bearer secret", authenticated_request.get_header("Authorization")) - self.assertIsNone(credential_free_request.get_header("Authorization")) - - def test_github_client_recovers_after_both_clients_have_connection_interruptions(self) -> None: - sleeps: list[float] = [] - client = GitHubClient("secret", max_attempts=3, retry_base_seconds=1, sleep=sleeps.append) - responses = [ - urllib.error.URLError(ConnectionResetError("authenticated connection reset")), - urllib.error.URLError(ConnectionResetError("credential-free connection reset")), - FakeResponse(b'{"default_branch":"main"}'), - ] - - with patch.object(urllib.request, "urlopen", side_effect=responses) as urlopen: - result = client.json("/repos/durable-workflow/cli") - - self.assertEqual({"default_branch": "main"}, result) - self.assertEqual([1], sleeps) - self.assertEqual(3, urlopen.call_count) - - def test_github_client_honors_rate_limit_retry_timing(self) -> None: - sleeps: list[float] = [] - client = GitHubClient( - max_attempts=3, - retry_base_seconds=1, - retry_max_seconds=30, - sleep=sleeps.append, - now=lambda: 100, - ) - responses = [ - http_error(403, b"API rate limit exceeded", **{"X-RateLimit-Remaining": "0", "X-RateLimit-Reset": "112"}), - http_error(429, **{"Retry-After": "120"}), - FakeResponse(b"[]"), - ] - - with patch.object(urllib.request, "urlopen", side_effect=responses): - self.assertEqual([], client.json("/repos/durable-workflow/cli/rules/branches/main")) - - self.assertEqual([12, 120], sleeps) - - def test_github_client_bounds_transient_exhaustion_with_endpoint_evidence(self) -> None: - client = GitHubClient("secret", max_attempts=3, retry_base_seconds=1, sleep=lambda _delay: None) - - with ( - patch.object( - urllib.request, - "urlopen", - side_effect=[http_error(503), http_error(502)] * 3, - ), - self.assertRaisesRegex( - GitHubInfrastructureError, - r"classification=github-api-transient, endpoint=GET /repos/durable-workflow/cli, " - r"reason=retry-exhausted, authenticated_attempts=3, authenticated_status=503, " - r"credential_free_attempts=3, credential_free_status=502", - ), - ): - client.json("/repos/durable-workflow/cli") - - def test_github_client_bounds_all_endpoint_retries_by_the_audit_deadline(self) -> None: - clock = [0.0] - sleeps: list[float] = [] - - def sleep(delay: float) -> None: - sleeps.append(delay) - clock[0] += delay - - client = GitHubClient( - "secret", - max_attempts=5, - retry_base_seconds=2, - audit_timeout_seconds=3, - sleep=sleep, - monotonic=lambda: clock[0], - ) - - responses = [ - http_error(503), - http_error(503), - FakeResponse(b'{"default_branch":"main"}'), - http_error(503), - http_error(503), - ] - with patch.object(urllib.request, "urlopen", side_effect=responses) as urlopen: - self.assertEqual( - {"default_branch": "main"}, - client.json("/repos/durable-workflow/cli"), - ) - with self.assertRaisesRegex( - GitHubInfrastructureError, - r"endpoint=GET /repos/durable-workflow/server, reason=audit-deadline, " - r"authenticated_attempts=1, authenticated_status=503, credential_free_attempts=1, " - r"credential_free_status=503", - ): - client.json("/repos/durable-workflow/server") - - self.assertEqual([2], sleeps) - self.assertEqual(5, urlopen.call_count) - - def test_github_client_does_not_retry_authorization_failures(self) -> None: - for status in (401, 403): - with self.subTest(status=status): - client = GitHubClient( - "secret", - max_attempts=3, - sleep=lambda _delay: self.fail("authorization failure was retried"), - ) - - with ( - patch.object( - urllib.request, - "urlopen", - side_effect=http_error(status, b"Resource not accessible"), - ) as urlopen, - self.assertRaisesRegex(PolicyError, rf"GitHub API {status}"), - ): - client.json("/repos/durable-workflow/cli") - - self.assertEqual(1, urlopen.call_count) - - def test_exhaustion_uses_a_distinct_temporary_failure_exit(self) -> None: - error = GitHubInfrastructureError( - "/repos/durable-workflow/cli", - {"authenticated": 5, "credential_free": 5}, - {"authenticated": "status=503", "credential_free": "status=503"}, - reason="retry-exhausted", - ) - stderr = io.StringIO() - - with ( - patch("scripts.qualification_policy.audit_policy", side_effect=error), - redirect_stderr(stderr), - ): - exit_code = main(["audit", "--policy", str(ROOT / "qualification" / "policy.json")]) - - self.assertEqual(INFRASTRUCTURE_EXIT_CODE, exit_code) - self.assertIn("qualification infrastructure failed", stderr.getvalue()) - self.assertIn("endpoint=GET /repos/durable-workflow/cli", stderr.getvalue()) - - def test_github_client_rejects_malformed_json_without_retry(self) -> None: - client = GitHubClient(max_attempts=3, sleep=lambda _delay: self.fail("malformed data was retried")) - - with ( - patch.object(urllib.request, "urlopen", return_value=FakeResponse(b"not JSON")) as urlopen, - self.assertRaisesRegex(PolicyError, r"is not valid JSON"), - ): - client.json("/repos/durable-workflow/cli") - - self.assertEqual(1, urlopen.call_count) - - def test_public_target_inventory_and_branches_are_complete(self) -> None: - policy = policy_fixture() - validate_policy(policy) - actual = {name: (target["repository"], target["branch"]) for name, target in policy["targets"].items()} - self.assertEqual(EXPECTED_TARGETS, actual) - - def test_governed_target_inventory_includes_ai_and_cloud(self) -> None: - policy = policy_fixture() - self.assertEqual(12, len(EXPECTED_TARGETS)) - self.assertEqual(("ai", "main"), EXPECTED_TARGETS["ai"]) - self.assertEqual(("cloud", "main"), EXPECTED_TARGETS["cloud"]) - self.assertEqual("Target branch qualification", policy["targets"]["ai"]["workflows"][0]["required_check"]) - self.assertEqual("Route Drift Guard", policy["targets"]["cloud"]["workflows"][0]["required_check"]) - self.assertFalse(policy["targets"]["cloud"]["public_audit"]) - self.assertNotIn("cloud", EXPECTED_PUBLIC_AUDIT_TARGETS) - - def test_every_product_target_has_one_enforced_action_policy_preflight(self) -> None: - policy = policy_fixture() - for name, target in policy["targets"].items(): - preflights = [ - workflow["path"] - for workflow in target["workflows"] - if workflow.get("action_policy_preflight") is True - ] - with self.subTest(target=name): - self.assertEqual(0 if name == "github-control-plane" else 1, len(preflights)) - - def test_required_check_cannot_disconnect_the_reviewed_preflight(self) -> None: - policy = policy_fixture() - workflow = policy["targets"]["cli"]["workflows"][0] - source = FakeGitHubClient(policy).bytes( - "/repos/durable-workflow/cli/contents/.github/workflows/build.yml?ref=" + "a" * 40 - ).decode() - verify_workflow_source("cli", "main", workflow, source) - - disconnected = source.replace( - "needs: [action-policy, test]", - "needs: [test]", - ) - with self.assertRaisesRegex(PolicyError, "does not depend on the central action policy"): - verify_workflow_source("cli", "main", workflow, disconnected) - - def test_action_policy_preflight_may_run_unconditionally(self) -> None: - policy = policy_fixture() - workflow = policy["targets"]["waterline"]["workflows"][0] - source = FakeGitHubClient(policy).bytes( - "/repos/durable-workflow/waterline/contents/.github/workflows/php.yml?ref=" + "a" * 40 - ).decode() - source = source.replace( - " if: ${{ github.server_url == 'https://github.com' }}\n", - "", - 1, - ).replace( - " if: ${{ github.server_url == 'https://github.com' }}\n", - "", - 1, - ) - verify_workflow_source("waterline", "v2", workflow, source) - - def test_each_product_preflight_rejects_an_unapproved_immutable_action_pin(self) -> None: - policy = policy_fixture() - source = self.trusted_pull_request_source().replace(CHECKOUT_PIN, "f" * 40) - for name in sorted(set(EXPECTED_TARGETS) - {"github-control-plane"}): - with self.subTest(target=name), self.assertRaisesRegex( - PolicyError, - "is not centrally approved", - ): - scan_workflow_sources(policy, name, {"candidate.yml": source}) - - def test_policy_rejects_a_product_target_without_one_preflight_workflow(self) -> None: - policy = policy_fixture() - del policy["targets"]["cli"]["workflows"][0]["action_policy_preflight"] - with self.assertRaisesRegex(PolicyError, "must declare exactly 1 central action policy"): - validate_policy(policy) - - def test_policy_rejects_a_missing_public_target(self) -> None: - policy = policy_fixture() - del policy["targets"]["sdk-rust"] - with self.assertRaisesRegex(PolicyError, "target inventory mismatch"): - validate_policy(policy) - - def test_policy_rejects_duplicate_check_contexts(self) -> None: - policy = policy_fixture() - duplicate = copy.deepcopy(policy["targets"]["sample-app"]["workflows"][0]) - duplicate["path"] = "duplicate.yml" - policy["targets"]["sample-app"]["workflows"].append(duplicate) - with self.assertRaisesRegex(PolicyError, "duplicate workflow paths or check contexts"): - validate_policy(policy) - - def test_policy_rejects_retired_runtime_as_supported(self) -> None: - policy = policy_fixture() - policy["action_runtime"]["supported_javascript_runtimes"] = ["node20"] + self.scan(workflow(f"- uses: actions/checkout@{commit}")) + + def test_pull_request_workflows_cannot_request_write_access(self) -> None: + with self.assertRaisesRegex(PolicyError, "top-level write permissions"): + self.scan(workflow("- run: true", permissions="contents: write")) + + def test_pull_request_target_is_rejected(self) -> None: + with self.assertRaisesRegex(PolicyError, "pull_request_target"): + self.scan(workflow("- run: true", trigger="pull_request_target")) + + def test_pull_request_jobs_cannot_use_environments_or_secrets(self) -> None: + source = workflow("- run: echo ${{ secrets.TOKEN }}") + source = source.replace(" runs-on: ubuntu-latest", " runs-on: ubuntu-latest\n environment: production") + with self.assertRaises(PolicyError): + self.scan(source) + + def test_pull_request_cache_rejects_broad_paths(self) -> None: + commit, version = self.approved_action("actions/cache") + source = workflow( + f"""- uses: actions/cache@{commit} # {version} + with: + path: . + key: test-${{{{ github.event_name }}}}""" + ) + with self.assertRaisesRegex(PolicyError, "unsafe cache path"): + self.scan(source) + + def test_policy_rejects_a_retired_javascript_runtime(self) -> None: + candidate = copy.deepcopy(self.policy) + candidate["action_runtime"]["supported_javascript_runtimes"] = ["node20"] with self.assertRaisesRegex(PolicyError, "supported JavaScript action runtimes"): - validate_policy(policy) - - def test_workflow_contract_requires_dispatch_timeout_and_independent_matrix(self) -> None: - workflow = { - "path": "ci.yml", - "required_check": "qualification", - "matrix_independent": True, - } - with self.assertRaisesRegex(PolicyError, "manual recovery"): - verify_workflow_source( - "sdk-python", - "main", - workflow, - "on:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n" - "jobs:\n test:\n timeout-minutes: 5\n fail-fast: false\n", + validate_policy(candidate) + + def test_cli_validate_writes_no_repository_state(self) -> None: + commit, version = self.approved_action("actions/checkout") + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) + (directory / "ci.yml").write_text( + workflow(f"- uses: actions/checkout@{commit} # {version}"), + encoding="utf-8", ) - - def test_audit_rejects_a_dynamic_matrix_without_polling_for_its_required_check(self) -> None: - policy = policy_fixture() - - class DynamicMatrixClient(FakeGitHubClient): - def __init__(self, dynamic_policy: dict[str, Any]) -> None: - super().__init__(dynamic_policy) - self.checked_repositories: list[str] = [] - - def bytes(self, path: str) -> bytes: - if "/repos/durable-workflow/cli/contents/.github/workflows/build.yml?" in path: - return f"""name: qualification -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: -permissions: - contents: read -jobs: - test: - name: Target branch qualification - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - php: ["8.3", "8.4"] - steps: - - uses: actions/checkout@{CHECKOUT_PIN} # v6 -""".encode() - return super().bytes(path) - - def collection(self, path: str, key: str) -> list[dict[str, Any]]: - self.checked_repositories.append(self._repository(path)) - return super().collection(path, key) - - client = DynamicMatrixClient(policy) - with self.assertRaisesRegex( - PolicyError, - "does not emit required check 'Target branch qualification' as exactly one stable non-matrix job", - ): - audit_policy(policy, client) - - self.assertNotIn("cli", client.checked_repositories) - - def test_workflow_contract_accepts_an_exact_aggregate_for_dynamic_matrix_cells(self) -> None: - workflow = { - "path": "ci.yml", - "required_check": "Target branch qualification", - "matrix_independent": True, - } - - verify_workflow_source( - "sdk-python", - "main", - workflow, - """name: qualification -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: -jobs: - test: - name: Python ${{ matrix.python }} - runs-on: ubuntu-latest - timeout-minutes: 5 - strategy: - fail-fast: false - matrix: - python: ["3.12", "3.13"] - aggregate: - name: Target branch qualification - needs: [test] - if: always() - runs-on: ubuntu-latest - timeout-minutes: 5 -""", - ) - - def test_latest_check_run_uses_the_latest_attempt(self) -> None: - latest = _latest_check_runs( - [ - {"id": 1, "name": "qualification", "conclusion": "failure"}, - {"id": 3, "name": "qualification", "conclusion": "success"}, - {"id": 2, "name": "other", "conclusion": "success"}, - ] - ) - self.assertEqual(3, latest["qualification"]["id"]) - - def test_audit_binds_successful_checks_and_protection_to_exact_heads(self) -> None: - policy = policy_fixture() - evidence = audit_policy(policy, FakeGitHubClient(policy)) - self.assertEqual(EXPECTED_PUBLIC_AUDIT_TARGETS, set(evidence["targets"])) - for target in evidence["targets"].values(): - self.assertEqual("a" * 40, target["commit"]) - self.assertEqual( - set(target["protected_checks"]), - set(target["successful_check_runs"]), - ) - self.assertTrue(all(release["runtime"] == "node24" for release in target["action_releases"])) - self.assertTrue( - any(".github/workflows/release.yml" in release["workflows"] for release in target["action_releases"]) - ) - sample_checkout = next( - release - for release in evidence["targets"]["sample-app"]["action_releases"] - if release["repository"] == "actions/checkout" - ) - self.assertEqual(CHECKOUT_V7_PIN, sample_checkout["commit"]) - self.assertEqual("v7.0.1", sample_checkout["version"]) - self.assertEqual("node24", sample_checkout["runtime"]) - sample_cache = next( - release - for release in evidence["targets"]["sample-app"]["action_releases"] - if release["repository"] == "actions/cache" - ) - self.assertEqual(CACHE_V6_PIN, sample_cache["commit"]) - self.assertEqual("v6.1.0", sample_cache["version"]) - self.assertEqual("node24", sample_cache["runtime"]) - sample_login = next( - release - for release in evidence["targets"]["sample-app"]["action_releases"] - if release["repository"] == "docker/login-action" - ) - self.assertEqual(DOCKER_LOGIN_V46_PIN, sample_login["commit"]) - self.assertEqual("v4.6.0", sample_login["version"]) - self.assertEqual("node24", sample_login["runtime"]) - - def test_audit_rejects_a_plan_commit_after_its_target_branch_advances(self) -> None: - policy = policy_fixture() - - with self.assertRaisesRegex(PolicyError, "requested release plan pins"): - audit_policy( - policy, - FakeGitHubClient(policy), - expected_commits={"workflow": "b" * 40}, - ) - - evidence = audit_policy( - policy, - FakeGitHubClient(policy), - expected_commits={name: "a" * 40 for name in EXPECTED_TARGETS}, - ) - self.assertEqual("a" * 40, evidence["targets"]["workflow"]["commit"]) - - def test_audit_rejects_an_unapproved_action_release(self) -> None: - policy = policy_fixture() - - class RetiredReleaseClient(FakeGitHubClient): - def bytes(self, path: str) -> bytes: - source = super().bytes(path) - if "/repos/durable-workflow/" in path and "/contents/.github/workflows/" in path: - return source.replace( - f"actions/checkout@{CHECKOUT_PIN} # v6".encode(), - b"actions/checkout@v4 # v4", - ) - return source - - with self.assertRaisesRegex(PolicyError, "is not pinned to a full commit SHA"): - audit_policy(policy, RetiredReleaseClient(policy)) - - def test_audit_rejects_a_flow_style_unapproved_action_release(self) -> None: - policy = policy_fixture() - - class FlowStyleReleaseClient(FakeGitHubClient): - def bytes(self, path: str) -> bytes: - source = super().bytes(path) - if "/repos/durable-workflow/" in path and "/contents/.github/workflows/" in path: - return source.replace( - f"- uses: actions/checkout@{CHECKOUT_PIN} # v6".encode(), - b"- { uses: actions/checkout@v4 } # v4", - ) - return source - - with self.assertRaisesRegex(PolicyError, "is not pinned to a full commit SHA"): - audit_policy(policy, FlowStyleReleaseClient(policy)) - - def test_audit_rejects_the_checkout_v7_release_on_a_retired_runtime(self) -> None: - policy = policy_fixture() - - class RetiredRuntimeClient(FakeGitHubClient): - def bytes(self, path: str) -> bytes: - if path.startswith("/repos/actions/checkout/contents/action.yml?") and CHECKOUT_V7_PIN in path: - return b"name: checkout\nruns:\n using: node20\n main: dist/index.js\n" - return super().bytes(path) - - with self.assertRaisesRegex( - PolicyError, - rf"actions/checkout@{CHECKOUT_V7_PIN}.*uses retired JavaScript runtime node20", - ): - audit_policy(policy, RetiredRuntimeClient(policy)) - - def test_audit_rejects_a_failed_required_check(self) -> None: - policy = policy_fixture() - - class FailedCheckClient(FakeGitHubClient): - def collection(self, path: str, key: str) -> list[dict[str, Any]]: - records = super().collection(path, key) - records[0]["conclusion"] = "failure" - return records - - with self.assertRaisesRegex(PolicyError, "completed/failure"): - audit_policy(policy, FailedCheckClient(policy)) - - def test_valid_source_retries_bounded_check_run_creation(self) -> None: - policy = policy_fixture() - - class DelayedCheckClient(FakeGitHubClient): - def __init__(self, delayed_policy: dict[str, Any]) -> None: - super().__init__(delayed_policy) - self.waterline_attempts = 0 - - def collection(self, path: str, key: str) -> list[dict[str, Any]]: - records = super().collection(path, key) - if "/durable-workflow/waterline/" not in path: - return records - self.waterline_attempts += 1 - if self.waterline_attempts == 1: - return [] - if self.waterline_attempts == 2: - records[0]["conclusion"] = None - records[0]["status"] = "in_progress" - return records - - client = DelayedCheckClient(policy) - sleeps: list[float] = [] - - evidence = audit_policy( - policy, - client, - check_run_max_attempts=3, - check_run_poll_seconds=5, - check_run_sleep=sleeps.append, - ) - - self.assertEqual([5, 5], sleeps) - self.assertEqual(3, client.waterline_attempts) - self.assertEqual( - {"Target branch qualification"}, - set(evidence["targets"]["waterline"]["successful_check_runs"]), - ) - - def test_audit_fails_after_bounded_check_convergence(self) -> None: - policy = policy_fixture() - - class MissingCheckClient(FakeGitHubClient): - def collection(self, path: str, key: str) -> list[dict[str, Any]]: - if "/durable-workflow/waterline/" in path: - return [] - return super().collection(path, key) - - sleeps: list[float] = [] - with self.assertRaisesRegex( - PolicyError, - "required checks did not converge after 3 attempts: 'Target branch qualification' has not been created", - ): - audit_policy( - policy, - MissingCheckClient(policy), - check_run_max_attempts=3, - check_run_poll_seconds=5, - check_run_sleep=sleeps.append, - ) - - self.assertEqual([5, 5], sleeps) - - def test_audit_rejects_unprotected_required_checks(self) -> None: - policy = policy_fixture() - - class UnprotectedClient(FakeGitHubClient): - def json(self, path: str) -> Any: - if "/rules/branches/" in path: - return [] - return super().json(path) - - with self.assertRaisesRegex(PolicyError, "does not protect checks"): - audit_policy(policy, UnprotectedClient(policy)) - - def test_self_check_can_be_skipped_during_the_same_push(self) -> None: - policy = policy_fixture() - - class NoSelfCheckClient(FakeGitHubClient): - def collection(self, path: str, key: str) -> list[dict[str, Any]]: - if "/durable-workflow/.github/" in path: - return [] - return super().collection(path, key) - - evidence = audit_policy( - policy, - NoSelfCheckClient(policy), - skip_check_runs_for={"github-control-plane"}, - ) - self.assertEqual({}, evidence["targets"]["github-control-plane"]["successful_check_runs"]) + before = sorted(path.name for path in directory.iterdir()) + validate_local_action_references(self.policy, directory, "github-control-plane") + self.assertEqual(before, sorted(path.name for path in directory.iterdir())) if __name__ == "__main__": diff --git a/tests/test_recovery_authority_reconciliation.py b/tests/test_recovery_authority_reconciliation.py deleted file mode 100644 index c553c3f..0000000 --- a/tests/test_recovery_authority_reconciliation.py +++ /dev/null @@ -1,848 +0,0 @@ -from __future__ import annotations - -import copy -import json -import unittest -import urllib.parse -from collections.abc import Mapping -from pathlib import Path -from unittest.mock import patch - -from jsonschema import Draft202012Validator - -from scripts.beta_candidate import canonical_json -from scripts.recovery_authority_reconciliation import reconcile_authority -from scripts.recovery_workflow_authority import ( - AUTHORITY_PATH, - CONTROL_REPOSITORY, - SOURCE_IDENTITIES_PATH, - SOURCE_IDENTITIES_SCHEMA, - SOURCE_IDENTITY_HISTORY_LIMIT, - RecoveryWorkflowAuthorityError, - authority_ref_url, - authority_url, - branch_url, - check_run_url, - compare_url, - exact_source_sha256, - qualification_policy_binding, - qualification_policy_url, - source_history_binding, - source_identities_url, - validate_authority, - validate_source_identities, - verify_authority_source_identities, - workflow_metadata_url, - workflow_run_url, - workflow_source_url, -) - -REPOSITORY = "durable-workflow/waterline" -BRANCH = "v2" -PATH = ".github/workflows/release-plan-recovery.yml" -POLICY_A_WORKFLOW = ".github/workflows/php.yml" -POLICY_A_CHECK = "Target branch qualification" -POLICY_B_WORKFLOW = ".github/workflows/source-qualification.yml" -POLICY_B_CHECK = "Source qualification" -OLD_COMMIT = "1" * 40 -NEW_COMMIT = "2" * 40 -POLICY_A_COMMIT = "a" * 40 -POLICY_B_COMMIT = "b" * 40 -CONTROL_HEAD = "f" * 40 -OLD_SOURCE = b"name: Recovery\non:\n workflow_dispatch:\n" -NEW_SOURCE = OLD_SOURCE + b"# successor\n" -ROOT = Path(__file__).resolve().parents[1] -SOURCE_IDENTITIES_JSON_SCHEMA = json.loads( - (ROOT / "release-recovery" / "protected-source-identities-schema.json").read_bytes() -) -PUBLIC_SOURCE_IDENTITIES = json.loads( - (ROOT / "release-recovery" / "protected-source-identities.json").read_bytes() -) - - -def exact_check_runs_url(repository: str, commit: str, check_name: str) -> str: - encoded_check_name = urllib.parse.quote(check_name, safe="") - return ( - f"https://api.github.com/repos/{repository}/commits/{commit}/check-runs" - f"?filter=latest&check_name={encoded_check_name}&per_page=100" - ) - - -def requirement(workflow: str, required_check: str) -> dict[str, str]: - return {"workflow": workflow, "required_check": required_check} - - -POLICY_A_REQUIREMENT = requirement(POLICY_A_WORKFLOW, POLICY_A_CHECK) -POLICY_B_REQUIREMENT = requirement(POLICY_B_WORKFLOW, POLICY_B_CHECK) - - -def qualification( - commit: str, - run_id: int, - check_run_id: int, - protected_requirement: Mapping[str, str] = POLICY_A_REQUIREMENT, -) -> dict[str, object]: - return { - "check_run_id": check_run_id, - "check_url": f"https://github.com/{REPOSITORY}/actions/runs/{run_id}/job/{check_run_id}", - "conclusion": "success", - "event": "push", - "head_branch": BRANCH, - "head_sha": commit, - "required_check": protected_requirement["required_check"], - "run_attempt": 1, - "run_id": run_id, - "status": "completed", - "url": f"https://github.com/{REPOSITORY}/actions/runs/{run_id}", - "workflow": protected_requirement["workflow"], - } - - -def authority() -> dict[str, object]: - return { - "schema": "durable-workflow.component-release-recovery-authority/v2", - "source": { - "repository": CONTROL_REPOSITORY, - "ref": "refs/heads/main", - "path": AUTHORITY_PATH, - "qualification": { - "workflow": ".github/workflows/beta-candidate.yml", - "event": "push", - }, - }, - "workflows": { - "waterline": { - "repository": REPOSITORY, - "ref": f"refs/heads/{BRANCH}", - "path": PATH, - "state": "active", - "sha256": exact_source_sha256(OLD_SOURCE), - } - }, - } - - -def policy(protected_requirement: Mapping[str, str] = POLICY_A_REQUIREMENT) -> dict[str, object]: - return { - "organization": "durable-workflow", - "targets": { - "waterline": { - "repository": "waterline", - "branch": BRANCH, - "workflows": [ - { - "path": protected_requirement["workflow"].removeprefix(".github/workflows/"), - "required_check": protected_requirement["required_check"], - } - ], - } - }, - } - - -def policy_binding( - value: dict[str, object], - commit: str, -) -> dict[str, str]: - return qualification_policy_binding(canonical_json(value), commit) - - -def source_identities() -> dict[str, object]: - policy_a = policy() - return { - "schema": SOURCE_IDENTITIES_SCHEMA, - "source": { - "repository": CONTROL_REPOSITORY, - "ref": "refs/heads/main", - "authority_path": AUTHORITY_PATH, - "path": SOURCE_IDENTITIES_PATH, - }, - "workflows": { - "waterline": { - "repository": REPOSITORY, - "ref": f"refs/heads/{BRANCH}", - "path": PATH, - "state": "active", - "identities": [ - { - "source_commit": OLD_COMMIT, - "sha256": exact_source_sha256(OLD_SOURCE), - "qualification": qualification(OLD_COMMIT, 101, 201), - "qualification_policy": policy_binding(policy_a, POLICY_A_COMMIT), - } - ], - } - }, - } - - -class FixtureClient: - def __init__( - self, - *, - head: str, - current_policy: dict[str, object] | None = None, - current_policy_commit: str = POLICY_A_COMMIT, - successor_requirement: Mapping[str, str] | None = None, - qualified: bool = True, - run_branch: str = BRANCH, - qualification_on_second_page: bool = False, - ) -> None: - self.head = head - self.qualified = qualified - self.run_branch = run_branch - self.qualification_on_second_page = qualification_on_second_page - self.requests: list[tuple[str, str]] = [] - self.current_policy = current_policy or policy() - self.current_policy_commit = current_policy_commit - self.sources = {OLD_COMMIT: OLD_SOURCE, NEW_COMMIT: NEW_SOURCE} - self.identities = { - OLD_COMMIT: (101, 201, POLICY_A_REQUIREMENT), - NEW_COMMIT: ( - 102, - 202, - successor_requirement - or next(iter(self.current_policy["targets"]["waterline"]["workflows"])), - ), - } - self.policies = { - POLICY_A_COMMIT: canonical_json(policy()), - current_policy_commit: canonical_json(self.current_policy), - } - self.historical_sources: dict[str, bytes] = {} - self.historical_authorities: dict[str, bytes] = {} - self.ancestor_commits: set[str] = set() - - @staticmethod - def _normalized_requirement(value: Mapping[str, object]) -> dict[str, str]: - workflow = str(value["path"]) if "path" in value else str(value["workflow"]) - return { - "workflow": ( - workflow if workflow.startswith(".github/workflows/") else f".github/workflows/{workflow}" - ), - "required_check": str(value["required_check"]), - } - - def json(self, url: str) -> dict[str, object]: - self.requests.append(("json", url)) - if url == authority_ref_url(): - return {"sha": CONTROL_HEAD} - if url == branch_url(REPOSITORY, BRANCH): - return {"commit": {"sha": self.head}} - if url == workflow_metadata_url(REPOSITORY, PATH): - return {"id": 71, "path": PATH, "state": "active"} - for policy_commit in self.policies: - if url == compare_url(CONTROL_REPOSITORY, policy_commit, CONTROL_HEAD): - return { - "status": "ahead", - "base_commit": {"sha": policy_commit}, - "merge_base_commit": {"sha": policy_commit}, - } - unresolved_policy_commit = "c" * 40 - if url == compare_url(CONTROL_REPOSITORY, unresolved_policy_commit, CONTROL_HEAD): - return { - "status": "ahead", - "base_commit": {"sha": unresolved_policy_commit}, - "merge_base_commit": {"sha": unresolved_policy_commit}, - } - for commit, (run_id, check_run_id, raw_requirement) in self.identities.items(): - protected_requirement = self._normalized_requirement(raw_requirement) - if url == workflow_run_url(REPOSITORY, run_id): - return { - "id": run_id, - "run_attempt": 1, - "path": protected_requirement["workflow"], - "event": "push", - "head_branch": self.run_branch if commit == self.head else BRANCH, - "head_sha": commit, - "status": "completed", - "conclusion": "success", - "html_url": f"https://github.com/{REPOSITORY}/actions/runs/{run_id}", - } - if url == check_run_url(REPOSITORY, check_run_id): - return self._check( - commit, - run_id, - check_run_id, - protected_requirement, - successful=True, - ) - if url == exact_check_runs_url( - REPOSITORY, - commit, - protected_requirement["required_check"], - ): - return { - "total_count": 1, - "check_runs": [ - self._check( - commit, - run_id, - check_run_id, - protected_requirement, - successful=self.qualified or commit != self.head, - ) - ] - } - if url.startswith( - f"https://api.github.com/repos/{REPOSITORY}/commits/{commit}/check-runs" - "?filter=latest&check_name=" - ) and url.endswith("&per_page=100"): - return {"total_count": 0, "check_runs": []} - if url.endswith(f"/commits/{commit}/check-runs?filter=latest&per_page=100"): - if self.qualification_on_second_page and commit == self.head: - return { - "total_count": 186, - "check_runs": [ - { - **self._check( - commit, - run_id, - check_run_id + index + 1, - protected_requirement, - successful=True, - ), - "name": f"Unrelated check {index}", - } - for index in range(100) - ] - } - return { - "total_count": 1, - "check_runs": [ - self._check( - commit, - run_id, - check_run_id, - protected_requirement, - successful=self.qualified or commit != self.head, - ) - ] - } - for base in {*self.identities, *self.ancestor_commits}: - for head in {*self.identities, self.head}: - if url == compare_url(REPOSITORY, base, head): - return { - "status": "identical" if base == head else "ahead", - "base_commit": {"sha": base}, - "merge_base_commit": {"sha": base}, - } - raise AssertionError(f"unexpected fixture URL: {url}") - - @staticmethod - def _check( - commit: str, - run_id: int, - check_run_id: int, - protected_requirement: Mapping[str, str], - *, - successful: bool, - ) -> dict[str, object]: - return { - "id": check_run_id, - "name": protected_requirement["required_check"], - "head_sha": commit, - "status": "completed", - "conclusion": "success" if successful else "failure", - "html_url": f"https://github.com/{REPOSITORY}/actions/runs/{run_id}/job/{check_run_id}", - "app": {"slug": "github-actions"}, - } - - def bytes(self, url: str, *, accept: str | None = None) -> bytes: - self.requests.append(("bytes", url)) - self.assert_media_type(accept) - for commit, raw in self.historical_sources.items(): - if url == source_identities_url(commit): - return raw - for commit, raw in self.historical_authorities.items(): - if url == authority_url(commit): - return raw - for commit, raw in self.policies.items(): - if url == qualification_policy_url(commit): - return raw - for commit, source in self.sources.items(): - if url == workflow_source_url(REPOSITORY, PATH, commit): - return source - raise RecoveryWorkflowAuthorityError(f"protected policy or source could not be resolved: {url}") - - @staticmethod - def assert_media_type(accept: str | None) -> None: - if accept != "application/vnd.github.raw+json": - raise AssertionError(f"unexpected media type: {accept}") - - -class RecoveryAuthorityReconciliationTest(unittest.TestCase): - components = {"waterline": (REPOSITORY, BRANCH)} - - def reconcile( - self, - client: FixtureClient, - *, - authority_document: dict[str, object] | None = None, - source_document: dict[str, object] | None = None, - source_raw: bytes | None = None, - current_policy: dict[str, object] | None = None, - current_policy_commit: str = POLICY_A_COMMIT, - ) -> tuple[dict[str, object], dict[str, object], dict[str, object]]: - selected_policy = current_policy or policy() - selected_sources = source_document or source_identities() - return reconcile_authority( - authority_document or authority(), - selected_sources, - selected_policy, - policy_binding(selected_policy, current_policy_commit), - client, - self.components, - source_raw=source_raw or canonical_json(selected_sources), - ) - - def full_history( - self, - *, - prior_checkpoint_count: int = 0, - segment: int = 0, - ) -> tuple[dict[str, object], dict[str, object], FixtureClient]: - current_authority = authority() - current_sources = source_identities() - client = FixtureClient(head=NEW_COMMIT) - identities: list[dict[str, object]] = [] - previous: dict[str, str] | None = None - if prior_checkpoint_count: - previous = { - "source_commit": "e" * 40, - "sha256": exact_source_sha256(b"prior checkpoint terminal\n"), - } - current_sources["workflows"]["waterline"]["checkpoint"] = { - "accepted_identities": prior_checkpoint_count, - "predecessor": previous, - "source": source_history_binding(b"{}\n", "d" * 40), - } - - for index in range(SOURCE_IDENTITY_HISTORY_LIMIT): - commit = OLD_COMMIT if index == 0 and segment == 0 else f"{segment * 10000 + index + 1000:040x}" - source = ( - OLD_SOURCE - if index == 0 and segment == 0 - else OLD_SOURCE + f"# segment {segment} accepted {index}\n".encode() - ) - run_id = 1000 + index - check_run_id = 2000 + index - identity: dict[str, object] = { - "source_commit": commit, - "sha256": exact_source_sha256(source), - "qualification": qualification(commit, run_id, check_run_id), - "qualification_policy": policy_binding(policy(), POLICY_A_COMMIT), - } - if previous is not None: - identity["supersedes"] = dict(previous) - identities.append(identity) - previous = {"source_commit": commit, "sha256": identity["sha256"]} - client.sources[commit] = source - client.identities[commit] = (run_id, check_run_id, POLICY_A_REQUIREMENT) - - current_sources["workflows"]["waterline"]["identities"] = identities - current_authority["workflows"]["waterline"]["sha256"] = identities[-1]["sha256"] - return current_authority, current_sources, client - - def rolled_history( - self, - ) -> tuple[ - dict[str, object], - dict[str, object], - dict[str, object], - dict[str, object], - dict[str, object], - FixtureClient, - ]: - current_authority, current_sources, client = self.full_history() - source_raw = canonical_json(current_sources) - proposed_authority, proposed_sources, observation = self.reconcile( - client, - authority_document=current_authority, - source_document=current_sources, - source_raw=source_raw, - ) - client.historical_sources[POLICY_A_COMMIT] = source_raw - client.historical_authorities[POLICY_A_COMMIT] = canonical_json(current_authority) - return current_authority, current_sources, proposed_authority, proposed_sources, observation, client - - def test_identical_observation_is_idempotent(self) -> None: - current_sources = source_identities() - - proposed_authority, proposed_sources, observation = self.reconcile( - FixtureClient(head=OLD_COMMIT), - source_document=current_sources, - ) - - self.assertEqual(authority(), proposed_authority) - self.assertEqual(current_sources, proposed_sources) - self.assertEqual("current", observation["outcome"]) - self.assertEqual([], observation["changes"]) - - def test_policy_b_successor_does_not_reinterpret_policy_a_history(self) -> None: - policy_b = policy(POLICY_B_REQUIREMENT) - client = FixtureClient( - head=NEW_COMMIT, - current_policy=policy_b, - current_policy_commit=POLICY_B_COMMIT, - ) - - proposed_authority, proposed_sources, observation = self.reconcile( - client, - current_policy=policy_b, - current_policy_commit=POLICY_B_COMMIT, - ) - - self.assertEqual("change-required", observation["outcome"]) - identities = proposed_sources["workflows"]["waterline"]["identities"] - self.assertEqual(2, len(identities)) - self.assertEqual(POLICY_A_WORKFLOW, identities[0]["qualification"]["workflow"]) - self.assertEqual(POLICY_A_COMMIT, identities[0]["qualification_policy"]["commit"]) - self.assertEqual(POLICY_B_WORKFLOW, identities[1]["qualification"]["workflow"]) - self.assertEqual(POLICY_B_COMMIT, identities[1]["qualification_policy"]["commit"]) - self.assertEqual( - identities[1]["qualification_policy"], - observation["changes"][0]["successor"]["qualification_policy"], - ) - self.assertEqual( - { - "source_commit": OLD_COMMIT, - "sha256": exact_source_sha256(OLD_SOURCE), - }, - identities[-1]["supersedes"], - ) - self.assertEqual( - exact_source_sha256(NEW_SOURCE), - proposed_authority["workflows"]["waterline"]["sha256"], - ) - - def test_exact_required_check_is_resolved_past_unfiltered_page_boundary(self) -> None: - client = FixtureClient( - head=NEW_COMMIT, - qualification_on_second_page=True, - ) - - proposed_authority, _proposed_sources, observation = self.reconcile(client) - - self.assertEqual("change-required", observation["outcome"]) - self.assertEqual( - exact_source_sha256(NEW_SOURCE), - proposed_authority["workflows"]["waterline"]["sha256"], - ) - self.assertIn( - ("json", exact_check_runs_url(REPOSITORY, NEW_COMMIT, POLICY_A_CHECK)), - client.requests, - ) - self.assertNotIn( - ( - "json", - f"https://api.github.com/repos/{REPOSITORY}/commits/{NEW_COMMIT}/check-runs" - "?filter=latest&per_page=100", - ), - client.requests, - ) - - def test_current_and_proposed_documents_enforce_the_same_byte_limit(self) -> None: - current_sources = source_identities() - current_raw = canonical_json(current_sources) - _authority, expected_sources, _observation = self.reconcile( - FixtureClient(head=NEW_COMMIT), - source_document=current_sources, - source_raw=current_raw, - ) - proposed_raw = canonical_json(expected_sources) - self.assertLess(len(current_raw), len(proposed_raw)) - - with ( - patch( - "scripts.recovery_authority_reconciliation.MAX_SOURCE_IDENTITIES_BYTES", - len(current_raw) - 1, - ), - self.assertRaisesRegex(RecoveryWorkflowAuthorityError, "protected source identities exceed"), - ): - self.reconcile( - FixtureClient(head=NEW_COMMIT), - source_document=current_sources, - source_raw=current_raw, - ) - - with patch( - "scripts.recovery_authority_reconciliation.MAX_SOURCE_IDENTITIES_BYTES", - len(proposed_raw), - ): - _authority, exact_boundary_sources, _observation = self.reconcile( - FixtureClient(head=NEW_COMMIT), - source_document=current_sources, - source_raw=current_raw, - ) - self.assertEqual(expected_sources, exact_boundary_sources) - - with ( - patch( - "scripts.recovery_authority_reconciliation.MAX_SOURCE_IDENTITIES_BYTES", - len(proposed_raw) - 1, - ), - self.assertRaisesRegex( - RecoveryWorkflowAuthorityError, - "proposed recovery protected source identities exceed", - ), - ): - self.reconcile( - FixtureClient(head=NEW_COMMIT), - source_document=current_sources, - source_raw=current_raw, - ) - - def test_successor_at_the_retention_boundary_rolls_over_and_remains_verifiable(self) -> None: - ( - current_authority, - current_sources, - proposed_authority, - proposed_sources, - observation, - client, - ) = self.rolled_history() - record = proposed_sources["workflows"]["waterline"] - checkpoint = record["checkpoint"] - - self.assertEqual(SOURCE_IDENTITY_HISTORY_LIMIT, len(current_sources["workflows"]["waterline"]["identities"])) - self.assertEqual(1, len(record["identities"])) - self.assertEqual(SOURCE_IDENTITY_HISTORY_LIMIT, checkpoint["accepted_identities"]) - self.assertEqual(checkpoint["predecessor"], record["identities"][0]["supersedes"]) - self.assertEqual( - current_sources["workflows"]["waterline"]["identities"][-1]["source_commit"], - checkpoint["predecessor"]["source_commit"], - ) - self.assertEqual( - source_history_binding(canonical_json(current_sources), POLICY_A_COMMIT), - checkpoint["source"], - ) - self.assertEqual(checkpoint, observation["changes"][0]["checkpoint"]) - - workflows = validate_authority(proposed_authority, self.components) - validated = validate_source_identities(proposed_sources, workflows, self.components) - evidence = verify_authority_source_identities(client, workflows, validated) - self.assertEqual(checkpoint, evidence["waterline"]["checkpoint"]) - self.assertEqual(NEW_COMMIT, evidence["waterline"]["identities"][0]["source_commit"]) - schema_proposal = copy.deepcopy(PUBLIC_SOURCE_IDENTITIES) - schema_proposal["workflows"]["waterline"] = copy.deepcopy(record) - Draft202012Validator(SOURCE_IDENTITIES_JSON_SCHEMA).validate(schema_proposal) - - oversized = copy.deepcopy(current_sources) - oversized["workflows"]["waterline"]["identities"].append(record["identities"][0]) - with self.assertRaisesRegex(RecoveryWorkflowAuthorityError, "100-identity limit"): - validate_source_identities(oversized, workflows, self.components) - schema_oversized = copy.deepcopy(schema_proposal) - schema_oversized["workflows"]["waterline"]["identities"] = copy.deepcopy( - oversized["workflows"]["waterline"]["identities"] - ) - self.assertTrue(list(Draft202012Validator(SOURCE_IDENTITIES_JSON_SCHEMA).iter_errors(schema_oversized))) - self.assertNotEqual(current_authority, proposed_authority) - - def test_rollover_checkpoint_rejects_drop_reorder_fork_and_forgery(self) -> None: - ( - _current_authority, - current_sources, - proposed_authority, - proposed_sources, - _observation, - client, - ) = self.rolled_history() - workflows = validate_authority(proposed_authority, self.components) - - mutations: list[tuple[str, dict[str, object], FixtureClient, str]] = [] - wrong_count = copy.deepcopy(proposed_sources) - wrong_count["workflows"]["waterline"]["checkpoint"]["accepted_identities"] = 200 - mutations.append(("drop", wrong_count, client, "discontinuous accepted identity count")) - - forged_predecessor = copy.deepcopy(proposed_sources) - forged = {"source_commit": "9" * 40, "sha256": "8" * 64} - forged_predecessor["workflows"]["waterline"]["checkpoint"]["predecessor"] = forged - forged_predecessor["workflows"]["waterline"]["identities"][0]["supersedes"] = forged - mutations.append(("fork", forged_predecessor, client, "exact current predecessor")) - - forged_source = copy.deepcopy(proposed_sources) - forged_source["workflows"]["waterline"]["checkpoint"]["source"]["sha256"] = "0" * 64 - mutations.append(("forge", forged_source, client, "protected binding")) - - reordered_history = copy.deepcopy(current_sources) - historical_identities = reordered_history["workflows"]["waterline"]["identities"] - historical_identities[-2], historical_identities[-1] = historical_identities[-1], historical_identities[-2] - reordered_raw = canonical_json(reordered_history) - reordered_sources = copy.deepcopy(proposed_sources) - reordered_sources["workflows"]["waterline"]["checkpoint"]["source"] = source_history_binding( - reordered_raw, - POLICY_A_COMMIT, - ) - reordered_client = copy.deepcopy(client) - reordered_client.historical_sources[POLICY_A_COMMIT] = reordered_raw - mutations.append(("reorder", reordered_sources, reordered_client, "mismatched predecessor")) - - for label, source_document, selected_client, message in mutations: - with self.subTest(mutation=label), self.assertRaisesRegex( - RecoveryWorkflowAuthorityError, - message, - ): - validated = validate_source_identities(source_document, workflows, self.components) - verify_authority_source_identities(selected_client, workflows, validated) - - def test_a_later_full_segment_rolls_forward_without_replaying_older_checkpoints(self) -> None: - root_authority, root_sources, _root_client = self.full_history() - root_raw = canonical_json(root_sources) - current_authority, current_sources, client = self.full_history( - prior_checkpoint_count=SOURCE_IDENTITY_HISTORY_LIMIT, - segment=1, - ) - root_terminal = { - field: root_sources["workflows"]["waterline"]["identities"][-1][field] - for field in ("source_commit", "sha256") - } - current_record = current_sources["workflows"]["waterline"] - current_record["checkpoint"] = { - "accepted_identities": SOURCE_IDENTITY_HISTORY_LIMIT, - "predecessor": root_terminal, - "source": source_history_binding(root_raw, POLICY_A_COMMIT), - } - current_record["identities"][0]["supersedes"] = root_terminal - client.ancestor_commits.add(root_terminal["source_commit"]) - current_raw = canonical_json(current_sources) - client.historical_sources[POLICY_A_COMMIT] = root_raw - client.historical_authorities[POLICY_A_COMMIT] = canonical_json(root_authority) - client.policies[POLICY_B_COMMIT] = canonical_json(policy()) - - proposed_authority, proposed_sources, observation = self.reconcile( - client, - authority_document=current_authority, - source_document=current_sources, - source_raw=current_raw, - current_policy_commit=POLICY_B_COMMIT, - ) - checkpoint = proposed_sources["workflows"]["waterline"]["checkpoint"] - self.assertEqual(2 * SOURCE_IDENTITY_HISTORY_LIMIT, checkpoint["accepted_identities"]) - self.assertEqual(source_history_binding(current_raw, POLICY_B_COMMIT), checkpoint["source"]) - self.assertEqual(checkpoint, observation["changes"][0]["checkpoint"]) - - client.historical_sources[POLICY_B_COMMIT] = current_raw - client.historical_authorities[POLICY_B_COMMIT] = canonical_json(current_authority) - client.requests.clear() - workflows = validate_authority(proposed_authority, self.components) - validated = validate_source_identities(proposed_sources, workflows, self.components) - evidence = verify_authority_source_identities(client, workflows, validated) - - self.assertEqual(2 * SOURCE_IDENTITY_HISTORY_LIMIT, evidence["waterline"]["checkpoint"]["accepted_identities"]) - self.assertFalse(any(url == source_identities_url(POLICY_A_COMMIT) for _method, url in client.requests)) - - def test_checkpoint_live_reads_do_not_grow_with_total_accepted_history(self) -> None: - ( - current_authority, - current_sources, - proposed_authority, - proposed_sources, - _observation, - client, - ) = self.rolled_history() - historical_record = current_sources["workflows"]["waterline"] - prior = {"source_commit": "e" * 40, "sha256": exact_source_sha256(b"older terminal\n")} - historical_record["checkpoint"] = { - "accepted_identities": 9900, - "predecessor": prior, - "source": source_history_binding(b"{}\n", "d" * 40), - } - historical_record["identities"][0]["supersedes"] = prior - historical_raw = canonical_json(current_sources) - checkpoint = proposed_sources["workflows"]["waterline"]["checkpoint"] - checkpoint["accepted_identities"] = 10000 - checkpoint["source"] = source_history_binding(historical_raw, POLICY_A_COMMIT) - client.historical_sources[POLICY_A_COMMIT] = historical_raw - client.historical_authorities[POLICY_A_COMMIT] = canonical_json(current_authority) - client.requests.clear() - - workflows = validate_authority(proposed_authority, self.components) - validated = validate_source_identities(proposed_sources, workflows, self.components) - evidence = verify_authority_source_identities(client, workflows, validated) - - self.assertEqual(10000, evidence["waterline"]["checkpoint"]["accepted_identities"]) - self.assertLessEqual(len(client.requests), 12) - self.assertFalse(any("d" * 40 in url for _method, url in client.requests)) - - def test_missing_or_altered_historical_policy_binding_fails_closed(self) -> None: - cases: list[tuple[str, dict[str, object], str]] = [] - missing = source_identities() - del missing["workflows"]["waterline"]["identities"][0]["qualification_policy"] - cases.append(("missing", missing, "invalid shape")) - altered = source_identities() - altered["workflows"]["waterline"]["identities"][0]["qualification_policy"]["sha256"] = "0" * 64 - cases.append(("altered", altered, "does not match its protected binding")) - - for label, source_document, message in cases: - with self.subTest(binding=label), self.assertRaisesRegex( - RecoveryWorkflowAuthorityError, - message, - ): - self.reconcile( - FixtureClient(head=OLD_COMMIT), - source_document=source_document, - ) - - def test_unresolvable_or_ambiguous_policy_binding_fails_closed(self) -> None: - unresolvable = source_identities() - binding = unresolvable["workflows"]["waterline"]["identities"][0]["qualification_policy"] - binding["commit"] = "c" * 40 - client = FixtureClient(head=OLD_COMMIT) - with self.assertRaisesRegex(RecoveryWorkflowAuthorityError, "could not be resolved"): - self.reconcile(client, source_document=unresolvable) - - ambiguous_policy = policy() - ambiguous_policy["targets"]["waterline"]["workflows"].append( - {"path": "other.yml", "required_check": "Other qualification"} - ) - ambiguous_client = FixtureClient( - head=NEW_COMMIT, - current_policy=ambiguous_policy, - current_policy_commit=POLICY_B_COMMIT, - ) - with self.assertRaisesRegex(RecoveryWorkflowAuthorityError, "mismatched protected target"): - self.reconcile( - ambiguous_client, - current_policy=ambiguous_policy, - current_policy_commit=POLICY_B_COMMIT, - ) - - def test_successor_qualified_only_by_obsolete_policy_is_rejected(self) -> None: - policy_b = policy(POLICY_B_REQUIREMENT) - client = FixtureClient( - head=NEW_COMMIT, - current_policy=policy_b, - current_policy_commit=POLICY_B_COMMIT, - successor_requirement=POLICY_A_REQUIREMENT, - ) - - with self.assertRaisesRegex(RecoveryWorkflowAuthorityError, "no protected qualification check"): - self.reconcile( - client, - current_policy=policy_b, - current_policy_commit=POLICY_B_COMMIT, - ) - - def test_mismatched_accepted_bytes_fail_before_observation(self) -> None: - client = FixtureClient(head=OLD_COMMIT) - client.sources[OLD_COMMIT] += b"# tampered\n" - - with self.assertRaisesRegex(RecoveryWorkflowAuthorityError, "protected source bytes"): - self.reconcile(client) - - def test_unqualified_or_cross_branch_successor_is_rejected(self) -> None: - cases = ( - (FixtureClient(head=NEW_COMMIT, qualified=False), "successful GitHub Actions check"), - (FixtureClient(head=NEW_COMMIT, run_branch="contributor"), "protected-branch run"), - ) - for client, message in cases: - with self.subTest(message=message), self.assertRaisesRegex( - RecoveryWorkflowAuthorityError, - message, - ): - self.reconcile(client, source_document=copy.deepcopy(source_identities())) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_recovery_workflow_authority.py b/tests/test_recovery_workflow_authority.py deleted file mode 100644 index 741ad68..0000000 --- a/tests/test_recovery_workflow_authority.py +++ /dev/null @@ -1,298 +0,0 @@ -from __future__ import annotations - -import copy -import hashlib -import json -import subprocess -import unittest -from pathlib import Path - -from scripts.component_release_recovery import ( - COMPONENTS, - RecoveryError, - load_recovery_workflow_authority, - verify_recovery_workflow_source, -) -from scripts.recovery_workflow_authority import ( - AUTHORITY_PATH, - QUALIFICATION_EVENT, - QUALIFICATION_WORKFLOW, - SOURCE_IDENTITIES_PATH, - SOURCE_IDENTITY_HISTORY_LIMIT, - RecoveryWorkflowAuthorityError, - authority_ref_url, - authority_url, - normalized_source_sha256, - qualification_requirements, - qualification_runs_url, - validate_authority, - validate_source_identities, - verify_authority_workflow_sources, -) - -ROOT = Path(__file__).resolve().parents[1] -AUTHORITY = json.loads((ROOT / AUTHORITY_PATH).read_bytes()) -SOURCE_IDENTITIES = json.loads((ROOT / SOURCE_IDENTITIES_PATH).read_bytes()) -IDENTITIES = { - name: (component.repository, component.default_branch) - for name, component in COMPONENTS.items() -} - - -AUTHORITY_COMMIT = "a" * 40 - - -def qualification_run( - status: str = "completed", - conclusion: str | None = "success", - *, - head_sha: str = AUTHORITY_COMMIT, - head_branch: str = "main", - path: str = ".github/workflows/beta-candidate.yml", -) -> dict[str, object]: - return { - "id": 71, - "run_attempt": 2, - "name": "Beta candidate", - "workflow_id": 37, - "path": path, - "event": "push", - "head_branch": head_branch, - "head_sha": head_sha, - "status": status, - "conclusion": conclusion, - "url": "https://api.github.com/repos/durable-workflow/.github/actions/runs/71", - "html_url": "https://github.com/durable-workflow/.github/actions/runs/71", - } - - -class FixtureClient: - def __init__(self, value: object, runs: list[dict[str, object]] | None = None) -> None: - self.raw = json.dumps(value).encode("utf-8") - self.runs = [qualification_run()] if runs is None else runs - self.requests: list[tuple[str, str, str | None]] = [] - - def json(self, url: str) -> dict[str, object]: - self.requests.append(("json", url, None)) - if url == authority_ref_url(): - return {"sha": AUTHORITY_COMMIT} - if url == qualification_runs_url(AUTHORITY_COMMIT): - return {"total_count": len(self.runs), "workflow_runs": self.runs} - raise AssertionError(f"unexpected fixture URL: {url}") - - def bytes(self, url: str, *, accept: str | None = None) -> bytes: - self.requests.append(("bytes", url, accept)) - if url != authority_url(AUTHORITY_COMMIT): - raise AssertionError(f"unexpected fixture URL: {url}") - return self.raw - - -class WorkflowSourceClient: - def __init__(self) -> None: - self.authority = copy.deepcopy(AUTHORITY) - self.sources = { - name: f"name: Recover {name}\non:\n workflow_dispatch:\n" - for name in COMPONENTS - } - for name, source in self.sources.items(): - self.authority["workflows"][name]["sha256"] = normalized_source_sha256(source) - - def _entry(self, url: str) -> tuple[str, dict[str, str]]: - for name, entry in self.authority["workflows"].items(): - if f"/repos/{entry['repository']}/" in url: - return name, entry - raise AssertionError(f"unexpected workflow source URL: {url}") - - def json(self, url: str) -> dict[str, object]: - name, entry = self._entry(url) - return { - "id": len(name), - "path": entry["path"], - "state": entry["state"], - "html_url": f"https://github.com/{entry['repository']}/actions", - } - - def bytes(self, url: str, *, accept: str | None = None) -> bytes: - name, _entry = self._entry(url) - if accept != "application/vnd.github.raw+json": - raise AssertionError(f"unexpected workflow source media type: {accept}") - return self.sources[name].encode("utf-8") - - -class RecoveryWorkflowAuthorityTest(unittest.TestCase): - def test_public_authority_names_the_complete_protected_branch_tuple(self) -> None: - workflows = validate_authority(AUTHORITY, IDENTITIES) - - self.assertEqual(set(COMPONENTS), set(workflows)) - for name, component in COMPONENTS.items(): - with self.subTest(component=name): - self.assertEqual(component.repository, workflows[name]["repository"]) - self.assertEqual(f"refs/heads/{component.default_branch}", workflows[name]["ref"]) - self.assertEqual("active", workflows[name]["state"]) - self.assertEqual( - {"workflow": QUALIFICATION_WORKFLOW, "event": QUALIFICATION_EVENT}, - AUTHORITY["source"]["qualification"], - ) - - def test_public_source_history_satisfies_bounded_continuity(self) -> None: - workflows = validate_authority(AUTHORITY, IDENTITIES) - histories = validate_source_identities( - SOURCE_IDENTITIES, - workflows, - IDENTITIES, - ) - - policy_requirements: dict[tuple[str, str], dict[str, dict[str, str]]] = {} - for name, record in histories.items(): - identities = record["identities"] - self.assertLessEqual(len(identities), SOURCE_IDENTITY_HISTORY_LIMIT) - self.assertEqual(AUTHORITY["workflows"][name]["sha256"], identities[-1]["sha256"]) - previous = record.get("checkpoint", {}).get("predecessor") - for identity in identities: - if previous is None: - self.assertNotIn("supersedes", identity) - else: - self.assertEqual(previous, identity["supersedes"]) - previous = { - "source_commit": identity["source_commit"], - "sha256": identity["sha256"], - } - - binding = identity["qualification_policy"] - policy_identity = (binding["commit"], binding["sha256"]) - if policy_identity not in policy_requirements: - ancestry = subprocess.run( - ["git", "merge-base", "--is-ancestor", binding["commit"], "HEAD"], - cwd=ROOT, - check=False, - capture_output=True, - ) - self.assertEqual(0, ancestry.returncode) - historical_policy = subprocess.run( - ["git", "show", f"{binding['commit']}:{binding['path']}"], - cwd=ROOT, - check=True, - capture_output=True, - ).stdout - self.assertEqual(binding["sha256"], hashlib.sha256(historical_policy).hexdigest()) - policy_requirements[policy_identity] = qualification_requirements( - json.loads(historical_policy), - IDENTITIES, - ) - self.assertEqual( - policy_requirements[policy_identity][name], - { - field: identity["qualification"][field] - for field in ("workflow", "required_check") - }, - ) - - def test_component_loader_reads_only_the_successfully_qualified_exact_revision(self) -> None: - client = FixtureClient(AUTHORITY) - - workflows, source = load_recovery_workflow_authority(client) - self.assertEqual(AUTHORITY["workflows"], workflows) - self.assertEqual(AUTHORITY_COMMIT, source["commit"]) - self.assertEqual(hashlib.sha256(client.raw).hexdigest(), source["sha256"]) - self.assertEqual(AUTHORITY_COMMIT, source["qualification"]["head_sha"]) - self.assertEqual(".github/workflows/beta-candidate.yml", source["qualification"]["path"]) - self.assertEqual("main", source["qualification"]["head_branch"]) - self.assertEqual("success", source["qualification"]["conclusion"]) - self.assertEqual( - [ - ("json", authority_ref_url(), None), - ("json", qualification_runs_url(AUTHORITY_COMMIT), None), - ( - "bytes", - authority_url(AUTHORITY_COMMIT), - "application/vnd.github.raw+json", - ), - ], - client.requests, - ) - - def test_non_green_or_mismatched_qualification_fails_before_manifest_download(self) -> None: - cases = ( - ("pending", [qualification_run("in_progress", None)], "pending"), - ("failed", [qualification_run("completed", "failure")], "failed"), - ("cancelled", [qualification_run("completed", "cancelled")], "cancelled"), - ("absent", [], "absent"), - ("revision-mismatch", [qualification_run(head_sha="b" * 40)], "another commit"), - ) - for label, runs, message in cases: - with self.subTest(state=label): - client = FixtureClient(AUTHORITY, runs) - with self.assertRaisesRegex(RecoveryError, message): - load_recovery_workflow_authority(client) - self.assertFalse(any(method == "bytes" for method, _url, _accept in client.requests)) - - def test_qualification_accepts_the_documented_protected_workflow_ref_suffix(self) -> None: - client = FixtureClient( - AUTHORITY, - [qualification_run(path=".github/workflows/beta-candidate.yml@main")], - ) - - _workflows, source = load_recovery_workflow_authority(client) - - self.assertEqual( - ".github/workflows/beta-candidate.yml@main", - source["qualification"]["path"], - ) - self.assertEqual("main", source["qualification"]["head_branch"]) - - def test_qualification_rejects_wrong_workflow_or_ref_before_manifest_download(self) -> None: - paths = ( - ".github/workflows/source-qualification.yml@main", - ".github/workflows/source-qualification.yml", - ".github/workflows/beta-candidate.yml@v2", - ) - for path in paths: - with self.subTest(path=path): - client = FixtureClient(AUTHORITY, [qualification_run(path=path)]) - with self.assertRaisesRegex(RecoveryError, "absent"): - load_recovery_workflow_authority(client) - self.assertFalse(any(method == "bytes" for method, _url, _accept in client.requests)) - - client = FixtureClient(AUTHORITY, [qualification_run(head_branch="v2")]) - with self.assertRaisesRegex(RecoveryError, "absent"): - load_recovery_workflow_authority(client) - self.assertFalse(any(method == "bytes" for method, _url, _accept in client.requests)) - - def test_mismatched_authority_and_workflow_source_fail_closed(self) -> None: - wrong_branch = copy.deepcopy(AUTHORITY) - wrong_branch["workflows"]["server"]["ref"] = "refs/heads/contributor" - with self.assertRaisesRegex(RecoveryError, "mismatched identity"): - load_recovery_workflow_authority(FixtureClient(wrong_branch)) - - source = "on:\n schedule:\n workflow_dispatch:\n" - digest = normalized_source_sha256(source) - self.assertEqual(digest, verify_recovery_workflow_source("server", source, digest)) - with self.assertRaisesRegex(RecoveryError, "protected source identity"): - verify_recovery_workflow_source("server", source + "# modified\n", digest) - - def test_source_qualification_verifies_every_protected_workflow_source(self) -> None: - client = WorkflowSourceClient() - workflows = validate_authority(client.authority, IDENTITIES) - - evidence = verify_authority_workflow_sources(client, workflows) - - self.assertEqual(set(COMPONENTS), set(evidence)) - for name, entry in evidence.items(): - with self.subTest(component=name): - self.assertEqual(workflows[name]["sha256"], entry["sha256"]) - self.assertEqual(workflows[name]["ref"], entry["ref"]) - - def test_source_qualification_rejects_protected_workflow_drift(self) -> None: - client = WorkflowSourceClient() - workflows = validate_authority(client.authority, IDENTITIES) - client.sources["workflow"] += "# changed after authority qualification\n" - - with self.assertRaisesRegex( - RecoveryWorkflowAuthorityError, - "workflow recovery workflow does not match the protected source identity", - ): - verify_authority_workflow_sources(client, workflows) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_release_asset_mirror.py b/tests/test_release_asset_mirror.py deleted file mode 100644 index 1fca664..0000000 --- a/tests/test_release_asset_mirror.py +++ /dev/null @@ -1,201 +0,0 @@ -from __future__ import annotations - -import json -import subprocess -import tempfile -import unittest -from pathlib import Path -from typing import TypeAlias - -import yaml - -from scripts.release_asset_mirror import MirrorError, repair_asset - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -REPOSITORY = "durable-workflow/.github" -TAG = "beta-candidate/2.0.0-beta.10" -ASSET = "candidate.json" -AUTHORITY = b'{"candidate":"2.0.0-beta.10"}\n' - -Outcome: TypeAlias = list[str] | bytes | RuntimeError | None - - -class FakeGitHub: - def __init__( - self, - *, - views: list[Outcome], - downloads: list[Outcome] | None = None, - upload: Outcome = None, - ) -> None: - self.views = views - self.downloads = downloads or [] - self.upload = upload - self.calls: list[list[str]] = [] - - def __call__(self, command: list[str]) -> subprocess.CompletedProcess[str]: - self.calls.append(command) - operation = tuple(command[1:3]) - if operation == ("release", "view"): - outcome = self.views.pop(0) - if isinstance(outcome, RuntimeError): - return subprocess.CompletedProcess(command, 1, stdout="", stderr=str(outcome)) - return subprocess.CompletedProcess( - command, - 0, - stdout=json.dumps({"assets": [{"name": name} for name in outcome]}), - stderr="", - ) - if operation == ("release", "download"): - outcome = self.downloads.pop(0) - if isinstance(outcome, RuntimeError): - return subprocess.CompletedProcess(command, 1, stdout="", stderr=str(outcome)) - destination = Path(command[command.index("--dir") + 1]) - asset_name = command[command.index("--pattern") + 1] - (destination / asset_name).write_bytes(outcome) - return subprocess.CompletedProcess(command, 0, stdout="", stderr="") - if operation == ("release", "upload"): - if isinstance(self.upload, RuntimeError): - return subprocess.CompletedProcess(command, 1, stdout="", stderr=str(self.upload)) - return subprocess.CompletedProcess(command, 0, stdout="", stderr="") - raise AssertionError(f"unexpected gh command: {command}") - - def call_count(self, operation: tuple[str, str]) -> int: - return sum(tuple(command[1:3]) == operation for command in self.calls) - - -class ReleaseAssetMirrorTest(unittest.TestCase): - def authority_file(self, root: Path) -> Path: - authority = root / ASSET - authority.write_bytes(AUTHORITY) - return authority - - def test_delayed_visibility_after_already_exists_refetches_and_compares(self) -> None: - github = FakeGitHub( - views=[ - [], - RuntimeError("release metadata is temporarily unavailable"), - [], - [ASSET], - ], - downloads=[ - RuntimeError("asset download is not visible yet"), - AUTHORITY, - ], - upload=RuntimeError("asset under the same name already exists"), - ) - sleeps: list[float] = [] - with tempfile.TemporaryDirectory() as temporary: - result = repair_asset( - REPOSITORY, - TAG, - self.authority_file(Path(temporary)), - ASSET, - runner=github, - retry_delays=(0, 0, 0), - sleep=sleeps.append, - ) - - self.assertEqual("matched", result) - self.assertEqual(4, github.call_count(("release", "view"))) - self.assertEqual(1, github.call_count(("release", "upload"))) - self.assertEqual(2, github.call_count(("release", "download"))) - self.assertEqual(3, len(sleeps)) - - def test_already_existing_equal_content_is_accepted_without_upload(self) -> None: - github = FakeGitHub(views=[[ASSET]], downloads=[AUTHORITY]) - with tempfile.TemporaryDirectory() as temporary: - result = repair_asset( - REPOSITORY, - TAG, - self.authority_file(Path(temporary)), - ASSET, - runner=github, - retry_delays=(0, 0), - sleep=lambda _delay: None, - ) - - self.assertEqual("matched", result) - self.assertEqual(1, github.call_count(("release", "view"))) - self.assertEqual(1, github.call_count(("release", "download"))) - self.assertEqual(0, github.call_count(("release", "upload"))) - - def test_conflicting_content_fails_immediately(self) -> None: - github = FakeGitHub( - views=[[], [ASSET]], - downloads=[b'{"candidate":"different"}\n'], - upload=RuntimeError("asset under the same name already exists"), - ) - sleeps: list[float] = [] - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(MirrorError, "differs from immutable Git authority"), - ): - repair_asset( - REPOSITORY, - TAG, - self.authority_file(Path(temporary)), - ASSET, - runner=github, - retry_delays=(0, 0, 0), - sleep=sleeps.append, - ) - - self.assertEqual(1, github.call_count(("release", "download"))) - self.assertEqual(1, github.call_count(("release", "upload"))) - self.assertEqual([], sleeps) - - def test_transient_metadata_reads_stop_at_the_retry_bound(self) -> None: - github = FakeGitHub( - views=[ - RuntimeError("temporary read failure"), - RuntimeError("temporary read failure"), - RuntimeError("temporary read failure"), - ] - ) - with ( - tempfile.TemporaryDirectory() as temporary, - self.assertRaisesRegex(MirrorError, "cannot inspect release assets after 3 attempts"), - ): - repair_asset( - REPOSITORY, - TAG, - self.authority_file(Path(temporary)), - ASSET, - runner=github, - retry_delays=(0, 0), - sleep=lambda _delay: None, - ) - - self.assertEqual(3, github.call_count(("release", "view"))) - self.assertEqual(0, github.call_count(("release", "upload"))) - - def test_beta_candidate_recorder_uses_the_bounded_mirror_helper(self) -> None: - workflow = yaml.safe_load( - (REPOSITORY_ROOT / ".github" / "workflows" / "beta-candidate.yml").read_text(encoding="utf-8") - ) - qualify = workflow["jobs"]["qualify"] - record = workflow["jobs"]["record"] - mirror = next( - step for step in record["steps"] if step.get("name") == "Create or repair the durable GitHub Release mirror" - ) - probe = next( - step - for step in record["steps"] - if step.get("name") == "Exercise recorder-only recovery before any mutation" - ) - - self.assertEqual("qualify", record["needs"]) - self.assertLess(record["steps"].index(probe), record["steps"].index(mirror)) - self.assertNotIn( - "release_asset_mirror.py", - "\n".join(step.get("run", "") for step in qualify["steps"]), - ) - self.assertEqual(2, mirror["run"].count("python scripts/release_asset_mirror.py repair")) - self.assertIn('--repository "$GITHUB_REPOSITORY"', mirror["run"]) - self.assertNotIn("gh release download", mirror["run"]) - self.assertNotIn("gh release upload", mirror["run"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_release_plan.py b/tests/test_release_plan.py deleted file mode 100644 index ea1ab5e..0000000 --- a/tests/test_release_plan.py +++ /dev/null @@ -1,3567 +0,0 @@ -from __future__ import annotations - -import copy -import hashlib -import json -import os -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path -from unittest import mock - -import yaml -from jsonschema import Draft202012Validator, ValidationError - -from scripts.beta_candidate import CandidateError, canonical_json -from scripts.release_plan import ( - COMPONENTS, - CONTINUITY_RESOLUTION_SCHEMA, - CONTINUITY_RESOLUTION_TAG_PREFIX, - FOUNDATION_COMMIT, - FOUNDATION_TAG, - LEGACY_SCHEMA, - OBSERVATION_FAILURE_REASON, - OCCUPIED_SOURCE_MANIFEST_REASON, - PLAN_TAG_PREFIX, - PREPARATION_SCHEMA, - SCHEMA, - SOURCE_CHANGELOGS, - SOURCE_MANIFEST_REASON, - SUPERSESSION_REASON, - candidate_manifest, - check_plan_compatibility, - completion_manifest, - conflict_component_names, - discover_plan, - failed_observation_state, - is_immediate_version_successor, - load_continuity_supersession, - load_plan, - load_public_supersession, - load_source_preparation, - manifest_digest, - materialize_current_plan_authority, - parse_conflict_components, - preflight_plan, - prepare_release, - prepare_supersession, - protected_environment_evidence, - protected_run_approval_evidence, - read_current_completion_authority, - record_completion, - record_plan, - record_supersession, - require_prior_plans_completed, - stage_current_plan_evidence, - terminal_failure_state, - validate_observation_handoff, - validate_plan, - validate_recorded_plan, - validate_release_preparation, - validate_successor_transition, - validate_supersession_handoff, - validate_supersession_record, -) -from scripts.release_plan import ( - main as release_plan_main, -) -from tests.verification_fixture import ( - candidate_verification, - legacy_beta_one_candidate_manifest, - legacy_beta_one_release_plan, -) - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] - - -def cargo_manifest(version: str) -> bytes: - return f'[package]\nname = "durable-workflow"\nversion = "{version}"\n'.encode() - - -def continuity_resolution_qualification() -> dict[str, object]: - return { - "repository": "durable-workflow/.github", - "workflow": ".github/workflows/beta-candidate.yml", - "event": "push", - "head_branch": "main", - "head_sha": "9" * 40, - "run_id": 987, - "run_attempt": 2, - "status": "completed", - "conclusion": "success", - } - - -def continuity_resolution_qualification_run() -> dict[str, object]: - qualification = continuity_resolution_qualification() - return { - "id": qualification["run_id"], - "run_attempt": qualification["run_attempt"], - "repository": {"full_name": "durable-workflow/.github"}, - "head_repository": {"full_name": "durable-workflow/.github"}, - "path": ".github/workflows/beta-candidate.yml@main", - "event": qualification["event"], - "head_branch": qualification["head_branch"], - "head_sha": qualification["head_sha"], - "status": qualification["status"], - "conclusion": qualification["conclusion"], - } - - -def python_manifest(version: str) -> bytes: - return f'[project]\nname = "durable-workflow"\nversion = "{version}"\n'.encode() - - -def planned_source_manifest(url: str, plan: dict[str, object]) -> bytes: - if url.endswith("/pyproject.toml?ref=" + plan["components"]["sdk-python"]["commit"]): - return python_manifest(plan["components"]["sdk-python"]["version"]) - if url.endswith("/Cargo.toml?ref=" + plan["components"]["sdk-rust"]["commit"]): - return cargo_manifest(plan["components"]["sdk-rust"]["version"]) - raise AssertionError(f"unexpected source manifest request: {url}") - - -def source_manifest_record(commit: str, version: str) -> dict[str, object]: - raw = cargo_manifest(version) - return { - "declared_version": version, - "package": "durable-workflow", - "path": "Cargo.toml", - "sha256": hashlib.sha256(raw).hexdigest(), - "source_commit": commit, - "url": f"https://github.com/durable-workflow/sdk-rust/blob/{commit}/Cargo.toml", - } - - -def python_source_manifest_record(commit: str, version: str) -> dict[str, object]: - raw = python_manifest(version) - return { - "declared_version": version, - "package": "durable-workflow", - "path": "pyproject.toml", - "sha256": hashlib.sha256(raw).hexdigest(), - "source_commit": commit, - "url": f"https://github.com/durable-workflow/sdk-python/blob/{commit}/pyproject.toml", - } - - -def release_plan(channel: str = "alpha") -> dict[str, object]: - prerelease = channel - return { - "schema": SCHEMA, - "plan": "recovery-proof-1", - "channel": channel, - "foundation": {"tag": FOUNDATION_TAG, "commit": FOUNDATION_COMMIT}, - "components": { - name: { - "version": f"2.0.0-{prerelease}.{index + 1}" if name in {"workflow", "waterline"} else f"1.2.{index}", - "commit": f"{index + 1:040x}", - } - for index, name in enumerate(COMPONENTS) - }, - "beta_authorization": ( - {"tag": "beta-authorization/recovery-proof-1", "commit": "f" * 40} - if channel in {"beta", "rc"} - else None - ), - } - - -def release_preparation(plan: dict[str, object], release_date: str = "2026-07-19") -> dict[str, object]: - components: dict[str, object] = {} - for name, identity in plan["components"].items(): - body = f"Prepared source changes for {name}." - heading = f"## [{identity['version']}] - {release_date}" - markdown = f"{heading}\n\n{body}\n" - repository = COMPONENTS[name].repository - if name in SOURCE_CHANGELOGS: - kind = "changelog-unreleased" - url = f"https://github.com/{repository}/blob/{identity['commit']}/CHANGELOG.md" - else: - kind = "source-commit-message" - url = f"https://github.com/{repository}/commit/{identity['commit']}" - components[name] = { - "version": identity["version"], - "source_commit": identity["commit"], - "release_notes": { - "format": "text/markdown", - "heading": heading, - "markdown": markdown, - "release_date": release_date, - "sha256": hashlib.sha256(markdown.encode()).hexdigest(), - "source": { - "kind": kind, - "sha256": hashlib.sha256(body.encode()).hexdigest(), - "url": url, - }, - }, - } - preparation = { - "schema": PREPARATION_SCHEMA, - "release_plan": { - "tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "sha256": manifest_digest(plan), - }, - "components": components, - } - validate_release_preparation(preparation, plan) - return preparation - - -def successor_plan( - failed: dict[str, object], - *, - component: str = "waterline", - components: tuple[str, ...] | None = None, -) -> dict[str, object]: - successor = copy.deepcopy(failed) - successor["plan"] = "recovery-proof-2" - for name in components or (component,): - if name == "sdk-rust": - successor["components"][name]["commit"] = "d" * 40 - else: - version = successor["components"][name]["version"] - prefix, number = version.rsplit(".", 1) - successor["components"][name]["version"] = f"{prefix}.{int(number) + 1}" - return successor - - -def continuity_supersession_records( - prior: dict[str, object], - requested: dict[str, object], - prior_commit: str, - *, - accepted_commit: str = "c" * 40, - interruption_commit: str = "d" * 40, -) -> dict[str, object]: - accepted_tag = f"beta-continuity/{requested['plan']}/accepted" - interruption_tag = f"beta-continuity/{prior['plan']}/interrupted" - prior_tag = f"release-plan/{prior['plan']}" - prior_digest = manifest_digest(prior) - requested_digest = manifest_digest(requested) - interruption_evidence = { - "schema": "durable-workflow.beta-continuity.evidence/v1", - "phase": "interrupted", - "outcome": "intentionally-interrupted", - "release_plan": {"tag": prior_tag, "sha256": prior_digest}, - "plan_record": { - "tag": prior_tag, - "commit": prior_commit, - "sha256": prior_digest, - }, - } - accepted_evidence = { - "schema": "durable-workflow.beta-continuity.evidence/v1", - "phase": "accepted", - "outcome": "accepted", - "release_plan": { - "tag": f"release-plan/{requested['plan']}", - "sha256": requested_digest, - }, - "candidate_identity": { - "components": requested["components"], - "plan_sha256": requested_digest, - }, - "superseded_interruption": { - "commit": interruption_commit, - "evidence_sha256": manifest_digest(interruption_evidence), - "plan_sha256": prior_digest, - "reason": "missing-post-acceptance-publication-trigger", - "tag": interruption_tag, - }, - } - return { - "accepted_commit": accepted_commit, - "accepted_evidence": accepted_evidence, - "accepted_plan": copy.deepcopy(requested), - "accepted_tag": accepted_tag, - "interruption_commit": interruption_commit, - "interruption_evidence": interruption_evidence, - "interruption_plan": copy.deepcopy(prior), - "interruption_tag": interruption_tag, - } - - -def environment_protection_evidence() -> dict[str, object]: - return { - "custom_branch_policies": [{"id": 23, "name": "main"}], - "deployment_branch_policy": { - "custom_branch_policies": True, - "protected_branches": False, - }, - "environment_id": 17, - "environment_url": ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=release-plan-supersession" - ), - "required_reviewer_rule_ids": [19], - } - - -def environment_protection_authority() -> tuple[dict[str, object], set[tuple[int, str]]]: - return environment_protection_evidence(), {(29, "release-reviewer")} - - -def environment_approval_evidence() -> dict[str, object]: - return { - "comment": "Supersession reviewed", - "environments": [ - { - "html_url": ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=release-plan-supersession" - ), - "id": 17, - "name": "release-plan-supersession", - "node_id": "ENV_kwDOApproval", - "url": ("https://api.github.com/repos/durable-workflow/.github/environments/release-plan-supersession"), - } - ], - "run_attempt": 1, - "run_id": 456, - "state": "approved", - "user": { - "html_url": "https://github.com/release-reviewer", - "id": 29, - "login": "release-reviewer", - "node_id": "U_kgDOReviewer", - "url": "https://api.github.com/users/release-reviewer", - }, - } - - -def github_environment() -> dict[str, object]: - return { - "id": 17, - "html_url": ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=release-plan-supersession" - ), - "protection_rules": [ - { - "id": 19, - "prevent_self_review": False, - "type": "required_reviewers", - "reviewers": [ - { - "type": "User", - "reviewer": { - "avatar_url": "https://avatars.githubusercontent.com/u/29?v=4", - "html_url": "https://github.com/release-reviewer", - "id": 29, - "login": "release-reviewer", - "node_id": "U_kgDOReviewer", - "site_admin": False, - "type": "User", - "url": "https://api.github.com/users/release-reviewer", - }, - } - ], - } - ], - "deployment_branch_policy": { - "custom_branch_policies": True, - "protected_branches": False, - }, - } - - -def workflow_run() -> dict[str, object]: - return { - "actor": {"login": "release-operator"}, - "conclusion": "success", - "event": "workflow_dispatch", - "head_branch": "main", - "head_sha": "f" * 40, - "html_url": "https://github.com/durable-workflow/.github/actions/runs/456", - "id": 456, - "path": ".github/workflows/release-plan-supersession.yml@main", - "repository": {"full_name": "durable-workflow/.github"}, - "run_attempt": 1, - "status": "completed", - } - - -def approval_history() -> list[dict[str, object]]: - approval = environment_approval_evidence() - return [ - { - "comment": approval["comment"], - "environments": [ - { - **approval["environments"][0], - "can_admins_bypass": True, - "created_at": "2026-07-23T00:00:00Z", - "updated_at": "2026-07-23T00:00:00Z", - } - ], - "state": approval["state"], - "user": { - **approval["user"], - "avatar_url": "https://avatars.githubusercontent.com/u/29?v=4", - "site_admin": False, - "type": "User", - }, - } - ] - - -def supersession_record( - failed: dict[str, object], - successor: dict[str, object], - *, - component: str = "waterline", - failed_commit: str = "a" * 40, -) -> dict[str, object]: - identity = failed["components"][component] - observed_commit = "e" * 40 - return { - "schema": "durable-workflow.release-plan-failure/v1", - "outcome": "terminal-failure", - "failed_plan": { - "tag": f"{PLAN_TAG_PREFIX}{failed['plan']}", - "commit": failed_commit, - "sha256": manifest_digest(failed), - }, - "conflicts": [ - { - "component": component, - "version": identity["version"], - "planned_commit": identity["commit"], - "observed_commit": observed_commit, - "reason": "published-version-source-conflict", - "github_release": { - "id": 123, - "url": "https://github.com/durable-workflow/waterline/releases/1", - }, - "distribution": { - "kind": "composer", - "source_reference": observed_commit, - "dist_reference": observed_commit, - }, - } - ], - "successor_plan": { - "tag": f"{PLAN_TAG_PREFIX}{successor['plan']}", - "sha256": manifest_digest(successor), - }, - "authorization": { - "actor": "release-operator", - "environment": "release-plan-supersession", - "environment_approval": environment_approval_evidence(), - "environment_protection": environment_protection_evidence(), - "repository": "durable-workflow/.github", - "run_attempt": 1, - "run_id": 456, - "run_url": "https://github.com/durable-workflow/.github/actions/runs/456", - "workflow_commit": "f" * 40, - "workflow_ref": ( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - }, - } - - -class ReleasePlanEntryPointTest(unittest.TestCase): - def test_observer_jobs_only_run_on_the_authoritative_github_host(self) -> None: - workflow = yaml.safe_load( - (REPOSITORY_ROOT / ".github" / "workflows" / "release-plan-observer.yml").read_text(encoding="utf-8") - ) - host_guard = "github.server_url == 'https://github.com'" - - self.assertEqual(f"${{{{ {host_guard} }}}}", workflow["jobs"]["observe"]["if"]) - self.assertIn(host_guard, workflow["jobs"]["record"]["if"]) - - def test_observer_preserves_failed_verification_handoffs_and_queues_current_completion(self) -> None: - workflow = yaml.safe_load( - (REPOSITORY_ROOT / ".github" / "workflows" / "release-plan-observer.yml").read_text(encoding="utf-8") - ) - record = workflow["jobs"]["record"] - publish_current = workflow["jobs"]["publish-current"] - self.assertEqual( - {"actions": "read", "attestations": "read", "contents": "write"}, - record["permissions"], - ) - self.assertEqual("write", publish_current["permissions"]["actions"]) - self.assertEqual("read", publish_current["permissions"]["contents"]) - package = next( - step - for step in workflow["jobs"]["observe"]["steps"] - if step.get("name") == "Package the privileged recorder handoff as one immutable file" - ) - self.assertIn("if [ -f verification.json ]; then", package["run"]) - publish = next( - step - for step in publish_current["steps"] - if step.get("name") == "Publish the matching aggregate current authority" - ) - self.assertIn("current_plan_publication.py reconcile-dispatch", publish["run"]) - self.assertIn('--repository "$TARGET_REPOSITORY"', publish["run"]) - self.assertIn('--workflow "$TARGET_WORKFLOW"', publish["run"]) - self.assertIn('--ref "$TARGET_REF"', publish["run"]) - self.assertEqual("record", publish_current["needs"]) - self.assertIn("success()", publish_current["if"]) - self.assertIn("needs.record.outputs.current-plan == 'true'", publish_current["if"]) - self.assertNotIn("actions/download-artifact", str(publish_current)) - - def test_current_train_checks_derive_their_candidate_from_the_current_plan(self) -> None: - current_workflow_path = REPOSITORY_ROOT / ".github" / "workflows" / "current-release-plan.yml" - current_workflow = current_workflow_path.read_text(encoding="utf-8") - parsed_current_workflow = yaml.safe_load(current_workflow) - current_steps = parsed_current_workflow["jobs"]["record"]["steps"] - evidence_staging = next( - step for step in current_steps if "release_plan.py stage-current-evidence" in step.get("run", "") - ) - evidence_upload = next( - step - for step in current_steps - if step.get("uses") == "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" - ) - source_qualification = (REPOSITORY_ROOT / ".github" / "workflows" / "source-qualification.yml").read_text( - encoding="utf-8" - ) - - self.assertNotIn("- 'candidates/main.json'", current_workflow) - self.assertIn("release_plan.py materialize-current", current_workflow) - self.assertIn("release_plan.py current-completion", current_workflow) - self.assertIn("steps.existing.outputs.status != 'existing'", current_workflow) - self.assertIn("load_product_train", current_workflow) - self.assertIn("application/vnd.github.raw+json", current_workflow) - self.assertIn('--completion-status "${{ steps.existing.outputs.status }}"', evidence_staging["run"]) - self.assertEqual( - "${{ runner.temp }}/current-release-plan-evidence/", - evidence_upload["with"]["path"], - ) - self.assertIn("release_plan.py materialize-current", source_qualification) - - def test_observer_uses_the_shared_immutable_discovery_contract(self) -> None: - candidate = release_plan("beta") - tag = f"{PLAN_TAG_PREFIX}{candidate['plan']}" - commit = "a" * 40 - prepared = release_preparation(candidate) - release = { - "tag_name": tag, - "assets": [ - {"name": "release-plan.json", "browser_download_url": "https://example.test/plan"}, - { - "name": "release-preparation.json", - "browser_download_url": "https://example.test/preparation", - }, - ], - } - client = mock.Mock() - client.json.return_value = release - client.bytes.side_effect = [canonical_json(candidate), canonical_json(prepared)] - - with ( - mock.patch( - "scripts.release_plan.recovery_discovery.select_implicit_plan_authority", - return_value={"tag": tag}, - ) as select, - mock.patch("scripts.release_plan.resolve_tag", return_value=commit), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=[candidate, prepared], - ), - ): - selected_tag, selected_plan, selected_preparation = discover_plan(client, None) - - self.assertEqual(tag, selected_tag) - self.assertEqual(candidate, selected_plan) - self.assertEqual(prepared, selected_preparation) - select.assert_called_once() - self.assertNotIn("/releases?per_page=", client.json.call_args.args[0]) - - def test_scheduled_discovery_fails_closed_without_plan_authority(self) -> None: - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - destination = root / "release-plan.json" - preparation = root / "release-preparation.json" - github_output = root / "github-output" - arguments = [ - "release_plan.py", - "discover", - str(destination), - "--preparation", - str(preparation), - "--allow-empty", - "--github-output", - str(github_output), - ] - with ( - mock.patch.object(sys, "argv", arguments), - mock.patch( - "scripts.release_plan.discover_plan", - side_effect=CandidateError("no public release plan is available"), - ), - ): - self.assertEqual(1, release_plan_main()) - - self.assertFalse(github_output.exists()) - self.assertFalse(destination.exists()) - self.assertFalse(preparation.exists()) - - def test_supersession_components_are_canonicalized_in_release_order(self) -> None: - self.assertEqual( - ["waterline", "sdk-rust"], - parse_conflict_components("waterline, sdk-rust"), - ) - with self.assertRaisesRegex(CandidateError, "release-plan component order"): - parse_conflict_components("sdk-rust,waterline") - - def test_workflow_commands_are_directly_executable(self) -> None: - for command in ( - "validate", - "validate-current", - "materialize-current", - "current-completion", - "stage-current-evidence", - "check", - "preflight", - "record", - "record-current-authorization", - "prepare-supersession", - "record-supersession", - "validate-supersession-handoff", - "discover", - "observe", - "validate-observation-handoff", - "complete", - ): - with self.subTest(command=command): - process = subprocess.run( - [sys.executable, "scripts/release_plan.py", command, "--help"], - cwd=REPOSITORY_ROOT, - text=True, - capture_output=True, - check=False, - ) - self.assertEqual(0, process.returncode, process.stderr) - - def test_terminal_and_completion_writers_share_the_plan_registry_lock(self) -> None: - workflow_jobs = { - "current-release-plan.yml": "record", - "release-plan.yml": "validate-and-record", - "release-plan-supersession.yml": "record", - } - for workflow, writer in workflow_jobs.items(): - with self.subTest(workflow=workflow): - parsed = yaml.safe_load( - (REPOSITORY_ROOT / ".github" / "workflows" / workflow).read_text(encoding="utf-8") - ) - self.assertEqual( - "release-plan-registry", - parsed["jobs"][writer]["concurrency"]["group"], - ) - - observer = yaml.safe_load( - (REPOSITORY_ROOT / ".github" / "workflows" / "release-plan-observer.yml").read_text(encoding="utf-8") - ) - self.assertEqual("release-plan-registry", observer["concurrency"]["group"]) - - def test_recorded_accepted_plan_dispatches_continuity_with_scoped_permission(self) -> None: - source = (REPOSITORY_ROOT / ".github" / "workflows" / "release-plan.yml").read_text(encoding="utf-8") - - self.assertIn("needs: validate-and-record", source) - self.assertIn("python scripts/beta_continuity.py dispatch-accepted", source) - self.assertIn("permissions:\n actions: write\n contents: read", source) - - def test_first_rc_plan_uses_the_protected_push_entrypoint_without_beta_continuity(self) -> None: - source = (REPOSITORY_ROOT / ".github" / "workflows" / "release-plan.yml").read_text(encoding="utf-8") - - self.assertIn("release-plans/first-release-candidate.json", source) - self.assertIn("environment: beta-authorization", source) - self.assertIn("needs.validate-and-record.outputs.channel != 'rc'", source) - - -class ReleasePlanValidationTest(unittest.TestCase): - def test_current_authority_materializes_from_the_verified_candidate(self) -> None: - plan, candidate = materialize_current_plan_authority() - verified_candidate = json.loads((REPOSITORY_ROOT / "candidates" / "main.json").read_text(encoding="utf-8")) - - self.assertEqual(candidate_manifest(plan), candidate) - self.assertEqual("current-2-0-20260801", plan["plan"]) - self.assertEqual("2.0.0-rc.13", plan["components"]["server"]["version"]) - self.assertEqual(verified_candidate, candidate) - - def test_current_rc_authority_binds_exact_published_sources(self) -> None: - plan = load_plan(REPOSITORY_ROOT / "release-plans" / "current.json", require_current=True) - expected_components = { - "cli": { - "commit": "e4413464908e5d42ae13071e2c6e6c280da06a1a", - "version": "2.0.0-rc.12", - }, - "sdk-php": { - "commit": "31698e4b97fd36e56f05517a0bd56ec7e16a8c05", - "version": "2.0.0-rc.6", - }, - "sdk-python": { - "commit": "6c5ad457d98834f90799db093c8dff515bee710e", - "version": "2.0.0-rc.8", - }, - "sdk-rust": { - "commit": "527dc8581131a40f127d1d8144a3b55d87829ac8", - "version": "2.0.0-rc.7", - }, - "server": { - "commit": "0f0ce78e8d4eadb91a34a0d09f67e04f5335cdea", - "version": "2.0.0-rc.13", - }, - "waterline": { - "commit": "747ba7712a5e1c6ebee196640d76fc11f77fa67d", - "version": "2.0.0-rc.9", - }, - "workflow": { - "commit": "9416c2a4d3fe71d85f77c2465ed8337c833a79ee", - "version": "2.0.0-rc.12", - }, - } - self.assertEqual("current-2-0-20260801", plan["plan"]) - self.assertEqual(expected_components, plan["components"]) - self.assertEqual( - { - "commit": "40d39e180e914406dcca2d64892180a0483ee2a2", - "tag": "beta-candidate/rc-current-2-0-20260801", - }, - plan["foundation"], - ) - self.assertIsNone(plan["beta_authorization"]) - - preparation = load_source_preparation() - self.assertEqual( - expected_components, - { - name: {"commit": identity["commit"], "version": identity["version"]} - for name, identity in preparation["components"].items() - }, - ) - self.assertEqual( - { - "cli": "760b2b13ac40e2d80cf9f2054e1a5b26f05e47d42066046146d9ac496ec9364b", - "sdk-php": "6b8ce586eaa0129b135d4c836b5bbfec82a5177bebc8106d8d136c1064d27401", - "sdk-python": "4689b2f61c08ab9ac30ef7d2aed192bfd5f6ba998ca3aa86b4beb982ad15fb65", - "sdk-rust": "4c154824f6a01ded7867276cfadc7ba987f790f25e81cf9fcb11fc954476a3bf", - "server": "f5d066dfcd29a1cbea01efc403a426a9ec969b193d5ec3f07f1a3dcb731fdff7", - "waterline": "a977ca7a24386c66dec39377d0004f562573fc27f04801e609450f78c7d4117d", - "workflow": "41f156a13f9483a312179fc57422a25302227e44eaac22da7011f77be558d0c5", - }, - {name: identity["release_notes"]["sha256"] for name, identity in preparation["components"].items()}, - ) - - def test_current_plan_requires_current_product_train(self) -> None: - plan = load_plan(REPOSITORY_ROOT / "release-plans" / "current.json") - - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "release-plan.json" - path.write_bytes(canonical_json(plan)) - self.assertEqual(plan, load_plan(path, require_current=True)) - - plan["components"]["server"]["version"] = "2.0.0-rc.6" - path.write_bytes(canonical_json(plan)) - with self.assertRaisesRegex(CandidateError, "supported product train 2.0.0-rc.13"): - load_plan(path, require_current=True) - - def test_supersession_handoff_binds_dispatch_identities(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - failed_tag = record["failed_plan"]["tag"] - conflict_components = conflict_component_names(record["conflicts"]) - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - record_path = root / "release-plan-failure.json" - successor_path = root / "successor-release-plan.json" - authorized_path = root / "authorized-release-plan-failure.json" - record_path.write_bytes(canonical_json(record)) - successor_path.write_bytes(canonical_json(successor)) - - validated = validate_supersession_handoff( - record_path, - successor_path, - authorized_path, - expected_failed_plan_tag=failed_tag, - expected_conflict_components=",".join(conflict_components), - ) - - self.assertEqual(record, validated) - self.assertEqual(canonical_json(record), authorized_path.read_bytes()) - - with self.assertRaisesRegex(CandidateError, "trusted failed plan dispatch input"): - validate_supersession_handoff( - record_path, - successor_path, - authorized_path, - expected_failed_plan_tag=f"{PLAN_TAG_PREFIX}different-plan", - expected_conflict_components=conflict_components, - ) - with self.assertRaisesRegex(CandidateError, "trusted conflict component dispatch input"): - validate_supersession_handoff( - record_path, - successor_path, - authorized_path, - expected_failed_plan_tag=failed_tag, - expected_conflict_components="server", - ) - - def test_observation_handoff_canonicalizes_only_plan_bound_evidence(self) -> None: - plan = release_plan() - preparation = release_preparation(plan) - candidate = candidate_manifest(plan) - verification = candidate_verification(candidate) - state = { - "schema": "durable-workflow.release-state/v1", - "plan": plan["plan"], - "channel": plan["channel"], - "plan_sha256": manifest_digest(plan), - "observed_at": "2026-07-20T21:00:00Z", - "phase": "complete", - "outcome": "verified", - "components": verification["components"], - "durable_evidence": { - "release_plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "component_actions": "repository Actions runs and public version tags", - "release_preparation_sha256": manifest_digest(preparation), - }, - "resume_action": "No recovery action is required", - } - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - inputs = root / "inputs" - authority = root / "authority" - outputs = root / "outputs" - inputs.mkdir() - authority.mkdir() - paths = { - "plan": inputs / "release-plan.json", - "preparation": inputs / "release-preparation.json", - "candidate": inputs / "candidate-verifier-input.json", - "verification": inputs / "verification.json", - "state": inputs / "release-state.json", - } - for name, value in ( - ("plan", plan), - ("preparation", preparation), - ("candidate", candidate), - ("verification", verification), - ("state", state), - ): - paths[name].write_bytes(canonical_json(value)) - authoritative_plan = authority / "release-plan.json" - authoritative_preparation = authority / "release-preparation.json" - authoritative_plan.write_bytes(canonical_json(plan)) - authoritative_preparation.write_bytes(canonical_json(preparation)) - authority_arguments = { - "authoritative_plan_path": authoritative_plan, - "authoritative_preparation_path": authoritative_preparation, - "expected_plan_tag": f"{PLAN_TAG_PREFIX}{plan['plan']}", - "expected_plan_sha256": manifest_digest(plan), - "expected_preparation_sha256": manifest_digest(preparation), - "expected_verification_outcome": "success", - "client": mock.Mock(), - } - - with mock.patch("scripts.release_plan.revalidate_verification", return_value=verification): - result = validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - **authority_arguments, - ) - - self.assertEqual(f"{PLAN_TAG_PREFIX}{plan['plan']}", result["tag"]) - self.assertEqual(canonical_json(plan), (outputs / "release-plan.json").read_bytes()) - changed_state = copy.deepcopy(state) - changed_state["plan_sha256"] = "f" * 64 - paths["state"].write_bytes(canonical_json(changed_state)) - with ( - mock.patch("scripts.release_plan.revalidate_verification", return_value=verification), - self.assertRaisesRegex(CandidateError, "trusted reconstruction"), - ): - validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - **authority_arguments, - ) - - paths["state"].write_bytes(canonical_json(state)) - mismatched_outcome_arguments = {**authority_arguments, "expected_verification_outcome": "failure"} - with self.assertRaisesRegex(CandidateError, "contradicts the trusted verification-step outcome"): - validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - **mismatched_outcome_arguments, - ) - - injected_state = copy.deepcopy(state) - injected_state["durable_evidence"]["same_user_process"] = "fabricated" - paths["state"].write_bytes(canonical_json(injected_state)) - with ( - mock.patch("scripts.release_plan.revalidate_verification", return_value=verification), - self.assertRaisesRegex(CandidateError, "trusted reconstruction"), - ): - validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - **authority_arguments, - ) - - oversized_state = copy.deepcopy(state) - oversized_state["resume_action"] = "x" * 4097 - paths["state"].write_bytes(canonical_json(oversized_state)) - with self.assertRaisesRegex(CandidateError, "oversized or invalid text"): - validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - **authority_arguments, - ) - - paths["state"].write_bytes(canonical_json(state)) - steered_plan = copy.deepcopy(plan) - steered_plan["plan"] = "credential-steered-plan" - paths["plan"].write_bytes(canonical_json(steered_plan)) - with self.assertRaisesRegex(CandidateError, "originally selected plan tag"): - validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - **authority_arguments, - ) - - def test_failed_observer_reason_tampering_fails_before_first_writer_output(self) -> None: - plan = release_plan() - preparation = release_preparation(plan) - candidate = candidate_manifest(plan) - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - inputs = root / "inputs" - authority = root / "authority" - outputs = root / "outputs" - inputs.mkdir() - authority.mkdir() - paths = { - "plan": inputs / "release-plan.json", - "preparation": inputs / "release-preparation.json", - "candidate": inputs / "candidate-verifier-input.json", - "verification": inputs / "verification.json", - "state": inputs / "release-state.json", - } - paths["plan"].write_bytes(canonical_json(plan)) - paths["preparation"].write_bytes(canonical_json(preparation)) - paths["candidate"].write_bytes(canonical_json(candidate)) - authoritative_plan = authority / "release-plan.json" - authoritative_preparation = authority / "release-preparation.json" - authoritative_plan.write_bytes(canonical_json(plan)) - authoritative_preparation.write_bytes(canonical_json(preparation)) - state = failed_observation_state(plan, preparation, "2026-07-20T21:00:00Z") - state["reason"] = "cli: verifier-controlled failure detail" - paths["state"].write_bytes(canonical_json(state)) - - with self.assertRaisesRegex(CandidateError, "writer's trusted reconstruction"): - validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - authoritative_plan_path=authoritative_plan, - authoritative_preparation_path=authoritative_preparation, - expected_plan_tag=f"{PLAN_TAG_PREFIX}{plan['plan']}", - expected_plan_sha256=manifest_digest(plan), - expected_preparation_sha256=manifest_digest(preparation), - expected_verification_outcome="failure", - client=mock.Mock(), - ) - - self.assertFalse(outputs.exists()) - state["reason"] = OBSERVATION_FAILURE_REASON - paths["state"].write_bytes(canonical_json(state)) - validate_observation_handoff( - paths["plan"], - paths["preparation"], - paths["candidate"], - paths["verification"], - paths["state"], - outputs, - authoritative_plan_path=authoritative_plan, - authoritative_preparation_path=authoritative_preparation, - expected_plan_tag=f"{PLAN_TAG_PREFIX}{plan['plan']}", - expected_plan_sha256=manifest_digest(plan), - expected_preparation_sha256=manifest_digest(preparation), - expected_verification_outcome="failure", - client=mock.Mock(), - ) - durable_state = json.loads((outputs / "release-state.json").read_bytes()) - self.assertEqual(OBSERVATION_FAILURE_REASON, durable_state["reason"]) - - def test_completion_rejects_a_handoff_for_a_different_public_plan(self) -> None: - plan = release_plan() - different = copy.deepcopy(plan) - different["components"]["server"]["commit"] = "e" * 40 - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - plan_path = root / "release-plan.json" - verification_path = root / "verification.json" - plan_path.write_bytes(canonical_json(plan)) - verification_path.write_text("{}", encoding="utf-8") - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value="a" * 40), - mock.patch("scripts.release_plan.read_public_record", return_value=different), - self.assertRaisesRegex(CandidateError, "immutable Git authority"), - ): - record_completion( - root, - plan_path, - verification_path, - remote="origin", - authoritative_completion=root / "authoritative-completion.json", - authoritative_verification=root / "authoritative-verification.json", - client=mock.Mock(), - ) - - def test_preparation_derives_exact_notes_from_immutable_sources(self) -> None: - plan = release_plan() - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - component = next(name for name in SOURCE_CHANGELOGS if f"/{name}/" in url) - return ( - f"# Changelog\n\n## [Unreleased]\n\nSource changes for {component}.\n\n" - "## [0.0.1] - 2026-07-18\n\nEarlier changes.\n" - ).encode() - - def json(self, url: str, **_kwargs: object) -> dict[str, object]: - component = next(name for name in COMPONENTS if f"/{name}/" in url) - return {"commit": {"message": f"Source changes for {component}."}} - - preparation = prepare_release(plan, FixtureClient(), "2026-07-19") - - self.assertEqual(set(COMPONENTS), set(preparation["components"])) - for name, identity in plan["components"].items(): - entry = preparation["components"][name] - self.assertEqual(identity["version"], entry["version"]) - self.assertEqual(identity["commit"], entry["source_commit"]) - self.assertEqual( - f"## [{identity['version']}] - 2026-07-19", - entry["release_notes"]["heading"], - ) - self.assertEqual( - hashlib.sha256(entry["release_notes"]["markdown"].encode()).hexdigest(), - entry["release_notes"]["sha256"], - ) - - preparation["components"]["sdk-php"]["version"] = "9.9.9" - with self.assertRaisesRegex(CandidateError, "different planned identity"): - validate_release_preparation(preparation, plan) - - preparation = release_preparation(plan) - preparation["components"]["sdk-php"]["release_notes"]["source"]["url"] = ( - f"https://github.com/durable-workflow/sdk-php/blob/{'f' * 40}/CHANGELOG.md" - ) - with self.assertRaisesRegex(CandidateError, "invalid note-source evidence"): - validate_release_preparation(preparation, plan) - - def test_preparation_schema_accepts_the_machine_record(self) -> None: - schema = json.loads((REPOSITORY_ROOT / "release-plans" / "preparation-schema.json").read_bytes()) - Draft202012Validator.check_schema(schema) - Draft202012Validator(schema).validate(release_preparation(release_plan())) - - def test_continuity_resolution_schema_requires_qualified_producer_identity(self) -> None: - schema = json.loads( - (REPOSITORY_ROOT / "release-plans" / "continuity-resolution-schema.json").read_bytes() - ) - selection = json.loads( - (REPOSITORY_ROOT / "release-plans" / "continuity-successor-selection.json").read_bytes() - ) - resolution = { - **selection, - "schema": CONTINUITY_RESOLUTION_SCHEMA, - "qualification": continuity_resolution_qualification(), - } - Draft202012Validator.check_schema(schema) - Draft202012Validator(schema).validate(resolution) - for field in ("workflow", "head_sha", "run_id", "run_attempt", "status", "conclusion"): - invalid = copy.deepcopy(resolution) - del invalid["qualification"][field] - with self.subTest(field=field), self.assertRaises(ValidationError): - Draft202012Validator(schema).validate(invalid) - - def test_alpha_plan_is_channel_bound(self) -> None: - plan = release_plan() - validate_plan(plan) - schema = json.loads((REPOSITORY_ROOT / "release-plans" / "schema.json").read_bytes()) - Draft202012Validator.check_schema(schema) - Draft202012Validator(schema).validate(plan) - candidate = candidate_manifest(plan) - self.assertEqual("alpha-recovery-proof-1", candidate["candidate"]) - self.assertEqual(plan["components"], candidate["components"]) - preparation = release_preparation(plan) - completion = completion_manifest(plan, "a" * 40, preparation) - self.assertEqual("alpha", completion["channel"]) - self.assertEqual("durable-workflow.release-candidate/v1", completion["schema"]) - - legacy = copy.deepcopy(plan) - legacy["schema"] = LEGACY_SCHEMA - with self.assertRaisesRegex(CandidateError, "release plan schema"): - validate_plan(legacy) - with self.assertRaisesRegex(CandidateError, "not an exact recorded historical contract"): - validate_recorded_plan(legacy) - - def test_exact_historical_beta_one_plan_and_candidate_remain_paired(self) -> None: - plan = legacy_beta_one_release_plan() - candidate = legacy_beta_one_candidate_manifest() - - self.assertEqual( - "e1fc6e20c9d2ded0b5e7ac4d6be75ba861d31fc4b2db651dc0272dca623f2c7f", - manifest_digest(plan), - ) - validate_recorded_plan(plan) - self.assertEqual(candidate, candidate_manifest(plan)) - - unrecorded = copy.deepcopy(plan) - unrecorded["plan"] = "beta-1-replacement" - with self.assertRaisesRegex(CandidateError, "not an exact recorded historical contract"): - validate_recorded_plan(unrecorded) - - def test_alpha_plan_rejects_beta_authorization(self) -> None: - plan = release_plan() - plan["beta_authorization"] = {"tag": "beta-authorization/recovery-proof-1", "commit": "f" * 40} - with self.assertRaisesRegex(CandidateError, "must not claim beta authorization"): - validate_plan(plan) - - def test_beta_plan_requires_exact_beta_versions_and_authorization(self) -> None: - plan = release_plan("beta") - validate_plan(plan) - plan["components"]["workflow"]["version"] = "2.0.0-alpha.99" - with self.assertRaisesRegex(CandidateError, "not an exact 2.0.0-beta.N identity"): - validate_plan(plan) - - def test_first_release_candidate_binds_the_exact_same_channel_tuple(self) -> None: - plan = load_plan(REPOSITORY_ROOT / "release-plans" / "first-release-candidate.json") - schema = json.loads((REPOSITORY_ROOT / "release-plans" / "schema.json").read_bytes()) - - Draft202012Validator(schema).validate(plan) - self.assertEqual("rc", plan["channel"]) - self.assertEqual("coherent-2-0-rc-1", plan["plan"]) - self.assertEqual( - { - "cli": "2.0.0-rc.1", - "sdk-php": "2.0.0-rc.1", - "sdk-python": "2.0.0-rc.1", - "sdk-rust": "2.0.0-rc.1", - "server": "2.0.0-rc.2", - "waterline": "2.0.0-rc.1", - "workflow": "2.0.0-rc.1", - }, - {name: identity["version"] for name, identity in plan["components"].items()}, - ) - self.assertEqual( - { - "commit": "73985d0c906d96e6edce6915ebe8000bbc19896e", - "tag": "beta-authorization/coherent-2-0-beta-21", - }, - plan["beta_authorization"], - ) - - changed = copy.deepcopy(plan) - changed["components"]["cli"]["version"] = "2.0.0-beta.21" - with self.assertRaisesRegex(CandidateError, "cli version .* is not an exact 2.0.0-rc.N identity"): - validate_plan(changed) - - def test_plan_component_versions_require_strict_semver(self) -> None: - schema = json.loads((REPOSITORY_ROOT / "release-plans" / "schema.json").read_bytes()) - validator = Draft202012Validator(schema) - for malformed in ("01.0.0", "1.0.0-alpha.01", "1.0.0-alpha..1", "1.0.0\n"): - plan = release_plan("beta") - plan["components"]["server"]["version"] = malformed - - with self.subTest(version=malformed), self.assertRaisesRegex( - CandidateError, - "components.server.version must be an exact SemVer release", - ): - validate_plan(plan) - with self.subTest(version=malformed), self.assertRaises(ValidationError): - validator.validate(plan) - - def test_plan_accepts_valid_prerelease_and_build_metadata(self) -> None: - schema = json.loads((REPOSITORY_ROOT / "release-plans" / "schema.json").read_bytes()) - validator = Draft202012Validator(schema) - for valid in ( - "1.0.0-alpha.1", - "1.0.0-alpha.1+build.01", - "1.0.0+build.01", - ): - plan = release_plan("beta") - plan["components"]["server"]["version"] = valid - - with self.subTest(version=valid): - validate_plan(plan) - validator.validate(plan) - - def test_derived_release_plan_schemas_share_strict_semver_validation(self) -> None: - version_schemas = { - "candidate-schema.json": ("$defs", "component", "properties", "version"), - "failure-schema.json": ("$defs", "version"), - "preparation-schema.json": ("$defs", "component", "properties", "version"), - } - malformed_versions = ("01.0.0", "1.0.0-alpha.01", "1.0.0-alpha..1", "1.0.0\n") - valid_versions = ("1.0.0-alpha.1", "1.0.0-alpha.1+build.01", "1.0.0+build.01") - - for filename, path in version_schemas.items(): - schema = json.loads((REPOSITORY_ROOT / "release-plans" / filename).read_bytes()) - Draft202012Validator.check_schema(schema) - version_schema = schema - for segment in path: - version_schema = version_schema[segment] - validator = Draft202012Validator(version_schema) - for malformed in malformed_versions: - with self.subTest(schema=filename, version=malformed), self.assertRaises( - ValidationError - ): - validator.validate(malformed) - for valid in valid_versions: - with self.subTest(schema=filename, version=valid): - validator.validate(valid) - - def test_plan_rejects_a_different_foundation(self) -> None: - plan = release_plan() - plan["foundation"]["commit"] = "0" * 40 - with self.assertRaisesRegex(CandidateError, "proven immutable candidate foundation"): - validate_plan(plan) - - def test_preflight_rejects_python_source_manifest_version_mismatch(self) -> None: - plan = release_plan() - plan["components"]["sdk-python"]["version"] = "0.4.100" - workflow_source = ( - b"on:\n schedule:\n workflow_dispatch:\n" - b"steps:\n - run: recovery resolve --preparation-output release-preparation.json\n" - ) - workflow_digest = hashlib.sha256(workflow_source).hexdigest() - recovery_authority = { - name: { - "repository": component.repository, - "ref": f"refs/heads/{'v2' if name in {'workflow', 'waterline'} else 'main'}", - "path": ".github/workflows/release-plan-recovery.yml", - "state": "active", - "sha256": workflow_digest, - } - for name, component in COMPONENTS.items() - } - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - if url.endswith("pyproject.toml?ref=" + plan["components"]["sdk-python"]["commit"]): - return python_manifest("0.4.99") - if url.endswith("Cargo.toml?ref=" + plan["components"]["sdk-rust"]["commit"]): - return cargo_manifest(plan["components"]["sdk-rust"]["version"]) - if url.endswith("release-plan-recovery.yml?ref=v2") or url.endswith( - "release-plan-recovery.yml?ref=main" - ): - return workflow_source - if url.endswith("scripts/ci/component-release-recovery.py?ref=v2") or url.endswith( - "scripts/ci/component-release-recovery.py?ref=main" - ): - return ( - b'CONTINUITY_TAG_PREFIX = "beta-continuity/"\n' - b"def scheduled_continuity_pause():\n pass\n" - b'if args.plan_tag is None:\n state = {"phase": "continuity-gate"}\n' - ) - raise AssertionError(f"unexpected bytes request: {url}") - - def json(self, url: str, **_kwargs: object) -> object: - if "/actions/workflows/" in url: - return { - "html_url": url, - "id": 1, - "path": ".github/workflows/release-plan-recovery.yml", - "state": "active", - } - if "/commits/" in url: - return {} - repository = url.removeprefix("https://api.github.com/repos/durable-workflow/") - return {"default_branch": "v2" if repository in {"workflow", "waterline"} else "main"} - - with ( - mock.patch( - "scripts.release_plan.read_public_record", - return_value={"candidate": "beta-continuity-foundation"}, - ), - mock.patch("scripts.release_plan.resolve_tag", return_value=None), - mock.patch("scripts.release_plan.require_prior_plans_completed", return_value={}), - mock.patch( - "scripts.release_plan.load_recovery_workflow_authority", - return_value=( - recovery_authority, - { - "repository": "durable-workflow/.github", - "ref": "refs/heads/main", - "commit": "a" * 40, - "path": "release-recovery/authority.json", - "sha256": "b" * 64, - "qualification": {"conclusion": "success"}, - }, - ), - ), - self.assertRaisesRegex(CandidateError, "sdk-python source manifest declares 0.4.99"), - ): - preflight_plan(plan, FixtureClient()) - - def test_new_plan_cannot_strand_an_interrupted_prior_plan(self) -> None: - prior = release_plan() - prior["plan"] = "plan-a" - requested = release_plan() - requested["plan"] = "plan-b" - - class FixtureClient: - def json(self, url: str) -> list[dict[str, str]]: - if url.endswith("matching-refs/tags/beta-continuity/"): - return [] - return [{"ref": "refs/tags/release-plan/plan-a"}] - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=["a" * 40, None, None, None]), - mock.patch("scripts.release_plan.read_public_record", return_value=prior), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - self.assertRaisesRegex(CandidateError, "prior plan release-plan/plan-a is incomplete"), - ): - require_prior_plans_completed(requested, FixtureClient()) - - def test_exact_continuity_successor_can_retain_a_diagnostic_interruption(self) -> None: - prior = release_plan() - prior["plan"] = "plan-a" - requested = release_plan() - requested["plan"] = "plan-b" - prior_commit = "a" * 40 - records = continuity_supersession_records(prior, requested, prior_commit) - - class FixtureClient: - def json(self, _url: str) -> list[dict[str, str]]: - return [{"ref": "refs/tags/release-plan/plan-a"}] - - def resolve(_client: object, _repository: str, tag: str) -> str | None: - return { - "release-plan/plan-a": prior_commit, - "release-candidate/alpha/plan-a": None, - records["accepted_tag"]: records["accepted_commit"], - records["interruption_tag"]: records["interruption_commit"], - }.get(tag) - - def read_record(_client: object, tag: str, _commit: str, filename: str) -> dict[str, object]: - if tag == "release-plan/plan-a": - return prior - if tag == records["accepted_tag"]: - return records["accepted_evidence"] if filename == "continuity-evidence.json" else requested - if tag == records["interruption_tag"]: - return records["interruption_evidence"] if filename == "continuity-evidence.json" else prior - raise AssertionError(f"unexpected public record {tag}:{filename}") - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - ): - evidence = require_prior_plans_completed(requested, FixtureClient()) - - self.assertEqual( - "superseded-diagnostic-interruption", - evidence["release-plan/plan-a"]["outcome"], - ) - self.assertEqual(records["accepted_commit"], evidence["release-plan/plan-a"]["accepted_commit"]) - - def test_completed_continuity_successors_terminalize_the_interruption_for_a_future_plan(self) -> None: - interrupted = release_plan() - interrupted["plan"] = "plan-a" - first_successor = release_plan() - first_successor["plan"] = "plan-b" - second_successor = release_plan() - second_successor["plan"] = "plan-c" - ordinary = release_plan() - ordinary["plan"] = "plan-x" - future = release_plan("beta") - future["plan"] = "plan-d" - plan_commits = { - "release-plan/plan-a": "a" * 40, - "release-plan/plan-b": "b" * 40, - "release-plan/plan-c": "c" * 40, - "release-plan/plan-x": "3" * 40, - } - first_records = continuity_supersession_records( - interrupted, - first_successor, - plan_commits["release-plan/plan-a"], - accepted_commit="d" * 40, - interruption_commit="e" * 40, - ) - second_records = continuity_supersession_records( - interrupted, - second_successor, - plan_commits["release-plan/plan-a"], - accepted_commit="f" * 40, - interruption_commit="e" * 40, - ) - completion_commits = { - "release-candidate/alpha/plan-b": "1" * 40, - "release-candidate/alpha/plan-c": "2" * 40, - "release-candidate/alpha/plan-x": "4" * 40, - } - ordinary_records = continuity_supersession_records( - interrupted, - ordinary, - plan_commits["release-plan/plan-a"], - accepted_commit="5" * 40, - interruption_commit="e" * 40, - ) - ordinary_records["accepted_evidence"].pop("superseded_interruption") - - resolution = { - "schema": CONTINUITY_RESOLUTION_SCHEMA, - "qualification": continuity_resolution_qualification(), - "interruption": { - "plan": { - "tag": "release-plan/plan-a", - "commit": plan_commits["release-plan/plan-a"], - "sha256": manifest_digest(interrupted), - }, - "evidence": { - "tag": first_records["interruption_tag"], - "commit": first_records["interruption_commit"], - "sha256": manifest_digest(first_records["interruption_evidence"]), - }, - }, - "successor_claims": [ - { - "plan": { - "tag": "release-plan/plan-b", - "commit": plan_commits["release-plan/plan-b"], - "sha256": manifest_digest(first_successor), - }, - "acceptance": { - "tag": first_records["accepted_tag"], - "commit": first_records["accepted_commit"], - "sha256": manifest_digest(first_records["accepted_evidence"]), - }, - }, - { - "plan": { - "tag": "release-plan/plan-c", - "commit": plan_commits["release-plan/plan-c"], - "sha256": manifest_digest(second_successor), - }, - "acceptance": { - "tag": second_records["accepted_tag"], - "commit": second_records["accepted_commit"], - "sha256": manifest_digest(second_records["accepted_evidence"]), - }, - }, - ], - } - resolution["selected_successor"] = resolution["successor_claims"][1]["plan"] - resolution_tag = f"{CONTINUITY_RESOLUTION_TAG_PREFIX}plan-a/{manifest_digest(resolution)}" - resolution_commit = "6" * 40 - - class FixtureClient: - def __init__( - self, - resolution_tags: list[str], - qualification_run: object | None = None, - ) -> None: - self.resolution_tags = resolution_tags - self.qualification_run = ( - continuity_resolution_qualification_run() - if qualification_run is None - else qualification_run - ) - - def json(self, url: str) -> object: - if url.endswith("matching-refs/tags/release-plan/"): - return [{"ref": f"refs/tags/{tag}"} for tag in plan_commits] - if url.endswith("matching-refs/tags/release-plan-continuity-resolution/plan-a/"): - return [{"ref": f"refs/tags/{tag}"} for tag in self.resolution_tags] - if "/actions/runs/987/attempts/2" in url: - return self.qualification_run - raise AssertionError(f"unexpected registry request {url}") - - def resolve(_client: object, _repository: str, tag: str) -> str | None: - return { - **plan_commits, - **completion_commits, - first_records["accepted_tag"]: first_records["accepted_commit"], - second_records["accepted_tag"]: second_records["accepted_commit"], - ordinary_records["accepted_tag"]: ordinary_records["accepted_commit"], - first_records["interruption_tag"]: first_records["interruption_commit"], - resolution_tag: resolution_commit, - }.get(tag) - - plans = { - "release-plan/plan-a": interrupted, - "release-plan/plan-b": first_successor, - "release-plan/plan-c": second_successor, - "release-plan/plan-x": ordinary, - } - accepted = { - first_records["accepted_tag"]: first_records, - second_records["accepted_tag"]: second_records, - ordinary_records["accepted_tag"]: ordinary_records, - } - - def read_record(_client: object, tag: str, _commit: str, filename: str) -> dict[str, object]: - if tag in plans: - if filename == "release-preparation.json": - raise CandidateError("public request failed (404)") - return plans[tag] - if tag in accepted: - records = accepted[tag] - return ( - records["accepted_evidence"] if filename == "continuity-evidence.json" else records["accepted_plan"] - ) - if tag == first_records["interruption_tag"]: - return ( - first_records["interruption_evidence"] - if filename == "continuity-evidence.json" - else first_records["interruption_plan"] - ) - if tag == resolution_tag: - return resolution - if tag == "release-candidate/alpha/plan-b": - return completion_manifest(first_successor, plan_commits["release-plan/plan-b"]) - if tag == "release-candidate/alpha/plan-c": - return completion_manifest(second_successor, plan_commits["release-plan/plan-c"]) - if tag == "release-candidate/alpha/plan-x": - return completion_manifest(ordinary, plan_commits["release-plan/plan-x"]) - raise AssertionError(f"unexpected public record {tag}:{filename}") - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - ): - with self.assertRaisesRegex(CandidateError, "multiple continuity successors"): - require_prior_plans_completed(future, FixtureClient([])) - evidence = require_prior_plans_completed(future, FixtureClient([resolution_tag])) - - terminal = evidence["release-plan/plan-a"] - self.assertEqual("superseded-diagnostic-interruption", terminal["outcome"]) - self.assertEqual(second_records["accepted_tag"], terminal["accepted_tag"]) - self.assertEqual("release-plan/plan-c", terminal["successor_plan_tag"]) - self.assertEqual("completed", evidence["release-plan/plan-b"]["outcome"]) - self.assertEqual("completed", evidence["release-plan/plan-c"]["outcome"]) - self.assertEqual("completed", evidence["release-plan/plan-x"]["outcome"]) - invalid_qualifications = ( - ([], "qualification is absent"), - ( - {**continuity_resolution_qualification_run(), "status": "queued", "conclusion": None}, - "qualification is pending", - ), - ( - {**continuity_resolution_qualification_run(), "conclusion": "failure"}, - "qualification failed", - ), - ( - {**continuity_resolution_qualification_run(), "conclusion": "cancelled"}, - "qualification was cancelled", - ), - ( - {**continuity_resolution_qualification_run(), "head_sha": "8" * 40}, - "another source revision", - ), - ( - { - **continuity_resolution_qualification_run(), - "path": ".github/workflows/untrusted.yml@main", - }, - "untrusted workflow", - ), - ) - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - ): - for run, message in invalid_qualifications: - with self.subTest(qualification=message), self.assertRaisesRegex( - CandidateError, - message, - ): - require_prior_plans_completed( - future, - FixtureClient([resolution_tag], run), - ) - - def test_accepted_but_incomplete_continuity_successor_does_not_terminalize_the_interruption(self) -> None: - interrupted = release_plan() - interrupted["plan"] = "plan-a" - successor = release_plan() - successor["plan"] = "plan-b" - future = release_plan("beta") - future["plan"] = "plan-c" - prior_commit = "a" * 40 - successor_commit = "b" * 40 - records = continuity_supersession_records(interrupted, successor, prior_commit) - - class FixtureClient: - def json(self, url: str) -> list[dict[str, str]]: - if url.endswith("matching-refs/tags/release-plan/"): - return [ - {"ref": "refs/tags/release-plan/plan-a"}, - {"ref": "refs/tags/release-plan/plan-b"}, - ] - raise AssertionError(f"unexpected registry request {url}") - - def resolve(_client: object, _repository: str, tag: str) -> str | None: - return { - "release-plan/plan-a": prior_commit, - "release-plan/plan-b": successor_commit, - records["accepted_tag"]: records["accepted_commit"], - records["interruption_tag"]: records["interruption_commit"], - }.get(tag) - - def read_record(_client: object, tag: str, _commit: str, filename: str) -> dict[str, object]: - if tag == "release-plan/plan-a": - return interrupted - if tag == "release-plan/plan-b": - return successor - if tag == records["accepted_tag"]: - return records["accepted_evidence"] if filename == "continuity-evidence.json" else successor - if tag == records["interruption_tag"]: - return records["interruption_evidence"] if filename == "continuity-evidence.json" else interrupted - raise AssertionError(f"unexpected public record {tag}:{filename}") - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - self.assertRaisesRegex(CandidateError, "successor release-plan/plan-b has no immutable completion"), - ): - require_prior_plans_completed(future, FixtureClient()) - - def test_identity_mismatched_continuity_successor_does_not_terminalize_the_interruption(self) -> None: - interrupted = release_plan() - interrupted["plan"] = "plan-a" - successor = release_plan() - successor["plan"] = "plan-b" - future = release_plan("beta") - future["plan"] = "plan-c" - prior_commit = "a" * 40 - records = continuity_supersession_records(interrupted, successor, prior_commit) - records["accepted_evidence"]["superseded_interruption"]["commit"] = "f" * 40 - - class FixtureClient: - def json(self, url: str) -> list[dict[str, str]]: - if url.endswith("matching-refs/tags/release-plan/"): - return [ - {"ref": "refs/tags/release-plan/plan-a"}, - {"ref": "refs/tags/release-plan/plan-b"}, - ] - raise AssertionError(f"unexpected registry request {url}") - - def resolve(_client: object, _repository: str, tag: str) -> str | None: - return { - "release-plan/plan-a": prior_commit, - records["accepted_tag"]: records["accepted_commit"], - records["interruption_tag"]: records["interruption_commit"], - }.get(tag) - - def read_record(_client: object, tag: str, _commit: str, filename: str) -> dict[str, object]: - if tag == "release-plan/plan-a": - return interrupted - if tag == records["accepted_tag"]: - return records["accepted_evidence"] if filename == "continuity-evidence.json" else successor - raise AssertionError(f"unexpected public record {tag}:{filename}") - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - self.assertRaisesRegex(CandidateError, "superseded interruption .* resolves to"), - ): - require_prior_plans_completed(future, FixtureClient()) - - def test_one_continuity_acceptance_cannot_clear_an_unrelated_incomplete_plan(self) -> None: - prior = release_plan() - prior["plan"] = "plan-a" - unrelated = release_plan() - unrelated["plan"] = "plan-x" - requested = release_plan() - requested["plan"] = "plan-b" - prior_commit = "a" * 40 - unrelated_commit = "b" * 40 - records = continuity_supersession_records(prior, requested, prior_commit) - - class FixtureClient: - def json(self, _url: str) -> list[dict[str, str]]: - return [ - {"ref": "refs/tags/release-plan/plan-a"}, - {"ref": "refs/tags/release-plan/plan-x"}, - ] - - def resolve(_client: object, _repository: str, tag: str) -> str | None: - return { - "release-plan/plan-a": prior_commit, - "release-plan/plan-x": unrelated_commit, - "release-candidate/alpha/plan-a": None, - "release-candidate/alpha/plan-x": None, - records["accepted_tag"]: records["accepted_commit"], - records["interruption_tag"]: records["interruption_commit"], - }.get(tag) - - def read_record(_client: object, tag: str, _commit: str, filename: str) -> dict[str, object]: - if tag == "release-plan/plan-a": - return prior - if tag == "release-plan/plan-x": - return unrelated - if tag == records["accepted_tag"]: - return records["accepted_evidence"] if filename == "continuity-evidence.json" else requested - if tag == records["interruption_tag"]: - return records["interruption_evidence"] if filename == "continuity-evidence.json" else prior - raise AssertionError(f"unexpected public record {tag}:{filename}") - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - self.assertRaisesRegex(CandidateError, "invalid superseded interruption identity"), - ): - require_prior_plans_completed(requested, FixtureClient()) - - def test_continuity_supersession_rejects_forged_or_stale_evidence(self) -> None: - prior = release_plan() - prior["plan"] = "plan-a" - requested = release_plan() - requested["plan"] = "plan-b" - prior_commit = "a" * 40 - - def accepted_plan_mismatch(records: dict[str, object]) -> None: - records["accepted_plan"]["components"]["server"]["commit"] = "e" * 40 - - def accepted_outcome_mismatch(records: dict[str, object]) -> None: - records["accepted_evidence"]["outcome"] = "waiting" - - def missing_identity(records: dict[str, object]) -> None: - records["accepted_evidence"].pop("superseded_interruption") - - def forged_interruption_tag(records: dict[str, object]) -> None: - records["accepted_evidence"]["superseded_interruption"]["tag"] = "beta-continuity/unrelated/interrupted" - - def mismatched_interruption_commit(records: dict[str, object]) -> None: - records["accepted_evidence"]["superseded_interruption"]["commit"] = "e" * 40 - - def mismatched_evidence_digest(records: dict[str, object]) -> None: - records["accepted_evidence"]["superseded_interruption"]["evidence_sha256"] = "e" * 64 - - def stale_plan_record(records: dict[str, object]) -> None: - records["interruption_evidence"]["plan_record"]["commit"] = "e" * 40 - records["accepted_evidence"]["superseded_interruption"]["evidence_sha256"] = manifest_digest( - records["interruption_evidence"] - ) - - def interruption_outcome_mismatch(records: dict[str, object]) -> None: - records["interruption_evidence"]["outcome"] = "complete" - records["accepted_evidence"]["superseded_interruption"]["evidence_sha256"] = manifest_digest( - records["interruption_evidence"] - ) - - cases = ( - ("accepted plan mismatch", accepted_plan_mismatch, "does not prove exact requested plan"), - ("accepted outcome mismatch", accepted_outcome_mismatch, "does not prove exact requested plan"), - ("missing supersession identity", missing_identity, "invalid superseded interruption identity"), - ("forged interruption tag", forged_interruption_tag, "invalid superseded interruption identity"), - ("mismatched interruption commit", mismatched_interruption_commit, "resolves to"), - ("mismatched evidence digest", mismatched_evidence_digest, "does not prove prior plan"), - ("stale plan record", stale_plan_record, "does not prove prior plan"), - ("interruption outcome mismatch", interruption_outcome_mismatch, "does not prove prior plan"), - ) - for name, mutate, message in cases: - with self.subTest(name=name): - records = continuity_supersession_records(prior, requested, prior_commit) - mutate(records) - - def resolve( - _client: object, - _repository: str, - tag: str, - current_records: dict[str, object] = records, - ) -> str | None: - return { - current_records["accepted_tag"]: current_records["accepted_commit"], - current_records["interruption_tag"]: current_records["interruption_commit"], - }.get(tag) - - def read_record( - _client: object, - tag: str, - _commit: str, - filename: str, - current_records: dict[str, object] = records, - ) -> dict[str, object]: - if tag == current_records["accepted_tag"]: - return ( - current_records["accepted_evidence"] - if filename == "continuity-evidence.json" - else current_records["accepted_plan"] - ) - if tag == current_records["interruption_tag"]: - return ( - current_records["interruption_evidence"] - if filename == "continuity-evidence.json" - else current_records["interruption_plan"] - ) - raise AssertionError(f"unexpected public record {tag}:{filename}") - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - self.assertRaisesRegex(CandidateError, message), - ): - load_continuity_supersession(requested, prior, prior_commit, object()) - - def test_continuity_supersession_rejects_a_moved_accepted_tag(self) -> None: - prior = release_plan() - prior["plan"] = "plan-a" - requested = release_plan() - requested["plan"] = "plan-b" - - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value="c" * 40), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=CandidateError("public record beta-continuity/plan-b/accepted resolves to a moved commit"), - ), - self.assertRaisesRegex(CandidateError, "moved commit"), - ): - load_continuity_supersession(requested, prior, "a" * 40, object()) - - def test_new_plan_checks_all_matching_refs_when_registry_exceeds_one_hundred(self) -> None: - requested = release_plan() - requested["plan"] = "plan-b" - requested_urls: list[str] = [] - - class FixtureClient: - def json(self, url: str) -> list[dict[str, str]]: - requested_urls.append(url) - return [ - *[{"ref": f"refs/tags/release-plan/completed-{index:03d}"} for index in range(125)], - {"ref": "refs/tags/release-plan/plan-a"}, - ] - - def plan_for_tag(tag: str) -> dict[str, object]: - prior = release_plan() - prior["plan"] = tag.removeprefix("release-plan/") - return prior - - def resolve(_client: object, _repository: str, tag: str) -> str | None: - if tag.startswith("beta-continuity/"): - return None - if tag == "release-candidate/alpha/plan-a": - return None - return "b" * 40 if tag.startswith("release-candidate/") else "a" * 40 - - def read_record(_client: object, tag: str, commit: str, filename: str) -> dict[str, object]: - if tag.startswith("release-plan/"): - plan = plan_for_tag(tag) - return release_preparation(plan) if filename == "release-preparation.json" else plan - plan_tag = tag.removeprefix("release-candidate/alpha/") - plan = plan_for_tag(f"release-plan/{plan_tag}") - return completion_manifest(plan, "a" * 40, release_preparation(plan)) - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", side_effect=read_record), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - mock.patch("scripts.release_plan.load_continuity_supersession", return_value=None), - self.assertRaisesRegex(CandidateError, "prior plan release-plan/plan-a is incomplete"), - ): - require_prior_plans_completed(requested, FixtureClient()) - - self.assertEqual( - ["https://api.github.com/repos/durable-workflow/.github/git/matching-refs/tags/release-plan/"], - requested_urls, - ) - - def test_completed_prior_plan_allows_the_next_plan(self) -> None: - prior = release_plan() - prior["plan"] = "plan-a" - requested = release_plan() - requested["plan"] = "plan-b" - record_commit = "a" * 40 - completed_commit = "b" * 40 - preparation = release_preparation(prior) - completion = completion_manifest(prior, record_commit, preparation) - - class FixtureClient: - def json(self, _url: str) -> list[dict[str, str]]: - return [{"ref": "refs/tags/release-plan/plan-a"}] - - with ( - mock.patch( - "scripts.release_plan.resolve_tag", - side_effect=[record_commit, completed_commit], - ), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=[prior, completion, preparation], - ), - mock.patch("scripts.release_plan.load_public_supersession", return_value=None), - ): - evidence = require_prior_plans_completed(requested, FixtureClient()) - self.assertEqual(completed_commit, evidence["release-plan/plan-a"]["completion_commit"]) - - def test_terminal_failure_admits_only_the_exact_immediate_successor(self) -> None: - failed = release_plan() - failed["plan"] = "plan-a" - successor = successor_plan(failed) - successor["plan"] = "plan-b" - record_commit = "a" * 40 - failure = supersession_record(failed, successor, failed_commit=record_commit) - - class FixtureClient: - def json(self, _url: str) -> list[dict[str, str]]: - return [{"ref": "refs/tags/release-plan/plan-a"}] - - with ( - mock.patch( - "scripts.release_plan.resolve_tag", - side_effect=[record_commit, None, "b" * 40, None], - ), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=[failed, failure, successor], - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - return_value=environment_protection_authority(), - ), - mock.patch( - "scripts.release_plan.protected_run_approval_evidence", - return_value=environment_approval_evidence(), - ), - ): - evidence = require_prior_plans_completed(successor, FixtureClient()) - self.assertEqual("terminal-failure", evidence["release-plan/plan-a"]["outcome"]) - - different = copy.deepcopy(successor) - different["components"]["server"]["commit"] = "d" * 40 - with ( - mock.patch( - "scripts.release_plan.resolve_tag", - side_effect=[record_commit, None, "b" * 40, None], - ), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=[failed, failure, successor], - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - return_value=environment_protection_authority(), - ), - mock.patch( - "scripts.release_plan.protected_run_approval_evidence", - return_value=environment_approval_evidence(), - ), - self.assertRaisesRegex(CandidateError, "admits only exact successor"), - ): - require_prior_plans_completed(different, FixtureClient()) - - later = copy.deepcopy(successor) - later["plan"] = "plan-c" - with ( - mock.patch( - "scripts.release_plan.resolve_tag", - side_effect=[record_commit, None, "b" * 40, "c" * 40], - ), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=[failed, failure, successor, successor], - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - return_value=environment_protection_authority(), - ), - mock.patch( - "scripts.release_plan.protected_run_approval_evidence", - return_value=environment_approval_evidence(), - ), - ): - evidence = require_prior_plans_completed(later, FixtureClient()) - self.assertEqual("terminal-failure", evidence["release-plan/plan-a"]["outcome"]) - - def test_successor_rejects_skipped_versions_and_unaffected_changes(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - validate_successor_transition(failed, successor, "waterline") - self.assertTrue(is_immediate_version_successor("2.0.0-alpha.135", "2.0.0-alpha.136")) - - skipped = copy.deepcopy(successor) - skipped["components"]["waterline"]["version"] = "2.0.0-alpha.4" - with self.assertRaisesRegex(CandidateError, "immediate next"): - validate_successor_transition(failed, skipped, "waterline") - - changed = copy.deepcopy(successor) - changed["components"]["server"]["commit"] = "d" * 40 - with self.assertRaisesRegex(CandidateError, "unaffected component server"): - validate_successor_transition(failed, changed, "waterline") - - def test_exact_semver_successors_cover_both_conflict_paths(self) -> None: - long_numeric = "9" * 4301 - cases = ( - ("release", "1.2.3", "1.2.4"), - ("prerelease", "1.2.3-alpha.9", "1.2.3-alpha.10"), - ("release-build", "1.2.3+build.1", "1.2.4+build.2"), - ( - "prerelease-build", - "1.2.3-alpha.9+build.1", - "1.2.3-alpha.10+build.2", - ), - ("single-numeric-prerelease", "1.2.3-9", "1.2.3-10"), - ( - "single-numeric-prerelease-build", - "1.2.3-9+build.1", - "1.2.3-10+build.2", - ), - ("nonnumeric-prerelease", "1.2.3-rc", "1.2.3-rc.1"), - ( - "nonnumeric-prerelease-build", - "1.2.3-rc+build.1", - "1.2.3-rc.1+build.2", - ), - ( - "long-core", - f"1.2.{long_numeric}", - f"1.2.1{'0' * 4301}", - ), - ( - "long-prerelease", - f"1.2.3-alpha.{long_numeric}", - f"1.2.3-alpha.1{'0' * 4301}", - ), - ) - - for reason in (SUPERSESSION_REASON, OCCUPIED_SOURCE_MANIFEST_REASON): - for label, previous_version, successor_version in cases: - failed = release_plan("beta") - failed["plan"] = f"semver-{label}-failed" - failed["components"]["server"]["version"] = previous_version - successor = copy.deepcopy(failed) - successor["plan"] = f"semver-{label}-successor" - successor["components"]["server"]["version"] = successor_version - if reason == OCCUPIED_SOURCE_MANIFEST_REASON: - successor["components"]["server"]["commit"] = "e" * 40 - - with self.subTest(reason=reason, kind=label): - validate_successor_transition( - failed, - successor, - [{"component": "server", "reason": reason}], - ) - - failed = release_plan("beta") - failed["plan"] = "semver-long-skipped-failed" - failed["components"]["server"]["version"] = f"1.2.{long_numeric}" - successor = copy.deepcopy(failed) - successor["plan"] = "semver-long-skipped-successor" - successor["components"]["server"]["version"] = f"1.2.2{'0' * 4301}" - if reason == OCCUPIED_SOURCE_MANIFEST_REASON: - successor["components"]["server"]["commit"] = "e" * 40 - - with ( - self.subTest(reason=reason, kind="invalid"), - self.assertRaisesRegex( - CandidateError, - "immediate next version", - ), - ): - validate_successor_transition( - failed, - successor, - [{"component": "server", "reason": reason}], - ) - - def test_terminal_record_rejects_mutated_evidence(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - validate_supersession_record(record, failed, "a" * 40, successor) - - record["conflicts"][0]["observed_commit"] = "d" * 40 - with self.assertRaisesRegex(CandidateError, "distribution evidence"): - validate_supersession_record(record, failed, "a" * 40, successor) - - def test_terminal_record_accepts_github_environment_activity_url(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - - self.assertEqual( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=release-plan-supersession", - record["authorization"]["environment_protection"]["environment_url"], - ) - validate_supersession_record(record, failed, "a" * 40, successor) - - record["authorization"]["environment_protection"]["environment_url"] = ( - "https://github.com/durable-workflow/.github/settings/environments/1" - ) - with self.assertRaisesRegex(CandidateError, "protected-environment reviewer evidence"): - validate_supersession_record(record, failed, "a" * 40, successor) - - def test_terminal_record_rejects_unbound_or_malformed_approval_evidence(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - - mutations = ( - ( - "wrong run", - lambda record: record["authorization"]["environment_approval"].update({"run_id": 999}), - "approved deployment bound", - ), - ( - "wrong environment", - lambda record: record["authorization"]["environment_approval"]["environments"][0].update( - {"name": "staging"} - ), - "wrong protected environment", - ), - ( - "malformed user", - lambda record: record["authorization"]["environment_approval"]["user"].pop("node_id"), - "approving user evidence", - ), - ) - for name, mutate, error in mutations: - with self.subTest(name=name): - record = supersession_record(failed, successor) - mutate(record) - with self.assertRaisesRegex(CandidateError, error): - validate_supersession_record(record, failed, "a" * 40, successor) - - def test_loading_terminal_record_requires_live_github_authority(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value="b" * 40), - mock.patch("scripts.release_plan.read_public_record", side_effect=[record, successor]), - mock.patch( - "scripts.release_plan.revalidate_conflict_public_evidence", - side_effect=AssertionError("historical records must not revalidate conflict evidence"), - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - return_value=environment_protection_authority(), - ), - mock.patch( - "scripts.release_plan.protected_run_approval_evidence", - return_value=environment_approval_evidence(), - ), - ): - loaded = load_public_supersession(failed, "a" * 40, object()) - self.assertEqual(record, loaded[2]) - self.assertEqual(successor, loaded[3]) - - def test_terminal_record_rejects_unverifiable_github_authority(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value="b" * 40), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=[record, successor], - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - side_effect=CandidateError("environment policy unavailable"), - ), - self.assertRaisesRegex(CandidateError, "environment policy unavailable"), - ): - load_public_supersession(failed, "a" * 40, object()) - - def test_terminal_record_rejects_fabricated_authorization_identity(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - record["authorization"]["workflow_ref"] = ( - "durable-workflow/.github/.github/workflows/release-plan.yml@refs/heads/main" - ) - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value="b" * 40), - mock.patch( - "scripts.release_plan.read_public_record", - side_effect=[record, successor], - ), - self.assertRaisesRegex(CandidateError, "not authorized by the protected supersession workflow"), - ): - load_public_supersession(failed, "a" * 40, object()) - - -class ReleasePlanSupersessionTest(unittest.TestCase): - def prepare_occupied_python_conflict( - self, - ) -> tuple[dict[str, object], dict[str, object], dict[str, object], str]: - failed = release_plan() - failed["plan"] = "plan-a" - failed["components"]["sdk-python"] = { - "version": "0.4.100", - "commit": "2018400368cf4251c58b24b3d53a99f0ca3512e3", - } - successor = copy.deepcopy(failed) - successor["plan"] = "plan-b" - successor["components"]["sdk-python"] = { - "version": "0.4.101", - "commit": "d" * 40, - } - failed_commit = "a" * 40 - source_tag = { - "repository": "durable-workflow/sdk-python", - "tag": "0.4.100", - "tag_object": failed["components"]["sdk-python"]["commit"], - "commit": failed["components"]["sdk-python"]["commit"], - "url": "https://github.com/durable-workflow/sdk-python/tree/0.4.100", - } - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - if failed["components"]["sdk-python"]["commit"] in url: - return python_manifest("0.4.99") - if successor["components"]["sdk-python"]["commit"] in url: - return python_manifest("0.4.101") - return planned_source_manifest(url, failed) - - def json(self, url: str, **_kwargs: object) -> object: - if url.endswith("deployment-branch-policies?per_page=100"): - return { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "main", "type": "branch"}], - } - if "/environments/" in url: - return github_environment() - if url.endswith("/approvals"): - return approval_history() - if "/actions/runs/" in url: - return workflow_run() - if url.endswith("/releases/tags/0.4.100") or url.endswith("/pypi/durable-workflow/0.4.100/json"): - raise CandidateError(f"public request failed (404) for {url}") - raise AssertionError(f"unexpected JSON request: {url}") - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == "release-plan/plan-a": - return failed_commit - if repository == "durable-workflow/sdk-python" and tag == "0.4.100": - return failed["components"]["sdk-python"]["commit"] - return None - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - mock.patch("scripts.release_plan.resolve_github_tag", return_value=source_tag), - ): - record, durable_successor = prepare_supersession( - "release-plan/plan-a", - ["sdk-python"], - successor, - FixtureClient(), - actor="release-operator", - run_id="456", - run_attempt="1", - workflow_ref=( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - workflow_commit="f" * 40, - ) - self.assertEqual(successor, durable_successor) - return failed, successor, record, failed_commit - - def test_environment_requires_custom_main_branch_policy(self) -> None: - def evidence(environment: object, policies: object) -> dict[str, object]: - class FixtureClient: - def json(_client, url: str, **kwargs: object) -> object: - self.assertEqual( - {"X-GitHub-Api-Version": "2026-03-10"}, - kwargs.get("headers"), - ) - if url.endswith("deployment-branch-policies?per_page=100"): - return policies - return environment - - return protected_environment_evidence(FixtureClient())[0] - - environment = github_environment() - policies = { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "main", "type": "branch"}], - } - self.assertEqual(environment_protection_evidence(), evidence(environment, policies)) - - disabled = copy.deepcopy(environment) - disabled["deployment_branch_policy"] = { - "custom_branch_policies": False, - "protected_branches": True, - } - with self.assertRaisesRegex(CandidateError, "enable custom branch policies"): - evidence(disabled, policies) - - wrong_policy = { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "release/*", "type": "branch"}], - } - with self.assertRaisesRegex(CandidateError, "allow only the main branch"): - evidence(environment, wrong_policy) - - extra_policy = { - "total_count": 2, - "branch_policies": [ - {"id": 23, "name": "main", "type": "branch"}, - {"id": 24, "name": "release/*", "type": "branch"}, - ], - } - with self.assertRaisesRegex(CandidateError, "allow only the main branch"): - evidence(environment, extra_policy) - - unsupported_reviewer = copy.deepcopy(environment) - unsupported_reviewer["protection_rules"][0]["reviewers"][0] = { - "type": "Team", - "reviewer": {"id": 29}, - } - with self.assertRaisesRegex(CandidateError, "unverifiable required reviewer"): - evidence(unsupported_reviewer, policies) - - def test_approval_history_rejects_absent_rejected_wrong_environment_wrong_run_and_malformed(self) -> None: - def evidence(run: object, history: object) -> dict[str, object]: - class FixtureClient: - def json(_client, url: str, **kwargs: object) -> object: - self.assertEqual( - {"X-GitHub-Api-Version": "2026-03-10"}, - kwargs.get("headers"), - ) - return history if url.endswith("/approvals") else run - - return protected_run_approval_evidence( - FixtureClient(), - actor="release-operator", - run_id=456, - run_attempt=1, - workflow_commit="f" * 40, - environment_protection=environment_protection_evidence(), - required_reviewers={(29, "release-reviewer")}, - require_success=True, - ) - - self.assertEqual(environment_approval_evidence(), evidence(workflow_run(), approval_history())) - - rejected = approval_history() - rejected[0]["state"] = "rejected" - wrong_environment = approval_history() - wrong_environment[0]["environments"][0]["name"] = "staging" - wrong_run = workflow_run() - wrong_run["id"] = 999 - wrong_attempt = workflow_run() - wrong_attempt["run_attempt"] = 2 - wrong_revision = workflow_run() - wrong_revision["head_sha"] = "0" * 40 - failed_run = workflow_run() - failed_run["conclusion"] = "failure" - malformed = approval_history() - malformed[0]["environments"] = "release-plan-supersession" - outside_policy = approval_history() - outside_policy[0]["user"]["id"] = 999 - - failures = ( - ("absent", workflow_run(), [], "exactly one approved review"), - ("rejected", workflow_run(), rejected, "exactly one approved review"), - ("wrong environment", workflow_run(), wrong_environment, "wrong protected environment"), - ("wrong run", wrong_run, approval_history(), "workflow run evidence does not match"), - ("wrong attempt", wrong_attempt, approval_history(), "workflow run evidence does not match"), - ("wrong revision", wrong_revision, approval_history(), "workflow run evidence does not match"), - ("failed run", failed_run, approval_history(), "workflow run evidence does not match"), - ("malformed", workflow_run(), malformed, "approval history is malformed"), - ( - "outside reviewer policy", - workflow_run(), - outside_policy, - "not authorized by the current reviewer policy", - ), - ) - for name, run, history, error in failures: - with self.subTest(name=name), self.assertRaisesRegex(CandidateError, error): - evidence(run, history) - - self.assertEqual( - ".github/workflows/release-plan-supersession.yml@main", - workflow_run()["path"], - ) - for path in ( - ".github/workflows/release-plan-supersession.yml@v2", - ".github/workflows/release-plan.yml@main", - ): - run = workflow_run() - run["path"] = path - with ( - self.subTest(path=path), - self.assertRaisesRegex(CandidateError, "workflow run evidence does not match"), - ): - evidence(run, approval_history()) - - def test_run_scoped_approval_history_cannot_authorize_a_rerun_attempt(self) -> None: - client = mock.Mock() - rerun = workflow_run() - rerun["run_attempt"] = 2 - client.json.side_effect = [rerun] - - with self.assertRaisesRegex(CandidateError, "cannot prove protected approval for a rerun"): - protected_run_approval_evidence( - client, - actor="release-operator", - run_id=456, - run_attempt=2, - workflow_commit="f" * 40, - environment_protection=environment_protection_evidence(), - required_reviewers={(29, "release-reviewer")}, - require_success=True, - ) - self.assertEqual(1, client.json.call_count) - - def test_prepare_proves_real_public_conflict_and_protected_run(self) -> None: - failed = release_plan() - failed["plan"] = "plan-a" - successor = successor_plan(failed) - successor["plan"] = "plan-b" - failed_commit = "a" * 40 - observed_commit = "e" * 40 - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - return planned_source_manifest(url, failed) - - def json(self, url: str, **_kwargs: object) -> object: - if url.endswith("deployment-branch-policies?per_page=100"): - return { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "main", "type": "branch"}], - } - if "/environments/" in url: - return github_environment() - if url.endswith("/approvals"): - return approval_history() - if "/actions/runs/" in url: - return workflow_run() - return { - "id": 123, - "tag_name": failed["components"]["waterline"]["version"], - "draft": False, - "html_url": "https://github.com/durable-workflow/waterline/releases/1", - } - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == "release-plan/plan-a": - return failed_commit - if repository == "durable-workflow/waterline" and tag == failed["components"]["waterline"]["version"]: - return observed_commit - return None - - distribution = { - "kind": "composer", - "source_reference": observed_commit, - "dist_reference": observed_commit, - } - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - mock.patch( - "scripts.release_plan.resolve_github_tag", - return_value={"commit": observed_commit}, - ), - mock.patch.dict( - "scripts.release_plan.VERIFIERS", - {"composer": mock.Mock(return_value=distribution)}, - ), - ): - record, durable_successor = prepare_supersession( - "release-plan/plan-a", - "waterline", - successor, - FixtureClient(), - actor="release-operator", - run_id="456", - run_attempt="1", - workflow_ref=( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - workflow_commit="f" * 40, - ) - - self.assertEqual(observed_commit, record["conflicts"][0]["observed_commit"]) - self.assertEqual(manifest_digest(successor), record["successor_plan"]["sha256"]) - self.assertEqual([19], record["authorization"]["environment_protection"]["required_reviewer_rule_ids"]) - self.assertEqual( - [{"id": 23, "name": "main"}], - record["authorization"]["environment_protection"]["custom_branch_policies"], - ) - self.assertEqual( - "release-reviewer", - record["authorization"]["environment_approval"]["user"]["login"], - ) - self.assertEqual( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=release-plan-supersession", - record["authorization"]["environment_protection"]["environment_url"], - ) - self.assertEqual(successor, durable_successor) - - def test_prepare_retains_public_tag_and_source_manifest_conflicts(self) -> None: - failed = release_plan() - failed["plan"] = "plan-a" - failed["components"]["sdk-rust"] = { - "version": "0.1.16", - "commit": "dde751dc45366beaf8a829ed42c7ab92d0aad775", - } - successor = successor_plan(failed, components=("waterline", "sdk-rust")) - successor["plan"] = "plan-b" - successor["components"]["sdk-rust"]["commit"] = "2e09d42d8380bd0a2c8145dfeabd9d6294a8e8e1" - failed_commit = "a" * 40 - observed_commit = "e" * 40 - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - if "/sdk-python/" in url: - return planned_source_manifest(url, failed) - if failed["components"]["sdk-rust"]["commit"] in url: - return cargo_manifest("0.1.15") - if successor["components"]["sdk-rust"]["commit"] in url: - return cargo_manifest("0.1.16") - raise AssertionError(f"unexpected source manifest request: {url}") - - def json(self, url: str, **_kwargs: object) -> object: - if url.endswith("deployment-branch-policies?per_page=100"): - return { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "main", "type": "branch"}], - } - if "/environments/" in url: - return github_environment() - if url.endswith("/approvals"): - return approval_history() - if "/actions/runs/" in url: - return workflow_run() - return { - "id": 123, - "tag_name": failed["components"]["waterline"]["version"], - "draft": False, - "html_url": "https://github.com/durable-workflow/waterline/releases/1", - } - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == "release-plan/plan-a": - return failed_commit - if repository == "durable-workflow/waterline" and tag == failed["components"]["waterline"]["version"]: - return observed_commit - return None - - distribution = { - "kind": "composer", - "source_reference": observed_commit, - "dist_reference": observed_commit, - } - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - mock.patch( - "scripts.release_plan.resolve_github_tag", - return_value={"commit": observed_commit}, - ), - mock.patch.dict( - "scripts.release_plan.VERIFIERS", - {"composer": mock.Mock(return_value=distribution)}, - ), - ): - record, durable_successor = prepare_supersession( - "release-plan/plan-a", - ["waterline", "sdk-rust"], - successor, - FixtureClient(), - actor="release-operator", - run_id="456", - run_attempt="1", - workflow_ref=( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - workflow_commit="f" * 40, - ) - - self.assertEqual( - ["waterline", "sdk-rust"], - [conflict["component"] for conflict in record["conflicts"]], - ) - self.assertEqual( - ["published-version-source-conflict", "source-manifest-version-conflict"], - [conflict["reason"] for conflict in record["conflicts"]], - ) - rust_conflict = record["conflicts"][1] - self.assertEqual("0.1.15", rust_conflict["source_manifest"]["declared_version"]) - self.assertEqual( - "0.1.16", - rust_conflict["successor_source_manifest"]["declared_version"], - ) - self.assertEqual(successor, durable_successor) - validate_supersession_record(record, failed, failed_commit, successor) - - unresolved = copy.deepcopy(successor) - unresolved["components"]["sdk-rust"] = copy.deepcopy(failed["components"]["sdk-rust"]) - with self.assertRaisesRegex(CandidateError, "leaves conflict unresolved for sdk-rust"): - validate_successor_transition(failed, unresolved, record["conflicts"]) - - changed_unaffected = copy.deepcopy(successor) - changed_unaffected["components"]["server"]["commit"] = "9" * 40 - with self.assertRaisesRegex(CandidateError, "unaffected component server"): - validate_successor_transition(failed, changed_unaffected, record["conflicts"]) - - mismatched_successor = copy.deepcopy(record) - mismatched_successor["conflicts"][1]["successor_source_manifest"] = source_manifest_record( - successor["components"]["sdk-rust"]["commit"], "0.1.15" - ) - with self.assertRaisesRegex(CandidateError, "does not match sdk-rust version allocation"): - validate_supersession_record( - mismatched_successor, - failed, - failed_commit, - successor, - ) - - terminal = ("release-plan-failure/plan-a", "b" * 40, record, successor) - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value=failed_commit), - mock.patch("scripts.release_plan.load_public_supersession", return_value=terminal), - ): - state = terminal_failure_state(failed, object()) - self.assertEqual(["waterline", "sdk-rust"], state["failed_components"]) - self.assertEqual(record["conflicts"], state["conflicts"]) - self.assertIn("source manifest declares 0.1.15", state["reason"]) - - def test_prepare_occupied_python_manifest_conflict_admits_exact_successor(self) -> None: - failed, successor, record, failed_commit = self.prepare_occupied_python_conflict() - conflict = record["conflicts"][0] - - self.assertEqual(OCCUPIED_SOURCE_MANIFEST_REASON, conflict["reason"]) - self.assertEqual("0.4.99", conflict["source_manifest"]["declared_version"]) - self.assertEqual("absent", conflict["github_release"]["status"]) - self.assertEqual("absent", conflict["distribution"]["status"]) - self.assertEqual("0.4.101", conflict["successor_source_manifest"]["declared_version"]) - for name in COMPONENTS: - if name != "sdk-python": - self.assertEqual(failed["components"][name], successor["components"][name]) - validate_supersession_record(record, failed, failed_commit, successor) - - class FixtureClient: - def json(self, _url: str) -> list[dict[str, str]]: - return [{"ref": "refs/tags/release-plan/plan-a"}] - - terminal = ("release-plan-failure/plan-a", "b" * 40, record, successor) - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=[failed_commit, None, None]), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - mock.patch("scripts.release_plan.load_public_supersession", return_value=terminal), - ): - evidence = require_prior_plans_completed(successor, FixtureClient()) - self.assertEqual("terminal-failure", evidence["release-plan/plan-a"]["outcome"]) - - def test_occupied_python_manifest_conflict_rejects_alternate_successors(self) -> None: - failed, successor, record, _failed_commit = self.prepare_occupied_python_conflict() - - skipped = copy.deepcopy(successor) - skipped["components"]["sdk-python"]["version"] = "0.4.102" - with self.assertRaisesRegex(CandidateError, "immediate next version"): - validate_successor_transition(failed, skipped, record["conflicts"]) - - unchanged_source = copy.deepcopy(successor) - unchanged_source["components"]["sdk-python"]["commit"] = failed["components"]["sdk-python"]["commit"] - with self.assertRaisesRegex(CandidateError, "replace sdk-python's incompatible tagged source commit"): - validate_successor_transition(failed, unchanged_source, record["conflicts"]) - - changed_unaffected = copy.deepcopy(successor) - changed_unaffected["components"]["server"]["commit"] = "9" * 40 - with self.assertRaisesRegex(CandidateError, "unaffected component server"): - validate_successor_transition(failed, changed_unaffected, record["conflicts"]) - - def test_occupied_python_manifest_conflict_rejects_mismatched_successor_manifest(self) -> None: - failed, successor, record, failed_commit = self.prepare_occupied_python_conflict() - record["conflicts"][0]["successor_source_manifest"] = python_source_manifest_record( - successor["components"]["sdk-python"]["commit"], "0.4.100" - ) - with self.assertRaisesRegex(CandidateError, "does not match sdk-python version allocation"): - validate_supersession_record(record, failed, failed_commit, successor) - - def test_occupied_python_manifest_conflict_remains_durable_after_source_tag_moves(self) -> None: - failed, successor, record, failed_commit = self.prepare_occupied_python_conflict() - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value="b" * 40), - mock.patch("scripts.release_plan.read_public_record", side_effect=[record, successor]), - mock.patch( - "scripts.release_plan.resolve_github_tag", - side_effect=AssertionError("historical records must not reload source tags"), - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - return_value=environment_protection_authority(), - ), - mock.patch( - "scripts.release_plan.protected_run_approval_evidence", - return_value=environment_approval_evidence(), - ), - ): - loaded = load_public_supersession(failed, failed_commit, object()) - self.assertEqual(record, loaded[2]) - self.assertEqual(successor, loaded[3]) - - def test_prepare_rejects_omitted_python_manifest_conflict(self) -> None: - failed = release_plan() - failed["plan"] = "plan-a" - failed["components"]["sdk-python"] = { - "version": "0.4.100", - "commit": "2018400368cf4251c58b24b3d53a99f0ca3512e3", - } - successor = successor_plan(failed) - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - if "/sdk-python/" in url: - return python_manifest("0.4.99") - return planned_source_manifest(url, failed) - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == "release-plan/plan-a": - return "a" * 40 - if repository == "durable-workflow/sdk-python" and tag == "0.4.100": - return failed["components"]["sdk-python"]["commit"] - return None - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - self.assertRaisesRegex(CandidateError, "omit independently proven.*sdk-python"), - ): - prepare_supersession( - "release-plan/plan-a", - ["waterline"], - successor, - FixtureClient(), - actor="release-operator", - run_id="456", - run_attempt="1", - workflow_ref=( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - workflow_commit="f" * 40, - ) - - def test_prepare_rejects_omitted_source_manifest_conflict(self) -> None: - failed = release_plan() - failed["plan"] = "plan-a" - failed["components"]["sdk-rust"] = { - "version": "0.1.16", - "commit": "dde751dc45366beaf8a829ed42c7ab92d0aad775", - } - successor = successor_plan(failed) - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - if "/sdk-python/" in url: - return planned_source_manifest(url, failed) - return cargo_manifest("0.1.15") - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == "release-plan/plan-a": - return "a" * 40 - return None - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - self.assertRaisesRegex(CandidateError, "omit independently proven.*sdk-rust"), - ): - prepare_supersession( - "release-plan/plan-a", - ["waterline"], - successor, - FixtureClient(), - actor="release-operator", - run_id="456", - run_attempt="1", - workflow_ref=( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - workflow_commit="f" * 40, - ) - - def test_prepare_rejects_omitted_public_tag_conflict(self) -> None: - failed = release_plan() - failed["plan"] = "plan-a" - failed["components"]["sdk-rust"] = { - "version": "0.1.16", - "commit": "dde751dc45366beaf8a829ed42c7ab92d0aad775", - } - successor = successor_plan(failed, component="sdk-rust") - successor["components"]["sdk-rust"]["commit"] = "2e09d42d8380bd0a2c8145dfeabd9d6294a8e8e1" - failed_commit = "a" * 40 - observed_waterline_commit = "e" * 40 - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - if "/sdk-python/" in url: - return planned_source_manifest(url, failed) - return cargo_manifest("0.1.15") - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == "release-plan/plan-a": - return failed_commit - if repository == "durable-workflow/waterline" and tag == failed["components"]["waterline"]["version"]: - return observed_waterline_commit - return None - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - self.assertRaisesRegex(CandidateError, "omit independently proven.*waterline"), - ): - prepare_supersession( - "release-plan/plan-a", - ["sdk-rust"], - successor, - FixtureClient(), - actor="release-operator", - run_id="456", - run_attempt="1", - workflow_ref=( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - workflow_commit="f" * 40, - ) - - def test_observer_classifies_terminal_conflict_and_exact_recovery(self) -> None: - failed = release_plan() - failed["plan"] = "plan-a" - successor = successor_plan(failed) - successor["plan"] = "plan-b" - failure = supersession_record(failed, successor) - terminal = ("release-plan-failure/plan-a", "b" * 40, failure, successor) - with ( - mock.patch("scripts.release_plan.resolve_tag", return_value="a" * 40), - mock.patch("scripts.release_plan.load_public_supersession", return_value=terminal), - ): - state = terminal_failure_state(failed, object()) - - self.assertEqual("terminal-failure", state["phase"]) - self.assertEqual("superseded", state["outcome"]) - self.assertEqual(["waterline"], state["failed_components"]) - self.assertIn("release-plan/plan-b", state["resume_action"]) - self.assertIn(manifest_digest(successor), state["resume_action"]) - - -class ReleasePlanRecordTest(unittest.TestCase): - def setUp(self) -> None: - self.temporary = tempfile.TemporaryDirectory() - root = Path(self.temporary.name) - self.repository = root / "work" - self.remote = root / "remote.git" - subprocess.run(["git", "init", "--bare", str(self.remote)], check=True, capture_output=True) - subprocess.run(["git", "init", str(self.repository)], check=True, capture_output=True) - self.plan_path = root / "release-plan.json" - self.preparation_path = root / "release-preparation.json" - self.authoritative_path = root / "authoritative-release-plan.json" - self.authoritative_authorization_path = root / "authoritative-beta-authorization.json" - self.authoritative_preparation_path = root / "authoritative-release-preparation.json" - self.authoritative_completion_path = root / "authoritative-release-candidate.json" - self.authoritative_verification_path = root / "authoritative-verification.json" - self.preflight_path = root / "release-plan-preflight.json" - self.evidence_directory = root / "current-release-plan-evidence" - self.failure_path = root / "release-plan-failure.json" - self.successor_path = root / "successor-release-plan.json" - self.authoritative_failure_path = root / "authoritative-release-plan-failure.json" - self.authoritative_successor_path = root / "authoritative-successor-release-plan.json" - - def tearDown(self) -> None: - self.temporary.cleanup() - - def write_plan(self, plan: dict[str, object]) -> None: - self.plan_path.write_bytes(canonical_json(plan)) - self.preparation_path.write_bytes(canonical_json(release_preparation(plan))) - - def write_completion_authority(self, plan: dict[str, object]) -> None: - preparation = release_preparation(plan) - completion = completion_manifest(plan, "a" * 40, preparation) - verification = { - "schema": "durable-workflow.release-candidate-verification/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "release_plan_sha256": manifest_digest(plan), - "release_preparation_sha256": manifest_digest(preparation), - "public_verification": candidate_verification(candidate_manifest(plan)), - } - self.plan_path.write_bytes(canonical_json(plan)) - self.authoritative_path.write_bytes(canonical_json(plan)) - self.authoritative_preparation_path.write_bytes(canonical_json(preparation)) - self.authoritative_completion_path.write_bytes(canonical_json(completion)) - self.authoritative_verification_path.write_bytes(canonical_json(verification)) - - def test_first_record_and_identical_recovery_keep_one_commit(self) -> None: - plan = release_plan() - self.write_plan(plan) - first_preparation = release_preparation(plan) - created = record_plan( - self.repository, - self.plan_path, - self.preparation_path, - remote=str(self.remote), - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - ) - self.preparation_path.write_bytes(canonical_json(release_preparation(plan, "2026-07-20"))) - repeated = record_plan( - self.repository, - self.plan_path, - self.preparation_path, - remote=str(self.remote), - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - ) - self.assertEqual("created", created["status"]) - self.assertEqual("existing", repeated["status"]) - self.assertEqual(created["commit"], repeated["commit"]) - files = subprocess.run( - ["git", "--git-dir", str(self.remote), "ls-tree", "-r", "--name-only", created["commit"]], - check=True, - text=True, - capture_output=True, - ).stdout.splitlines() - self.assertEqual(["release-plan.json", "release-preparation.json"], files) - self.assertEqual(canonical_json(plan), self.authoritative_path.read_bytes()) - self.assertEqual( - canonical_json(first_preparation), - self.authoritative_preparation_path.read_bytes(), - ) - - def test_completed_plan_retry_reads_historical_semver_pypi_evidence(self) -> None: - plan = release_plan("beta") - plan["components"] = { - name: {"version": "2.0.0-beta.21", "commit": identity["commit"]} - for name, identity in plan["components"].items() - } - self.write_plan(plan) - recorded = record_plan( - self.repository, - self.plan_path, - self.preparation_path, - remote=str(self.remote), - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - ) - candidate = candidate_manifest(plan) - public_verification = candidate_verification(candidate) - self.assertEqual( - "https://pypi.org/pypi/durable-workflow/2.0.0-beta.21/json", - public_verification["components"]["sdk-python"]["distribution"]["registry"], - ) - completion = completion_manifest( - plan, - recorded["commit"], - release_preparation(plan), - ) - completion_verification = { - "schema": "durable-workflow.release-candidate-verification/v1", - "candidate": plan["plan"], - "channel": plan["channel"], - "release_plan_sha256": manifest_digest(plan), - "release_preparation_sha256": manifest_digest(release_preparation(plan)), - "public_verification": public_verification, - } - - blobs = {} - for filename, value in ( - ("release-candidate.json", canonical_json(completion)), - ("verification.json", canonical_json(completion_verification)), - ): - blobs[filename] = ( - subprocess.run( - ["git", "hash-object", "-w", "--stdin"], - cwd=self.repository, - input=value, - check=True, - capture_output=True, - ) - .stdout.decode() - .strip() - ) - tree_input = "".join(f"100644 blob {blob}\t{filename}\n" for filename, blob in sorted(blobs.items())) - tree = subprocess.run( - ["git", "mktree"], - cwd=self.repository, - input=tree_input, - text=True, - check=True, - capture_output=True, - ).stdout.strip() - identity = { - "GIT_AUTHOR_NAME": "Durable Workflow Test", - "GIT_AUTHOR_EMAIL": "support@durable-workflow.com", - "GIT_COMMITTER_NAME": "Durable Workflow Test", - "GIT_COMMITTER_EMAIL": "support@durable-workflow.com", - } - completion_commit = subprocess.run( - ["git", "commit-tree", tree], - cwd=self.repository, - input="Record historical completion\n", - text=True, - check=True, - capture_output=True, - env={**os.environ, **identity}, - ).stdout.strip() - subprocess.run( - [ - "git", - "push", - str(self.remote), - f"{completion_commit}:refs/tags/release-candidate/beta/{plan['plan']}", - ], - cwd=self.repository, - check=True, - capture_output=True, - ) - - result = read_current_completion_authority( - self.repository, - self.plan_path, - remote=str(self.remote), - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - authoritative_completion=Path(self.temporary.name) / "authoritative-completion.json", - authoritative_verification=Path(self.temporary.name) / "authoritative-verification.json", - ) - - self.assertEqual("existing", result["status"]) - self.assertEqual(completion_commit, result["completion_commit"]) - - def test_existing_completion_evidence_is_staged_atomically_and_idempotently(self) -> None: - plan = release_plan("beta") - self.write_completion_authority(plan) - - first = stage_current_plan_evidence( - self.plan_path, - self.evidence_directory, - completion_status="existing", - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - authoritative_completion=self.authoritative_completion_path, - authoritative_verification=self.authoritative_verification_path, - preflight=self.preflight_path, - ) - repeated = stage_current_plan_evidence( - self.plan_path, - self.evidence_directory, - completion_status="existing", - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - authoritative_completion=self.authoritative_completion_path, - authoritative_verification=self.authoritative_verification_path, - preflight=self.preflight_path, - ) - - self.assertEqual("created", first["status"]) - self.assertEqual("existing", repeated["status"]) - self.assertEqual( - { - "authoritative-release-candidate.json", - "authoritative-release-plan.json", - "authoritative-release-preparation.json", - "authoritative-verification.json", - "evidence-manifest.json", - }, - {path.name for path in self.evidence_directory.iterdir()}, - ) - manifest = json.loads((self.evidence_directory / "evidence-manifest.json").read_bytes()) - self.assertEqual("existing", manifest["completion_status"]) - self.assertEqual("not-run-existing-authority", manifest["preflight"]) - - def test_new_completion_evidence_requires_preflight_evidence(self) -> None: - plan = release_plan("beta") - self.write_completion_authority(plan) - - with self.assertRaisesRegex(CandidateError, "requires release-plan preflight"): - stage_current_plan_evidence( - self.plan_path, - self.evidence_directory, - completion_status="new", - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - authoritative_completion=self.authoritative_completion_path, - authoritative_verification=self.authoritative_verification_path, - preflight=self.preflight_path, - ) - - self.assertFalse(self.evidence_directory.exists()) - self.preflight_path.write_bytes( - canonical_json( - { - "schema": "durable-workflow.release-plan-preflight/v1", - "plan": plan["plan"], - "channel": plan["channel"], - "outcome": "verified", - "release_preparation_sha256": manifest_digest(release_preparation(plan)), - } - ) - ) - - result = stage_current_plan_evidence( - self.plan_path, - self.evidence_directory, - completion_status="new", - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - authoritative_completion=self.authoritative_completion_path, - authoritative_verification=self.authoritative_verification_path, - preflight=self.preflight_path, - ) - - self.assertEqual("created", result["status"]) - self.assertEqual("included", result["preflight"]) - self.assertTrue((self.evidence_directory / "release-plan-preflight.json").is_file()) - - def test_current_rc_authority_uses_the_exact_candidate_foundation(self) -> None: - current = load_plan(REPOSITORY_ROOT / "release-plans" / "current.json") - - self.assertIsNone(current["beta_authorization"]) - self.assertEqual( - { - "commit": "40d39e180e914406dcca2d64892180a0483ee2a2", - "tag": "beta-candidate/rc-current-2-0-20260801", - }, - current["foundation"], - ) - - def test_existing_plan_rejects_tuple_mutation(self) -> None: - plan = release_plan() - self.write_plan(plan) - record_plan( - self.repository, - self.plan_path, - self.preparation_path, - remote=str(self.remote), - authoritative_plan=self.authoritative_path, - authoritative_preparation=self.authoritative_preparation_path, - ) - changed = copy.deepcopy(plan) - changed["components"]["server"]["commit"] = "e" * 40 - self.write_plan(changed) - with self.assertRaisesRegex(CandidateError, "immutable"): - check_plan_compatibility(self.repository, self.plan_path, remote=str(self.remote)) - - def test_terminal_record_and_successor_are_idempotently_immutable(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - self.failure_path.write_bytes(canonical_json(record)) - self.successor_path.write_bytes(canonical_json(successor)) - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == record["failed_plan"]["tag"]: - return "a" * 40 - if repository == "durable-workflow/waterline" and tag == failed["components"]["waterline"]["version"]: - return "e" * 40 - return None - - client = mock.Mock() - client.json.return_value = { - "draft": False, - "html_url": record["conflicts"][0]["github_release"]["url"], - "id": record["conflicts"][0]["github_release"]["id"], - "tag_name": failed["components"]["waterline"]["version"], - } - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - mock.patch.dict( - "scripts.release_plan.VERIFIERS", - {"composer": mock.Mock(return_value=record["conflicts"][0]["distribution"])}, - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - return_value=environment_protection_authority(), - ) as protection, - mock.patch( - "scripts.release_plan.protected_run_approval_evidence", - return_value=environment_approval_evidence(), - ) as approval, - ): - created = record_supersession( - self.repository, - self.failure_path, - self.successor_path, - remote=str(self.remote), - authoritative_record=self.authoritative_failure_path, - authoritative_successor=self.authoritative_successor_path, - client=client, - ) - protection.side_effect = CandidateError("environment policy changed after publication") - approval.side_effect = CandidateError("approval history unavailable after publication") - repeated = record_supersession( - self.repository, - self.failure_path, - self.successor_path, - remote=str(self.remote), - authoritative_record=self.authoritative_failure_path, - authoritative_successor=self.authoritative_successor_path, - client=client, - ) - - self.assertEqual("created", created["status"]) - self.assertEqual("existing", repeated["status"]) - self.assertEqual(created["commit"], repeated["commit"]) - files = subprocess.run( - ["git", "--git-dir", str(self.remote), "ls-tree", "-r", "--name-only", created["commit"]], - check=True, - text=True, - capture_output=True, - ).stdout.splitlines() - self.assertEqual(["release-plan-failure.json", "successor-release-plan.json"], files) - - def test_terminal_record_rechecks_mutable_evidence_before_publication(self) -> None: - failed = release_plan() - successor = successor_plan(failed) - record = supersession_record(failed, successor) - self.failure_path.write_bytes(canonical_json(record)) - self.successor_path.write_bytes(canonical_json(successor)) - failed_version = failed["components"]["waterline"]["version"] - successor_version = successor["components"]["waterline"]["version"] - - errors = { - "conflict": "terminal conflict source tag .* moved", - "release": "GitHub Release evidence .* no longer matches GitHub", - "release-absent": "GitHub Release evidence .* no longer matches GitHub", - "distribution": "distribution evidence .* no longer matches its registry", - "successor": "successor version .* already points to", - "policy": "protected environment policy no longer matches", - "approval": "approval history unavailable", - } - for drift, error in errors.items(): - with self.subTest(drift=drift): - - def resolve( - _client: object, - repository: str, - tag: str, - drift: str = drift, - ) -> str | None: - if repository == "durable-workflow/.github" and tag == record["failed_plan"]["tag"]: - return "a" * 40 - if repository == "durable-workflow/waterline" and tag == failed_version: - return "d" * 40 if drift == "conflict" else "e" * 40 - if repository == "durable-workflow/waterline" and tag == successor_version: - return "d" * 40 if drift == "successor" else None - return None - - protection = environment_protection_evidence() - if drift == "policy": - protection["custom_branch_policies"] = [{"id": 24, "name": "main"}] - - def approval( - _client: object, - drift: str = drift, - **_kwargs: object, - ) -> dict[str, object]: - if drift == "approval": - raise CandidateError("approval history unavailable") - return environment_approval_evidence() - - client = mock.Mock() - if drift == "release-absent": - client.json.side_effect = CandidateError("GitHub Release was removed") - else: - client.json.return_value = { - "draft": False, - "html_url": record["conflicts"][0]["github_release"]["url"], - "id": (124 if drift == "release" else record["conflicts"][0]["github_release"]["id"]), - "tag_name": failed_version, - } - live_distribution = copy.deepcopy(record["conflicts"][0]["distribution"]) - if drift == "distribution": - live_distribution["dist"] = { - "sha256": "f" * 64, - "url": "https://example.com/repacked.zip", - } - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - mock.patch.dict( - "scripts.release_plan.VERIFIERS", - {"composer": mock.Mock(return_value=live_distribution)}, - ), - mock.patch( - "scripts.release_plan.protected_environment_evidence", - return_value=(protection, {(29, "release-reviewer")}), - ), - mock.patch( - "scripts.release_plan.protected_run_approval_evidence", - side_effect=approval, - ), - self.assertRaisesRegex(CandidateError, error), - ): - record_supersession( - self.repository, - self.failure_path, - self.successor_path, - remote=str(self.remote), - authoritative_record=self.authoritative_failure_path, - authoritative_successor=self.authoritative_successor_path, - client=client, - ) - - published = subprocess.run( - [ - "git", - "--git-dir", - str(self.remote), - "for-each-ref", - "--format=%(refname)", - "refs/tags/release-plan-failure/recovery-proof-1", - ], - check=True, - capture_output=True, - ) - self.assertEqual(b"", published.stdout) - - def test_terminal_record_rejects_manifest_tag_appearing_after_prepare(self) -> None: - failed = release_plan() - failed["components"]["sdk-rust"] = { - "version": "0.1.16", - "commit": "dde751dc45366beaf8a829ed42c7ab92d0aad775", - } - successor = successor_plan(failed, component="sdk-rust") - failed_identity = failed["components"]["sdk-rust"] - successor_identity = successor["components"]["sdk-rust"] - source_tag_commit = None - - class FixtureClient: - def bytes(self, url: str, **_kwargs: object) -> bytes: - if "/sdk-python/" in url: - return planned_source_manifest(url, failed) - if failed_identity["commit"] in url: - return cargo_manifest("0.1.15") - if successor_identity["commit"] in url: - return cargo_manifest(successor_identity["version"]) - raise AssertionError(f"unexpected source manifest request: {url}") - - def json(self, url: str, **_kwargs: object) -> object: - if url.endswith("deployment-branch-policies?per_page=100"): - return { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "main", "type": "branch"}], - } - if "/environments/" in url: - return github_environment() - if url.endswith("/approvals"): - return approval_history() - if "/actions/runs/" in url: - return workflow_run() - raise AssertionError(f"unexpected public evidence request: {url}") - - client = FixtureClient() - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == f"{PLAN_TAG_PREFIX}{failed['plan']}": - return "a" * 40 - if repository == "durable-workflow/sdk-rust" and tag == failed_identity["version"]: - return source_tag_commit - return None - - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - ): - record, durable_successor = prepare_supersession( - f"{PLAN_TAG_PREFIX}{failed['plan']}", - ["sdk-rust"], - successor, - client, - actor="release-operator", - run_id="456", - run_attempt="1", - workflow_ref=( - "durable-workflow/.github/.github/workflows/release-plan-supersession.yml@refs/heads/main" - ), - workflow_commit="f" * 40, - ) - self.assertEqual(SOURCE_MANIFEST_REASON, record["conflicts"][0]["reason"]) - self.failure_path.write_bytes(canonical_json(record)) - self.successor_path.write_bytes(canonical_json(durable_successor)) - - for appeared_commit in (failed_identity["commit"], successor_identity["commit"]): - with self.subTest(appeared_commit=appeared_commit): - source_tag_commit = appeared_commit - with self.assertRaisesRegex( - CandidateError, - "terminal conflict source tag .* appeared", - ): - record_supersession( - self.repository, - self.failure_path, - self.successor_path, - remote=str(self.remote), - authoritative_record=self.authoritative_failure_path, - authoritative_successor=self.authoritative_successor_path, - client=client, - ) - - published = subprocess.run( - [ - "git", - "--git-dir", - str(self.remote), - "for-each-ref", - "--format=%(refname)", - "refs/tags/release-plan-failure/recovery-proof-1", - ], - check=True, - capture_output=True, - ) - self.assertEqual(b"", published.stdout) - - def test_terminal_record_revalidates_occupied_manifest_publication_evidence(self) -> None: - failed = release_plan() - failed["components"]["sdk-python"] = { - "version": "0.4.100", - "commit": "2018400368cf4251c58b24b3d53a99f0ca3512e3", - } - successor = copy.deepcopy(failed) - successor["plan"] = "recovery-proof-2" - successor["components"]["sdk-python"] = { - "version": "0.4.101", - "commit": "d" * 40, - } - record = supersession_record(failed, successor, component="sdk-python") - failed_identity = failed["components"]["sdk-python"] - successor_identity = successor["components"]["sdk-python"] - release_api = "https://api.github.com/repos/durable-workflow/sdk-python/releases/tags/0.4.100" - distribution_api = "https://pypi.org/pypi/durable-workflow/0.4.100/json" - record["conflicts"][0] = { - "component": "sdk-python", - "version": failed_identity["version"], - "planned_commit": failed_identity["commit"], - "reason": OCCUPIED_SOURCE_MANIFEST_REASON, - "source_manifest": python_source_manifest_record( - failed_identity["commit"], - "0.4.99", - ), - "source_tag": { - "commit": failed_identity["commit"], - "repository": "durable-workflow/sdk-python", - "tag": failed_identity["version"], - "tag_object": failed_identity["commit"], - "url": "https://github.com/durable-workflow/sdk-python/tree/0.4.100", - }, - "github_release": { - "api_url": release_api, - "status": "absent", - "url": "https://github.com/durable-workflow/sdk-python/releases/tag/0.4.100", - }, - "distribution": { - "api_url": distribution_api, - "kind": "pypi", - "status": "absent", - "url": "https://pypi.org/project/durable-workflow/0.4.100/", - }, - "successor_source_manifest": python_source_manifest_record( - successor_identity["commit"], - successor_identity["version"], - ), - } - self.failure_path.write_bytes(canonical_json(record)) - self.successor_path.write_bytes(canonical_json(successor)) - - def resolve(_client: object, repository: str, tag: str) -> str | None: - if repository == "durable-workflow/.github" and tag == record["failed_plan"]["tag"]: - return "a" * 40 - return None - - def source_manifest(url: str, **_kwargs: object) -> bytes: - if failed_identity["commit"] in url: - return python_manifest("0.4.99") - if successor_identity["commit"] in url: - return python_manifest(successor_identity["version"]) - raise AssertionError(f"unexpected source manifest request: {url}") - - for appeared_surface, error in ( - ("source-tag", "source tag .* moved"), - ("github-release", "already has a GitHub Release"), - ("distribution", "already has a public distribution"), - ): - with self.subTest(appeared_surface=appeared_surface): - client = mock.Mock() - client.bytes.side_effect = source_manifest - - def json(url: str, surface: str = appeared_surface) -> object: - if url == release_api and surface == "github-release": - return {"id": 123} - if url == distribution_api and surface == "distribution": - return {"info": {"version": failed_identity["version"]}} - raise CandidateError(f"public request failed (404) for {url}") - - client.json.side_effect = json - live_source_tag = copy.deepcopy(record["conflicts"][0]["source_tag"]) - if appeared_surface == "source-tag": - live_source_tag["commit"] = "e" * 40 - with ( - mock.patch("scripts.release_plan.resolve_tag", side_effect=resolve), - mock.patch("scripts.release_plan.read_public_record", return_value=failed), - mock.patch( - "scripts.release_plan.resolve_github_tag", - return_value=live_source_tag, - ), - self.assertRaisesRegex(CandidateError, error), - ): - record_supersession( - self.repository, - self.failure_path, - self.successor_path, - remote=str(self.remote), - authoritative_record=self.authoritative_failure_path, - authoritative_successor=self.authoritative_successor_path, - client=client, - ) - - published = subprocess.run( - [ - "git", - "--git-dir", - str(self.remote), - "for-each-ref", - "--format=%(refname)", - "refs/tags/release-plan-failure/recovery-proof-1", - ], - check=True, - capture_output=True, - ) - self.assertEqual(b"", published.stdout) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_release_recovery_consumer_conformance.py b/tests/test_release_recovery_consumer_conformance.py deleted file mode 100644 index fb261cd..0000000 --- a/tests/test_release_recovery_consumer_conformance.py +++ /dev/null @@ -1,360 +0,0 @@ -from __future__ import annotations - -import copy -import json -import tempfile -import unittest -from pathlib import Path -from types import ModuleType -from unittest import mock - -from jsonschema import Draft202012Validator - -from scripts import release_recovery_consumer_conformance as conformance - -ROOT = Path(__file__).resolve().parents[1] -CONTRACT_PATH = ROOT / "release-recovery" / "consumer-conformance" / "contract.json" -CONTRACT_SCHEMA_PATH = ROOT / "release-recovery" / "consumer-conformance" / "contract-schema.json" -SUITE_PATH = ROOT / "scripts" / "release_recovery_consumer_conformance.py" - - -class ReleaseRecoveryConsumerConformanceTest(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.contract_raw = CONTRACT_PATH.read_bytes() - cls.contract = json.loads(cls.contract_raw) - cls.contract_schema = json.loads(CONTRACT_SCHEMA_PATH.read_bytes()) - - def test_contract_binds_the_exact_suite_and_required_target_set(self) -> None: - digest = conformance.validate_contract( - self.contract, - self.contract_raw, - SUITE_PATH, - ) - - self.assertEqual(conformance.sha256_bytes(self.contract_raw), digest) - self.assertEqual( - list(conformance.REQUIRED_CASES), - [case["id"] for case in self.contract["cases"]], - ) - self.assertEqual(list(conformance.CONSUMERS), self.contract["consumers"]) - - def changed_contract(self, previous_version: str, current_version: str) -> tuple[dict, dict]: - previous = copy.deepcopy(self.contract) - previous["version"] = previous_version - current = copy.deepcopy(previous) - current["cases"][0]["requirement"] += " Changed." - current["version"] = current_version - return previous, current - - def test_changed_contract_requires_strictly_greater_semver_precedence(self) -> None: - accepted = ( - ("1.2.0", "1.2.1"), - ("1.2.0", "1.3.0"), - ("1.2.0", "2.0.0"), - ("1.3.0-rc.1", "1.3.0-rc.2"), - ("1.3.0-rc.2", "1.3.0"), - ) - for previous_version, current_version in accepted: - with self.subTest(previous=previous_version, current=current_version): - previous, current = self.changed_contract(previous_version, current_version) - conformance.require_versioned_contract_change(previous, current) - - rejected = ( - ("1.2.0", "1.2.0"), - ("2.0.0", "1.9.9"), - ("1.3.0", "1.3.0+rebuilt"), - ("1.3.0+first", "1.3.0+second"), - ("1.3.0-rc.2", "1.3.0-rc.1"), - ) - for previous_version, current_version in rejected: - with ( - self.subTest(previous=previous_version, current=current_version), - self.assertRaisesRegex(conformance.ConformanceError, "strictly advancing"), - ): - previous, current = self.changed_contract(previous_version, current_version) - conformance.require_versioned_contract_change(previous, current) - - def test_suite_digest_change_requires_strictly_advancing_version(self) -> None: - current = copy.deepcopy(self.contract) - current["version"] = "1.4.2" - previous = copy.deepcopy(current) - previous["suite"]["sha256"] = "0" * 64 - - with self.assertRaisesRegex(conformance.ConformanceError, "strictly advancing"): - conformance.require_versioned_contract_change(previous, current) - - current["version"] = "1.5.0" - conformance.require_versioned_contract_change(previous, current) - - def test_contract_version_uses_exact_semver(self) -> None: - validator = Draft202012Validator(self.contract_schema) - valid = ("0.0.0", "1.0.0-rc.1", "1.0.0-0A.0", "1.0.0+build.01") - for version in valid: - with self.subTest(version=version): - contract = copy.deepcopy(self.contract) - contract["version"] = version - validator.validate(contract) - conformance.validate_contract( - contract, - conformance.canonical_json(contract), - SUITE_PATH, - ) - - malformed = ("1.0.0-rc.01", "1.0.0-01", "01.0.0", "1.0.0-alpha..1") - for version in malformed: - with ( - self.subTest(version=version), - self.assertRaisesRegex(conformance.ConformanceError, "exact SemVer"), - ): - contract = copy.deepcopy(self.contract) - contract["version"] = version - self.assertTrue(list(validator.iter_errors(contract))) - conformance.validate_contract( - contract, - conformance.canonical_json(contract), - SUITE_PATH, - ) - - def adapter_fixture( - self, - root: Path, - ) -> tuple[dict, dict, str, Path, Path]: - suite_path = root / "scripts" / "ci" / "release_recovery_consumer_conformance.py" - contract_path = root / "scripts" / "ci" / "release-recovery-consumer-contract.json" - consumer_path = root / "scripts" / "ci" / "component-release-recovery.py" - verifier_path = root / "scripts" / "ci" / "test-component-release-recovery.py" - suite_path.parent.mkdir(parents=True) - suite_path.write_bytes(SUITE_PATH.read_bytes()) - contract = copy.deepcopy(self.contract) - contract_raw = conformance.canonical_json(contract) - contract_path.write_bytes(contract_raw) - consumer_path.write_text("# consumer fixture\n") - verifier_path.write_text("# verifier fixture\n") - adapter = { - "component": "workflow", - "consumer": consumer_path.relative_to(root).as_posix(), - "contract": { - "path": contract_path.relative_to(root).as_posix(), - "sha256": conformance.sha256_bytes(contract_raw), - "version": contract["version"], - }, - "distribution_verification": {"command": ["{python}", verifier_path.relative_to(root).as_posix()]}, - "repository": "durable-workflow/workflow", - "schema": conformance.ADAPTER_SCHEMA, - "suite": { - "path": suite_path.relative_to(root).as_posix(), - "sha256": contract["suite"]["sha256"], - }, - "target_branch": "v2", - } - return adapter, contract, conformance.sha256_bytes(contract_raw), suite_path, contract_path - - def validate_adapter_fixture( - self, - adapter: dict, - contract: dict, - contract_sha256: str, - root: Path, - suite_path: Path, - contract_path: Path, - ) -> tuple[Path, list[str]]: - return conformance.validate_adapter( - adapter, - contract, - contract_sha256, - root, - suite_path, - contract_path, - ) - - def test_adapter_declared_and_invoked_contract_identity_matches(self) -> None: - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - adapter, contract, digest, suite_path, contract_path = self.adapter_fixture(root) - - consumer, command = self.validate_adapter_fixture( - adapter, - contract, - digest, - root, - suite_path, - contract_path, - ) - - self.assertEqual("component-release-recovery.py", consumer.name) - self.assertEqual(["{python}", "scripts/ci/test-component-release-recovery.py"], command) - - def test_alternate_invoked_contract_fails_closed(self) -> None: - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - adapter, contract, digest, suite_path, contract_path = self.adapter_fixture(root) - alternate_path = contract_path.with_name("alternate-contract.json") - alternate_path.write_bytes(contract_path.read_bytes()) - - with self.assertRaisesRegex( - conformance.ConformanceError, - "invoked contract is not the adapter's declared contract", - ): - self.validate_adapter_fixture( - adapter, - contract, - digest, - root, - suite_path, - alternate_path, - ) - - def test_stale_declared_contract_fails_closed(self) -> None: - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - adapter, contract, _, suite_path, contract_path = self.adapter_fixture(root) - stale_contract = copy.deepcopy(contract) - stale_contract["version"] = "1.4.0" - stale_raw = conformance.canonical_json(stale_contract) - contract_path.write_bytes(stale_raw) - - with self.assertRaisesRegex( - conformance.ConformanceError, - "declared contract does not match its version and digest pins", - ): - self.validate_adapter_fixture( - adapter, - stale_contract, - conformance.sha256_bytes(stale_raw), - root, - suite_path, - contract_path, - ) - - def test_mismatched_declared_contract_bytes_fail_closed(self) -> None: - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - adapter, contract, digest, suite_path, contract_path = self.adapter_fixture(root) - mismatched = copy.deepcopy(contract) - mismatched["cases"][0]["requirement"] += " (mismatched declared bytes)" - contract_path.write_bytes(conformance.canonical_json(mismatched)) - - with self.assertRaisesRegex( - conformance.ConformanceError, - "declared contract does not match its version and digest pins", - ): - self.validate_adapter_fixture( - adapter, - contract, - digest, - root, - suite_path, - contract_path, - ) - - def test_previous_contract_distinguishes_first_adoption_from_unavailable_commit(self) -> None: - contract_path = ROOT / "scripts" / "ci" / "release-recovery-consumer-contract.json" - commit = "a" * 40 - commit_exists = mock.Mock(returncode=0, stdout=b"") - contract_absent = mock.Mock(returncode=0, stdout=b"") - with mock.patch.object( - conformance.subprocess, - "run", - side_effect=(commit_exists, contract_absent), - ): - self.assertIsNone(conformance.previous_contract(ROOT, contract_path, commit)) - - commit_unavailable = mock.Mock(returncode=128, stdout=b"") - with ( - mock.patch.object(conformance.subprocess, "run", return_value=commit_unavailable), - self.assertRaisesRegex(conformance.ConformanceError, "commit is unavailable"), - ): - conformance.previous_contract(ROOT, contract_path, commit) - - def test_blanket_two_successor_rejection_fails_the_shared_continuity_case(self) -> None: - mutant = ModuleType("blanket_continuity_rejection") - - class RecoveryError(RuntimeError): - pass - - mutant.RecoveryError = RecoveryError - mutant.CONTROL_REPOSITORY = "durable-workflow/.github" - mutant.CONTINUITY_RESOLUTION_SCHEMA = "durable-workflow.release-plan-continuity-resolution/v2" - mutant.CONTINUITY_RESOLUTION_TAG_PREFIX = "release-plan-continuity-resolution/" - mutant.CONTINUITY_RESOLUTION_QUALIFICATION_WORKFLOW = ".github/workflows/beta-candidate.yml" - mutant.CONTINUITY_RESOLUTION_QUALIFICATION_EVENT = "push" - mutant.CONTINUITY_RESOLUTION_QUALIFICATION_BRANCH = "main" - mutant.manifest_digest = lambda value: conformance.sha256_bytes(conformance.canonical_json(value)) - mutant.list_continuity_resolution_tags = lambda *_args: [] - mutant.resolve_tag = lambda *_args: None - mutant.read_record = lambda *_args: {} - - def reject_every_fork(*_args): - raise RecoveryError("blanket two-successor rejection") - - mutant.resolve_continuity_successor_fork = reject_every_fork - - with self.assertRaisesRegex(RecoveryError, "blanket two-successor rejection"): - conformance.case_continuity_ambiguity_rejection(mutant) - - def test_continuity_case_exercises_exact_transport_authorities(self) -> None: - consumer = conformance.load_consumer(ROOT / "scripts" / "component_release_recovery.py") - - conformance.case_continuity_ambiguity_rejection(consumer) - - def test_current_consumer_accepts_only_exact_authoritative_rc_foundations(self) -> None: - consumer = conformance.load_consumer(ROOT / "scripts" / "component_release_recovery.py") - - conformance.case_authoritative_rc_foundation(consumer) - - def test_current_consumer_records_scheduled_empty_recovery_as_no_op(self) -> None: - consumer = conformance.load_consumer(ROOT / "scripts" / "component_release_recovery.py") - - conformance.case_scheduled_empty_no_op(consumer) - - def test_public_audit_requires_every_target_to_pin_identical_bytes(self) -> None: - suite_raw = SUITE_PATH.read_bytes() - consumers = { - consumer["repository"].removeprefix("durable-workflow/"): consumer - for consumer in self.contract["consumers"] - } - - def public_file(url: str) -> bytes: - relative = url.removeprefix("https://raw.githubusercontent.com/durable-workflow/") - repository, branch, path = relative.split("/", 2) - consumer = consumers[repository] - self.assertEqual(consumer["target_branch"], branch) - if path.endswith("release-recovery-consumer-contract.json"): - return self.contract_raw - if path.endswith("release_recovery_consumer_conformance.py"): - return suite_raw - if path.endswith("release-recovery-consumer-adapter.json"): - return conformance.canonical_json( - { - "component": consumer["component"], - "contract": { - "path": ("scripts/ci/release-recovery-consumer-contract.json"), - "sha256": conformance.sha256_bytes(self.contract_raw), - "version": self.contract["version"], - }, - "repository": consumer["repository"], - "target_branch": consumer["target_branch"], - } - ) - self.fail(f"unexpected audit path: {path}") - - with mock.patch.object( - conformance, - "fetch_public", - side_effect=public_file, - ): - results = conformance.audit_public_targets( - self.contract, - self.contract_raw, - ) - - self.assertEqual( - [consumer["component"] for consumer in self.contract["consumers"]], - [result["component"] for result in results], - ) - self.assertTrue(all(result["status"] == "pass" for result in results)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_release_recovery_target_qualification.py b/tests/test_release_recovery_target_qualification.py deleted file mode 100644 index a0de6da..0000000 --- a/tests/test_release_recovery_target_qualification.py +++ /dev/null @@ -1,236 +0,0 @@ -from __future__ import annotations - -import copy -import json -import subprocess -import sys -import unittest -from pathlib import Path -from unittest import mock - -import yaml - -from scripts import release_recovery_consumer_conformance as conformance -from scripts import release_recovery_target_qualification as qualification - -ROOT = Path(__file__).resolve().parents[1] -CONTRACT_PATH = ROOT / "release-recovery" / "consumer-conformance" / "contract.json" -SUITE_PATH = ROOT / "scripts" / "release_recovery_consumer_conformance.py" -SOURCE_QUALIFICATION = ROOT / ".github" / "workflows" / "source-qualification.yml" - - -class ReleaseRecoveryTargetQualificationTest(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.contract_raw = CONTRACT_PATH.read_bytes() - cls.contract = json.loads(cls.contract_raw) - cls.suite_raw = SUITE_PATH.read_bytes() - - def adapter(self, consumer: dict[str, str], contract: dict, contract_raw: bytes) -> bytes: - return conformance.canonical_json( - { - "component": consumer["component"], - "consumer": "scripts/ci/component-release-recovery.py", - "contract": { - "path": qualification.CONTRACT_PATH, - "sha256": conformance.sha256_bytes(contract_raw), - "version": contract["version"], - }, - "distribution_verification": { - "command": ["{python}", "scripts/ci/verify-release-recovery-distribution.py"] - }, - "repository": consumer["repository"], - "schema": conformance.ADAPTER_SCHEMA, - "suite": { - "path": qualification.SUITE_PATH, - "sha256": contract["suite"]["sha256"], - }, - "target_branch": consumer["target_branch"], - } - ) - - def public_state( - self, - expected_contract: dict, - expected_raw: bytes, - advanced_components: set[str], - ): - consumers = {consumer["repository"]: consumer for consumer in expected_contract["consumers"]} - baseline_contract = self.contract - baseline_raw = self.contract_raw - - def public_file(url: str) -> bytes: - relative = url.removeprefix("https://raw.githubusercontent.com/") - organization, repository, commit, path = relative.split("/", 3) - self.assertEqual("durable-workflow", organization) - consumer = consumers[f"{organization}/{repository}"] - self.assertEqual(conformance.sha256_bytes(consumer["component"].encode())[:40], commit) - advanced = consumer["component"] in advanced_components - remote_contract = expected_contract if advanced else baseline_contract - remote_raw = expected_raw if advanced else baseline_raw - if path == qualification.CONTRACT_PATH: - return remote_raw - if path == qualification.ADAPTER_PATH: - return self.adapter(consumer, remote_contract, remote_raw) - if path == qualification.SUITE_PATH: - return self.suite_raw - self.fail(f"unexpected public target path: {path}") - - return public_file - - def resolved_target(self, repository: str, branch: str) -> tuple[str, str]: - consumer = next(consumer for consumer in self.contract["consumers"] if consumer["repository"] == repository) - self.assertEqual(consumer["target_branch"], branch) - return f"refs/heads/{branch}", conformance.sha256_bytes(consumer["component"].encode())[:40] - - def test_required_target_branch_workflow_runs_aggregate_qualification(self) -> None: - workflow = yaml.safe_load(SOURCE_QUALIFICATION.read_text()) - source_job = workflow["jobs"]["source"] - steps = source_job["steps"] - audit = next(step for step in steps if step.get("name") == "Audit synchronized release-recovery consumers") - policy = json.loads((ROOT / "qualification" / "policy.json").read_bytes()) - required_check = policy["targets"]["github-control-plane"]["workflows"][0]["required_check"] - self.assertEqual(required_check, source_job["name"]) - self.assertIn("schedule", workflow[True]) - self.assertIn("scripts/release_recovery_target_qualification.py", audit["run"]) - self.assertIn("github.ref == 'refs/heads/main'", audit["if"]) - - def test_cli_is_directly_executable(self) -> None: - result = subprocess.run( - [sys.executable, "scripts/release_recovery_target_qualification.py", "--help"], - cwd=ROOT, - check=False, - capture_output=True, - text=True, - ) - self.assertEqual(0, result.returncode, result.stderr) - - def test_target_ref_resolves_to_one_exact_commit(self) -> None: - commit = "a" * 40 - result = mock.Mock( - returncode=0, - stdout=f"{commit}\trefs/heads/v2\n", - ) - with mock.patch.object(qualification.subprocess, "run", return_value=result) as run: - self.assertEqual( - ("refs/heads/v2", commit), - qualification.resolve_target_commit("durable-workflow/workflow", "v2"), - ) - - run.assert_called_once_with( - [ - "git", - "ls-remote", - "--exit-code", - "--refs", - "https://github.com/durable-workflow/workflow.git", - "refs/heads/v2", - ], - check=False, - capture_output=True, - text=True, - timeout=20, - ) - - result.stdout = f"{commit}\trefs/heads/main\n" - with ( - mock.patch.object(qualification.subprocess, "run", return_value=result), - self.assertRaisesRegex(conformance.ConformanceError, "did not resolve to an exact commit"), - ): - qualification.resolve_target_commit("durable-workflow/workflow", "v2") - - def test_one_consumer_advance_fails_and_a_synchronized_advance_passes(self) -> None: - advanced = copy.deepcopy(self.contract) - advanced["version"] = "1.5.1" - advanced_raw = conformance.canonical_json(advanced) - conformance.validate_contract(advanced, advanced_raw, SUITE_PATH) - - with ( - mock.patch.object( - qualification, - "resolve_target_commit", - side_effect=self.resolved_target, - ), - mock.patch.object( - qualification, - "fetch_public", - side_effect=self.public_state( - advanced, - advanced_raw, - {"workflow"}, - ), - ), - self.assertRaisesRegex( - conformance.ConformanceError, - "waterline does not carry the current shared contract", - ), - ): - qualification.audit_public_targets(advanced, advanced_raw) - - with ( - mock.patch.object( - qualification, - "resolve_target_commit", - side_effect=self.resolved_target, - ), - mock.patch.object( - qualification, - "fetch_public", - side_effect=self.public_state( - advanced, - advanced_raw, - {consumer["component"] for consumer in advanced["consumers"]}, - ), - ), - ): - evidence = qualification.audit_public_targets(advanced, advanced_raw) - - self.assertEqual(7, len(evidence)) - self.assertTrue(all(target["status"] == "pass" for target in evidence)) - self.assertEqual( - {f"refs/heads/{consumer['target_branch']}" for consumer in advanced["consumers"]}, - {target["target_ref"] for target in evidence}, - ) - self.assertTrue(all(conformance.COMMIT_PATTERN.fullmatch(target["source_commit"]) for target in evidence)) - - def test_missing_or_mismatched_identity_and_digest_fail_closed(self) -> None: - consumer = self.contract["consumers"][0] - adapter_raw = self.adapter(consumer, self.contract, self.contract_raw) - adapter = json.loads(adapter_raw) - adapter["target_branch"] = "main" - with self.assertRaisesRegex(conformance.ConformanceError, "wrong target identity"): - qualification.validate_adapter( - conformance.canonical_json(adapter), - consumer, - self.contract, - conformance.sha256_bytes(self.contract_raw), - ) - - adapter = json.loads(adapter_raw) - adapter["suite"]["sha256"] = "0" * 64 - with self.assertRaisesRegex(conformance.ConformanceError, "does not pin the current suite"): - qualification.validate_adapter( - conformance.canonical_json(adapter), - consumer, - self.contract, - conformance.sha256_bytes(self.contract_raw), - ) - - with ( - mock.patch.object( - qualification, - "resolve_target_commit", - side_effect=self.resolved_target, - ), - mock.patch.object( - qualification, - "fetch_public", - side_effect=conformance.ConformanceError("artifact absent"), - ), - self.assertRaisesRegex(conformance.ConformanceError, "artifact absent"), - ): - qualification.audit_public_targets(self.contract, self.contract_raw) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_release_source_preparation.py b/tests/test_release_source_preparation.py deleted file mode 100644 index 935aacf..0000000 --- a/tests/test_release_source_preparation.py +++ /dev/null @@ -1,112 +0,0 @@ -from __future__ import annotations - -import copy -import json -import unittest -from pathlib import Path - -from jsonschema import Draft202012Validator - -from scripts.beta_candidate import COMPONENTS, CandidateError -from scripts.product_train import load_product_train -from scripts.release_plan import ( - RELEASE_CANDIDATE_SOURCE_PREPARATION_PATH, - load_plan, - load_source_preparation, - require_current_source_preparation, - require_prepared_note_sources, - validate_source_preparation, -) - -ROOT = Path(__file__).resolve().parents[1] - - -class ReleaseSourcePreparationTest(unittest.TestCase): - def setUp(self) -> None: - self.preparation = load_source_preparation() - - def test_current_source_preparation_matches_schema_and_product_train(self) -> None: - schema = json.loads((ROOT / "release-plans" / "source-preparation-schema.json").read_text(encoding="utf-8")) - Draft202012Validator.check_schema(schema) - Draft202012Validator(schema).validate(self.preparation) - - product_train = load_product_train() - self.assertEqual(product_train["current"], self.preparation["train"]) - self.assertEqual(list(COMPONENTS), list(self.preparation["components"])) - self.assertEqual( - product_train["trains"][product_train["current"]]["versions"], - {name: identity["version"] for name, identity in self.preparation["components"].items()}, - ) - - def test_current_plan_must_match_the_exact_prepared_sources(self) -> None: - plan = load_plan(ROOT / "release-plans" / "current.json") - self.assertEqual(self.preparation, require_current_source_preparation(plan)) - - plan["components"]["server"]["commit"] = "e" * 40 - with self.assertRaisesRegex(CandidateError, "exact prepared seven-component source tuple"): - require_current_source_preparation(plan) - - def test_first_release_candidate_matches_its_prepared_sources_and_schema(self) -> None: - preparation = load_source_preparation(RELEASE_CANDIDATE_SOURCE_PREPARATION_PATH) - plan = load_plan(ROOT / "release-plans" / "first-release-candidate.json") - schema = json.loads((ROOT / "release-plans" / "source-preparation-schema.json").read_text(encoding="utf-8")) - - Draft202012Validator(schema).validate(preparation) - self.assertEqual("rc", preparation["channel"]) - self.assertEqual("2.0.0-rc.1", preparation["train"]) - self.assertEqual( - { - "cli": "2.0.0-rc.1", - "sdk-php": "2.0.0-rc.1", - "sdk-python": "2.0.0-rc.1", - "sdk-rust": "2.0.0-rc.1", - "server": "2.0.0-rc.2", - "waterline": "2.0.0-rc.1", - "workflow": "2.0.0-rc.1", - }, - {name: identity["version"] for name, identity in preparation["components"].items()}, - ) - self.assertEqual(preparation, require_current_source_preparation(plan)) - self.assertEqual( - plan["components"], - { - name: {"version": identity["version"], "commit": identity["commit"]} - for name, identity in preparation["components"].items() - }, - ) - - changed = copy.deepcopy(preparation) - changed["components"]["cli"]["version"] = "2.0.0-beta.21" - with self.assertRaisesRegex(CandidateError, "component cli has an invalid identity"): - validate_source_preparation(changed) - - def test_prepared_note_digests_are_release_preflight_authority(self) -> None: - release_preparation = { - "components": { - name: { - "release_notes": { - "source": { - "kind": identity["release_notes"]["kind"], - "sha256": identity["release_notes"]["sha256"], - } - } - } - for name, identity in self.preparation["components"].items() - } - } - require_prepared_note_sources(self.preparation, release_preparation) - - changed = copy.deepcopy(release_preparation) - changed["components"]["sdk-python"]["release_notes"]["source"]["sha256"] = "0" * 64 - with self.assertRaisesRegex(CandidateError, "sdk-python release notes differ"): - require_prepared_note_sources(self.preparation, changed) - - def test_source_preparation_rejects_an_unprotected_authorization_state(self) -> None: - changed = copy.deepcopy(self.preparation) - changed["authorization"]["state"] = "authorized" - with self.assertRaisesRegex(CandidateError, "protected authorization boundary"): - validate_source_preparation(changed) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_repository_hygiene.py b/tests/test_repository_hygiene.py deleted file mode 100644 index 4740171..0000000 --- a/tests/test_repository_hygiene.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -import json -import unittest -from pathlib import Path - -from jsonschema import Draft202012Validator - -ROOT = Path(__file__).resolve().parents[1] -INVENTORY_PATH = ROOT / "repository-hygiene" / "inventory.json" -SCHEMA_PATH = ROOT / "repository-hygiene" / "inventory-schema.json" - - -class RepositoryHygieneInventoryTest(unittest.TestCase): - def setUp(self) -> None: - self.inventory = json.loads(INVENTORY_PATH.read_bytes()) - self.schema = json.loads(SCHEMA_PATH.read_bytes()) - - def test_inventory_matches_its_published_schema(self) -> None: - Draft202012Validator.check_schema(self.schema) - Draft202012Validator(self.schema).validate(self.inventory) - - def test_inventory_covers_the_public_repository_authority(self) -> None: - policy = json.loads((ROOT / "issue-authority" / "policy.json").read_bytes()) - repositories = self.inventory["repositories"] - - self.assertEqual(policy["repositories"], [entry["repository"] for entry in repositories]) - self.assertEqual( - { - ".github": "main", - "workflow": "v2", - "waterline": "v2", - "server": "main", - "cli": "main", - "ai": "main", - "sample-app": "main", - "sdk-php": "main", - "sdk-python": "main", - "sdk-rust": "main", - "durable-workflow.github.io": "main", - }, - {entry["repository"]: entry["branch"] for entry in repositories}, - ) - - def test_inventory_uses_the_published_schema_identity(self) -> None: - self.assertEqual("./inventory-schema.json", self.inventory["$schema"]) - self.assertEqual( - self.schema["properties"]["schema"]["const"], - self.inventory["schema"], - ) - self.assertEqual( - self.schema["properties"]["organization"]["const"], - self.inventory["organization"], - ) - self.assertEqual("2.0.0-beta.17", self.inventory["cleanup_release_train"]) - - def test_cleaned_and_retired_repositories_record_removals(self) -> None: - for entry in self.inventory["repositories"]: - if entry["disposition"] in {"cleaned", "retired-bootstrap"}: - self.assertTrue(entry["removals"], entry["repository"]) - self.assertEqual(len(entry["removals"]), len(set(entry["removals"]))) - self.assertEqual(len(entry["retained_surfaces"]), len(set(entry["retained_surfaces"]))) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_stable_authorization.py b/tests/test_stable_authorization.py deleted file mode 100644 index ca8094d..0000000 --- a/tests/test_stable_authorization.py +++ /dev/null @@ -1,536 +0,0 @@ -from __future__ import annotations - -import copy -import hashlib -import json -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path -from unittest import mock - -import yaml -from jsonschema import Draft202012Validator - -from scripts.beta_candidate import CandidateError, canonical_json, manifest_digest -from scripts.stable_authorization import ( - AUTHORIZATION_SCHEMA, - AUTHORIZATION_WORKFLOW_REF, - COMPONENT_NAMES, - CONTRACT_SCHEMA, - CONTRACT_URL, - EXPERIMENT_EVIDENCE_SCHEMA, - POLYGLOT_CELLS, - READOUT_SCHEMA, - RELEASE_CRITICAL_EXPERIMENTS, - REQUEST_SCHEMA, - evaluate, - load_contract, - load_request, - protected_environment_evidence, - protected_run_evidence, - require_ready, - tuple_binding, - validate_existing_authorization, - validate_request, - verified_readout, - verify_artifact_tuple_candidate, - verify_evidence_sources, -) - -ROOT = Path(__file__).resolve().parents[1] -CONTRACT_PATH = ROOT / "stable-authorization" / "contract.json" - - -class RouteClient: - def __init__(self, routes: dict[str, object]) -> None: - self.routes = routes - - def json(self, url: str, **_kwargs: object) -> object: - if url not in self.routes: - raise AssertionError(f"unexpected route: {url}") - return copy.deepcopy(self.routes[url]) - - -def github_user(login: str, identifier: int) -> dict[str, object]: - return { - "login": login, - "id": identifier, - "node_id": f"U_{identifier}", - "url": f"https://api.github.com/users/{login}", - "html_url": f"https://github.com/{login}", - } - - -def artifact_tuple() -> dict[str, object]: - return { - "tag": "release-candidate/rc/coherent-2-0-rc-9", - "commit": "f" * 40, - "components": { - name: { - "version": f"2.0.0-rc.{index + 1}", - "commit": f"{index + 1:040x}", - } - for index, name in enumerate(COMPONENT_NAMES) - }, - } - - -def source_payload(record: dict[str, object]) -> bytes: - return canonical_json({key: value for key, value in record.items() if key != "source"}) - - -def experiment_evidence( - experiment: str, - binding: dict[str, str], -) -> dict[str, object]: - record: dict[str, object] = { - "schema": EXPERIMENT_EVIDENCE_SCHEMA, - "experiment": experiment, - "outcome": "pass", - "runner_blocked": False, - "artifact_tuple": copy.deepcopy(binding), - } - if experiment == "polyglot": - record["cells"] = { - cell: { - "outcome": "pass", - "runner_blocked": False, - "artifact_tuple": copy.deepcopy(binding), - } - for cell in POLYGLOT_CELLS - } - payload = canonical_json(record) - record["source"] = { - "url": f"https://github.com/durable-workflow/.github/releases/download/evidence/{experiment}.json", - "sha256": hashlib.sha256(payload).hexdigest(), - "generated_at": "2026-07-29T13:00:00Z", - } - return record - - -def request() -> dict[str, object]: - selected_tuple = artifact_tuple() - binding = tuple_binding(selected_tuple) - return { - "$schema": "./request-schema.json", - "schema": REQUEST_SCHEMA, - "stable_version": "2.0.0", - "artifact_tuple": selected_tuple, - "evidence": { - "experiments": { - experiment: experiment_evidence(experiment, binding) for experiment in RELEASE_CRITICAL_EXPERIMENTS - } - }, - } - - -def authorization_record( - value: dict[str, object], - readout: dict[str, object], - contract: dict[str, object], -) -> dict[str, object]: - return { - "schema": AUTHORIZATION_SCHEMA, - "channel": "stable", - "stable_version": "2.0.0", - "artifact_tuple": value["artifact_tuple"], - "contract": { - "url": CONTRACT_URL, - "sha256": manifest_digest(contract), - }, - "request_sha256": manifest_digest(value), - "readout_sha256": manifest_digest(readout), - "evidence_gate": "pass", - "decision": { - "status": "authorized", - "type": "protected-human-review", - "actor": "release-operator", - "repository": "durable-workflow/.github", - "workflow_ref": AUTHORIZATION_WORKFLOW_REF, - "workflow_commit": "e" * 40, - "run_id": 100, - "run_attempt": 1, - "run_url": "https://github.com/durable-workflow/.github/actions/runs/100", - "environment": "stable-authorization", - "environment_protection": { - "prevent_self_review": True, - "required_reviewer_user_ids": [1130888], - }, - "environment_approval": { - "state": "approved", - "run_id": 100, - "run_attempt": 1, - "user": github_user("release-owner", 1130888), - }, - }, - } - - -class StableAuthorizationContractTest(unittest.TestCase): - def setUp(self) -> None: - self.contract = load_contract(CONTRACT_PATH) - - def test_contract_declares_the_exact_fixed_tier_and_sdk_cells(self) -> None: - self.assertEqual(CONTRACT_SCHEMA, self.contract["schema"]) - self.assertEqual(list(COMPONENT_NAMES), self.contract["artifact_components"]) - self.assertEqual( - list(RELEASE_CRITICAL_EXPERIMENTS), - self.contract["release_critical_experiments"], - ) - self.assertEqual( - {"polyglot": list(POLYGLOT_CELLS)}, - self.contract["required_sdk_cells"], - ) - self.assertEqual( - "never_authoritative", - self.contract["evidence_policy"]["aggregate_historical_pass_rate"], - ) - self.assertEqual( - {"required": True, "occurs_after_evidence_gate": True}, - self.contract["human_decision"], - ) - - def test_machine_readable_schemas_accept_a_passing_decision(self) -> None: - value = request() - readout = evaluate(self.contract, value) - for filename, document in ( - ("contract-schema.json", self.contract), - ("request-schema.json", value), - ("readout-schema.json", readout), - ( - "authorization-schema.json", - authorization_record(value, readout, self.contract), - ), - ): - schema = json.loads((ROOT / "stable-authorization" / filename).read_bytes()) - Draft202012Validator(schema).validate(document) - - def test_passing_tier_still_awaits_a_human_decision(self) -> None: - readout = evaluate(self.contract, request()) - - self.assertEqual(READOUT_SCHEMA, readout["schema"]) - self.assertEqual("pass", readout["evidence_gate"]) - self.assertEqual("awaiting-human-decision", readout["stable_authorization"]) - self.assertEqual("allowed", readout["prerelease_iteration"]) - self.assertNotIn("pass_rate", json.dumps(readout)) - for status in readout["experiments"].values(): - self.assertEqual("current", status["freshness"]) - self.assertEqual("pass", status["outcome"]) - self.assertTrue(status["ready"]) - for status in readout["experiments"]["polyglot"]["cells"].values(): - self.assertEqual("current", status["freshness"]) - self.assertEqual("pass", status["outcome"]) - self.assertTrue(status["ready"]) - require_ready(readout) - validate_existing_authorization( - authorization_record(request(), readout, self.contract), - request(), - readout, - self.contract, - ) - - def test_missing_evidence_is_visible_without_blocking_prerelease_iteration(self) -> None: - value = request() - value["evidence"]["experiments"].pop("cloud") - - readout = evaluate(self.contract, value) - - self.assertEqual("fail", readout["evidence_gate"]) - self.assertEqual("blocked", readout["stable_authorization"]) - self.assertEqual("allowed", readout["prerelease_iteration"]) - self.assertEqual("missing", readout["experiments"]["cloud"]["freshness"]) - self.assertEqual("missing", readout["experiments"]["cloud"]["outcome"]) - with self.assertRaisesRegex(CandidateError, "cloud"): - require_ready(readout) - - def test_stale_pass_blocks_a_superficially_favorable_aggregate(self) -> None: - value = request() - stale = value["evidence"]["experiments"]["timers"] - stale["artifact_tuple"]["sha256"] = "0" * 64 - - readout = evaluate(self.contract, value) - passing_outcomes = sum(status["outcome"] == "pass" for status in readout["experiments"].values()) - apparent_rate = 100 * passing_outcomes / len(RELEASE_CRITICAL_EXPERIMENTS) - - self.assertGreater(apparent_rate, 90) - self.assertEqual("stale", readout["experiments"]["timers"]["freshness"]) - self.assertEqual("pass", readout["experiments"]["timers"]["outcome"]) - self.assertFalse(readout["experiments"]["timers"]["ready"]) - self.assertEqual("fail", readout["evidence_gate"]) - self.assertEqual( - {"release_authority": "never-authoritative"}, - readout["historical_aggregate"], - ) - - def test_fail_and_runner_blocked_are_independent_fail_closed_states(self) -> None: - failed = request() - failed["evidence"]["experiments"]["activities"]["outcome"] = "fail" - blocked = request() - blocked["evidence"]["experiments"]["heartbeats"]["runner_blocked"] = True - - failed_readout = evaluate(self.contract, failed) - blocked_readout = evaluate(self.contract, blocked) - - self.assertEqual("fail", failed_readout["experiments"]["activities"]["outcome"]) - self.assertEqual( - "runner-blocked", - blocked_readout["experiments"]["heartbeats"]["outcome"], - ) - self.assertEqual("fail", failed_readout["evidence_gate"]) - self.assertEqual("fail", blocked_readout["evidence_gate"]) - - def test_every_polyglot_sdk_cell_must_be_current_and_passing(self) -> None: - missing = request() - missing["evidence"]["experiments"]["polyglot"]["cells"].pop("rust") - stale = request() - stale["evidence"]["experiments"]["polyglot"]["cells"]["php"]["artifact_tuple"]["commit"] = "0" * 40 - blocked = request() - blocked["evidence"]["experiments"]["polyglot"]["cells"]["python"]["runner_blocked"] = True - - missing_readout = evaluate(self.contract, missing) - stale_readout = evaluate(self.contract, stale) - blocked_readout = evaluate(self.contract, blocked) - - self.assertEqual( - "missing", - missing_readout["experiments"]["polyglot"]["cells"]["rust"]["status"], - ) - self.assertEqual( - "stale", - stale_readout["experiments"]["polyglot"]["cells"]["php"]["status"], - ) - self.assertEqual( - "runner-blocked", - blocked_readout["experiments"]["polyglot"]["cells"]["python"]["status"], - ) - for readout in (missing_readout, stale_readout, blocked_readout): - self.assertFalse(readout["experiments"]["polyglot"]["ready"]) - self.assertEqual("fail", readout["evidence_gate"]) - - def test_aggregate_claims_and_non_polyglot_cells_are_rejected(self) -> None: - aggregate = request() - aggregate["historical_pass_rate"] = 99.9 - with self.assertRaisesRegex(CandidateError, "keys must be exactly"): - validate_request(aggregate) - - cells = request() - cells["evidence"]["experiments"]["replay"]["cells"] = {} - with self.assertRaisesRegex(CandidateError, "keys must be exactly"): - validate_request(cells) - - def test_readout_must_be_canonical_and_recomputed_before_use(self) -> None: - value = request() - expected = evaluate(self.contract, value) - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "readout.json" - path.write_bytes(canonical_json(expected)) - self.assertEqual(expected, verified_readout(self.contract, value, path)) - - changed = copy.deepcopy(expected) - changed["experiments"]["timers"]["ready"] = False - path.write_bytes(canonical_json(changed)) - with self.assertRaisesRegex(CandidateError, "differs"): - verified_readout(self.contract, value, path) - - def test_request_loader_rejects_a_noncanonical_tier_name(self) -> None: - value = request() - value["evidence"]["experiments"]["python-sdk"] = value["evidence"]["experiments"].pop("python") - with tempfile.TemporaryDirectory() as temporary: - path = Path(temporary) / "request.json" - path.write_bytes(canonical_json(value)) - with self.assertRaisesRegex(CandidateError, "unknown experiment"): - load_request(path) - - def test_cli_entry_points_are_directly_executable(self) -> None: - for command in ("readout", "require-ready", "check", "record"): - process = subprocess.run( - [sys.executable, "scripts/stable_authorization.py", command, "--help"], - cwd=ROOT, - text=True, - capture_output=True, - check=False, - ) - self.assertEqual(0, process.returncode, process.stderr) - - -class PublicEvidenceTest(unittest.TestCase): - def test_public_evidence_bytes_must_match_every_inline_record(self) -> None: - value = request() - routes = { - record["source"]["url"]: source_payload(record) for record in value["evidence"]["experiments"].values() - } - client = mock.Mock() - client.bytes.side_effect = lambda url: routes[url] - - verify_evidence_sources(client, value) - self.assertEqual(len(RELEASE_CRITICAL_EXPERIMENTS), client.bytes.call_count) - - routes[value["evidence"]["experiments"]["cloud"]["source"]["url"]] = b"{}\n" - with self.assertRaisesRegex(CandidateError, "cloud public evidence digest"): - verify_evidence_sources(client, value) - - @mock.patch("scripts.stable_authorization.read_public_record") - @mock.patch("scripts.stable_authorization.resolve_tag") - def test_candidate_tag_must_publish_the_same_seven_component_tuple( - self, - resolve: mock.Mock, - read: mock.Mock, - ) -> None: - value = request() - resolve.return_value = value["artifact_tuple"]["commit"] - read.return_value = { - "schema": "durable-workflow.release-candidate/v1", - "channel": "rc", - "components": value["artifact_tuple"]["components"], - } - - verify_artifact_tuple_candidate(mock.Mock(), value) - - read.return_value = copy.deepcopy(read.return_value) - read.return_value["components"]["server"]["version"] = "2.0.0-rc.99" - with self.assertRaisesRegex(CandidateError, "differs"): - verify_artifact_tuple_candidate(mock.Mock(), value) - - -class ProtectedHumanDecisionTest(unittest.TestCase): - def test_environment_and_approval_require_an_independent_product_owner(self) -> None: - api = "https://api.github.com/repos/durable-workflow/.github/environments/stable-authorization" - activity = ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=stable-authorization" - ) - run_url = "https://api.github.com/repos/durable-workflow/.github/actions/runs/100" - routes = { - api: { - "id": 17, - "html_url": activity, - "deployment_branch_policy": { - "custom_branch_policies": True, - "protected_branches": False, - }, - "protection_rules": [ - { - "id": 19, - "type": "required_reviewers", - "prevent_self_review": True, - "reviewers": [ - { - "type": "User", - "reviewer": github_user("release-owner", 1130888), - } - ], - } - ], - }, - f"{api}/deployment-branch-policies?per_page=100": { - "total_count": 1, - "branch_policies": [{"id": 23, "name": "main", "type": "branch"}], - }, - run_url: { - "actor": {"login": "release-operator"}, - "repository": {"full_name": "durable-workflow/.github"}, - "id": 100, - "run_attempt": 1, - "event": "workflow_dispatch", - "path": ".github/workflows/stable-authorization.yml", - "head_branch": "main", - "head_sha": "e" * 40, - "html_url": "https://github.com/durable-workflow/.github/actions/runs/100", - }, - f"{run_url}/approvals": [ - { - "comment": "Authorize stable 2.0", - "state": "approved", - "environments": [ - { - "id": 17, - "name": "stable-authorization", - "node_id": "ENV_17", - "html_url": activity, - "url": api, - } - ], - "user": github_user("release-owner", 1130888), - } - ], - } - client = RouteClient(routes) - - protection = protected_environment_evidence(client) - approval = protected_run_evidence( - client, - actor="release-operator", - run_id=100, - run_attempt=1, - workflow_commit="e" * 40, - environment_protection=protection, - ) - - self.assertTrue(protection["prevent_self_review"]) - self.assertEqual([1130888], protection["required_reviewer_user_ids"]) - self.assertEqual("approved", approval["state"]) - self.assertEqual(1130888, approval["user"]["id"]) - - def test_self_review_enabled_environment_fails_closed(self) -> None: - api = "https://api.github.com/repos/durable-workflow/.github/environments/stable-authorization" - activity = ( - "https://github.com/durable-workflow/.github/deployments/activity_log" - "?environments_filter=stable-authorization" - ) - routes = { - api: { - "id": 17, - "html_url": activity, - "deployment_branch_policy": { - "custom_branch_policies": True, - "protected_branches": False, - }, - "protection_rules": [ - { - "id": 19, - "type": "required_reviewers", - "prevent_self_review": False, - "reviewers": [ - { - "type": "User", - "reviewer": github_user("release-owner", 1130888), - } - ], - } - ], - } - } - - with self.assertRaisesRegex(CandidateError, "independent product-owner"): - protected_environment_evidence(RouteClient(routes)) - - -class StableAuthorizationWorkflowTest(unittest.TestCase): - def test_human_environment_is_reached_only_after_the_evidence_job(self) -> None: - path = ROOT / ".github" / "workflows" / "stable-authorization.yml" - source = path.read_text(encoding="utf-8") - workflow = yaml.safe_load(source) - evidence = workflow["jobs"]["evidence"] - authorize = workflow["jobs"]["authorize"] - - self.assertEqual("evidence", authorize["needs"]) - self.assertIn("github.ref == 'refs/heads/main'", authorize["if"]) - self.assertEqual("stable-authorization", authorize["environment"]) - self.assertEqual("read", evidence["permissions"]["contents"]) - self.assertEqual("write", authorize["permissions"]["contents"]) - evidence_commands = "\n".join(step.get("run", "") for step in evidence["steps"]) - authorize_commands = "\n".join(step.get("run", "") for step in authorize["steps"]) - self.assertIn("--verify-public-sources", evidence_commands) - self.assertIn("require-ready", evidence_commands) - self.assertIn("stable_authorization.py record", authorize_commands) - self.assertLess( - evidence_commands.index("--verify-public-sources"), - evidence_commands.index("require-ready"), - ) - self.assertNotIn("pass_rate", source) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_waterline_service_conformance.py b/tests/test_waterline_service_conformance.py deleted file mode 100644 index 0fc96b8..0000000 --- a/tests/test_waterline_service_conformance.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -import json -import tempfile -import unittest -from pathlib import Path - -from scripts.waterline_service_conformance import ( - ServiceConformanceError, - distribution_identity, - write_result, -) - - -class WaterlineServiceConformanceTest(unittest.TestCase): - def test_distribution_identity_retains_exact_manifest(self) -> None: - digest = "a" * 64 - self.assertEqual( - { - "kind": "oci", - "locator": "oci:docker.io/durableworkflow/waterline@2.0.0-beta.10", - "artifacts": [{"name": "manifest", "sha256": digest}], - }, - distribution_identity( - f"docker.io/durableworkflow/waterline@sha256:{digest}", - "2.0.0-beta.10", - ), - ) - - with self.assertRaises(ServiceConformanceError): - distribution_identity("docker.io/durableworkflow/waterline:latest", "2.0.0-beta.10") - - def test_result_retains_service_and_standalone_versions(self) -> None: - environment = { - "DW_SERVER_VERSION": "2.0.0-beta.10", - "DW_WATERLINE_VERSION": "2.0.0-beta.10", - } - identity = distribution_identity( - f"docker.io/durableworkflow/waterline@sha256:{'b' * 64}", - "2.0.0-beta.10", - ) - with tempfile.TemporaryDirectory() as temporary: - directory = Path(temporary) - write_result( - directory, - environment, - "2026-07-22T00:00:00Z", - status="pass", - runner_blocked=False, - identity=identity, - ) - result = json.loads((directory / "waterline-service-conformance-result.json").read_bytes()) - - self.assertEqual( - {"server": "2.0.0-beta.10", "waterline": "2.0.0-beta.10"}, - result["artifact_versions"], - ) - self.assertEqual({"waterline-service": identity}, result["executed_distribution_identities"]) - self.assertEqual("pass", result["scenario_results"]["service_image_php_sdk_standalone"]["status"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_waterline_train.py b/tests/test_waterline_train.py deleted file mode 100644 index c57d4be..0000000 --- a/tests/test_waterline_train.py +++ /dev/null @@ -1,328 +0,0 @@ -from __future__ import annotations - -import subprocess -import unittest -from pathlib import Path - -from jsonschema import Draft202012Validator - -from scripts.waterline_train import ( - QUICKSTART_SCENARIOS, - TrainError, - compatibility_decision, - read_json, - release_identity, - solve_composer_tuple, - validate_contract, - validate_successor_source, - verify_docs_documents, -) - -ROOT = Path(__file__).resolve().parents[1] -WORKFLOW = ROOT / ".github" / "workflows" / "waterline-release-train.yml" -VERSIONS = { - "cli": "2.0.0-rc.12", - "sdk-php": "2.0.0-rc.11", - "sdk-python": "2.0.0-rc.19", - "sdk-rust": "2.0.0-rc.8", - "server": "2.0.0-rc.19", - "waterline": "2.0.0-rc.12", - "workflow": "2.0.0-rc.13", -} - - -def successor_plan() -> dict: - return { - "schema": "durable-workflow.release-plan/v2", - "plan": "waterline-current-successor", - "channel": "rc", - "foundation": { - "tag": "beta-candidate/rc-waterline-current-successor", - "commit": "f" * 40, - }, - "components": { - name: {"version": version, "commit": str(index + 1) * 40} - for index, (name, version) in enumerate(VERSIONS.items()) - }, - "beta_authorization": None, - } - - -def waterline_manifest(*, version: str, sdk: str, workflow: str) -> dict: - return { - "name": "durable-workflow/waterline", - "require": {"durable-workflow/sdk": sdk}, - "require-dev": {"durable-workflow/workflow": workflow}, - "extra": {"durable-workflow": {"product-train": version}}, - } - - -def docs_documents(*, schema_version: int = 8) -> tuple[dict, dict, dict, dict]: - evidence_id = "quickstart-20260809t120000z" - evidence_url = f"https://durable-workflow.com/platform-conformance/evidence/{evidence_id}.json" - audit = { - "schema": "durable-workflow.docs.page-release-audit", - "schema_version": schema_version, - "artifact_versions": VERSIONS, - "artifact_compatibility_evidence": { - "role": "qualified_aggregate_recommendation", - "outcome": "pass", - "qualified_artifact_versions": VERSIONS, - }, - "quickstart_qualification": { - "role": "five_scenario_exact_current", - "outcome": "pass", - "artifact_versions": VERSIONS, - "contract_artifact_versions": VERSIONS, - "execution_artifact_versions": VERSIONS, - "required_scenarios": list(QUICKSTART_SCENARIOS), - "evidence": {"id": evidence_id, "url": evidence_url}, - }, - } - published = {"artifacts": VERSIONS} - contract = { - "schema": "durable-workflow.docs.v2.quickstart-execution-contract", - "artifacts": {name: {"version": version} for name, version in VERSIONS.items()}, - "scenarios": [{"id": scenario} for scenario in QUICKSTART_SCENARIOS], - } - evidence = { - "schema": "durable-workflow.v2.platform-conformance.run-evidence", - "schema_version": 1, - "id": evidence_id, - "experiment": "quickstart", - "evidence_kind": "executed_run", - "artifact_tuple": VERSIONS, - "outcome": "pass", - "runner_blocked": False, - "qualification": { - "scenario_results": [ - {"id": scenario, "outcome": "pass"} for scenario in QUICKSTART_SCENARIOS - ], - "exact_composer_graph": { - "outcome": "pass", - "artifact_tuple": { - name: VERSIONS[name] for name in ("sdk-php", "waterline", "workflow") - }, - "manifest_sha256": "a" * 64, - "install_output_sha256": "b" * 64, - "package_discovery": "pass", - "package_discovery_output_sha256": "c" * 64, - "laravel_boot": "pass", - }, - }, - } - return audit, published, contract, evidence - - -class WaterlineTrainTest(unittest.TestCase): - def test_contract_preserves_exact_sequential_release_policy(self) -> None: - validate_contract(read_json(ROOT / "waterline-train" / "contract.json")) - - def test_completion_schema_requires_immutable_public_identities(self) -> None: - schema = read_json(ROOT / "waterline-train" / "completion-evidence-schema.json") - - Draft202012Validator.check_schema(schema) - plan_authority = schema["properties"]["plan_authority"] - self.assertIn("record_commit", plan_authority["required"]) - self.assertIn("release_asset_id", plan_authority["required"]) - self.assertIn("quickstart_evidence_sha256", schema["properties"]["deployed_docs"]["required"]) - self.assertIn("laravel_boot", schema["properties"]["composer_resolution"]["required"]) - self.assertIn("contract_artifact_tuple", schema["properties"]["quickstart"]["required"]) - self.assertIn("execution_artifact_tuple", schema["properties"]["quickstart"]["required"]) - - def test_github_release_identity_must_be_public_and_immutable(self) -> None: - with self.assertRaisesRegex(TrainError, "immutable release id"): - release_identity({"id": True, "html_url": "https://github.com/example/release"}, label="release") - with self.assertRaisesRegex(TrainError, "canonical public URL"): - release_identity({"id": 42, "html_url": "https://example.test/release"}, label="release") - - def test_sdk_only_advance_routes_immediate_waterline_successor(self) -> None: - decision = compatibility_decision( - "2.0.0-rc.11", - "2.0.0-rc.11", - {"require": {"durable-workflow/sdk": "2.0.0-rc.7"}}, - ) - - self.assertEqual("route_sequential_waterline_successor", decision["action"]) - self.assertEqual("2.0.0-rc.12", decision["required_successor_version"]) - - def test_matching_exact_dependency_can_enter_public_qualification(self) -> None: - decision = compatibility_decision( - "2.0.0-rc.11", - "2.0.0-rc.12", - {"require": {"durable-workflow/sdk": "2.0.0-rc.11"}}, - ) - - self.assertEqual("qualify_exact_current_tuple", decision["action"]) - - def test_successor_source_binds_sequential_version_and_exact_packages(self) -> None: - result = validate_successor_source( - successor_plan(), - waterline_manifest( - version=VERSIONS["waterline"], - sdk=VERSIONS["sdk-php"], - workflow=VERSIONS["workflow"], - ), - waterline_manifest( - version="2.0.0-rc.11", - sdk="2.0.0-rc.7", - workflow="2.0.0-rc.12", - ), - ) - - self.assertEqual("sdk-advance-with-sequential-waterline-successor", result["kind"]) - self.assertEqual("2.0.0-rc.11", result["predecessor_waterline"]) - - def test_cross_prerelease_shim_cannot_replace_the_exact_successor_pin(self) -> None: - manifest = waterline_manifest( - version=VERSIONS["waterline"], - sdk=VERSIONS["sdk-php"], - workflow=VERSIONS["workflow"], - ) - manifest["require"]["durable-workflow/sdk"] = "^2.0.0-rc.7" - - with self.assertRaisesRegex(TrainError, "exact PHP SDK"): - validate_successor_source(successor_plan(), manifest, manifest) - - def test_current_v8_docs_audit_can_complete_exact_public_qualification(self) -> None: - self.assertEqual(VERSIONS, verify_docs_documents(*docs_documents(), VERSIONS)) - - def test_previous_v7_docs_audit_remains_supported(self) -> None: - audit, published, contract, evidence = docs_documents(schema_version=7) - audit["quickstart_qualification"].pop("contract_artifact_versions") - audit["quickstart_qualification"].pop("execution_artifact_versions") - - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_previous_v6_docs_audit_remains_supported(self) -> None: - audit, published, contract, evidence = docs_documents(schema_version=6) - audit["quickstart_qualification"].pop("contract_artifact_versions") - audit["quickstart_qualification"].pop("execution_artifact_versions") - - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_unknown_docs_audit_schema_remains_fail_closed(self) -> None: - audit, published, contract, evidence = docs_documents(schema_version=9) - - with self.assertRaisesRegex(TrainError, "unsupported schema"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_source_only_state_cannot_complete_without_retained_quickstart(self) -> None: - audit, published, contract, evidence = docs_documents() - audit["quickstart_qualification"] = { - **audit["quickstart_qualification"], - "outcome": "incomplete", - "evidence": None, - } - - with self.assertRaisesRegex(TrainError, "lack passing five-scenario"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_fabricated_or_stale_docs_tuple_cannot_complete(self) -> None: - audit, published, contract, evidence = docs_documents() - audit["artifact_versions"] = {**VERSIONS, "waterline": "2.0.0-rc.99"} - - with self.assertRaisesRegex(TrainError, "exact immutable successor tuple"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_stale_rendered_install_pin_cannot_complete(self) -> None: - audit, published, contract, evidence = docs_documents() - audit["artifact_compatibility_evidence"]["qualified_artifact_versions"] = { - **VERSIONS, - "sdk-php": "2.0.0-rc.6", - } - - with self.assertRaisesRegex(TrainError, "install pins do not name the exact qualified"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_tuple_mismatched_retained_run_cannot_complete(self) -> None: - audit, published, contract, evidence = docs_documents() - evidence["artifact_tuple"] = {**VERSIONS, "sdk-php": "2.0.0-rc.10"} - - with self.assertRaisesRegex(TrainError, "does not prove the exact current"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_contract_tuple_must_equal_the_executed_tuple(self) -> None: - audit, published, contract, evidence = docs_documents() - contract["artifacts"]["sdk-php"]["version"] = "2.0.0-rc.6" - - with self.assertRaisesRegex(TrainError, "contract does not name the exact execution tuple"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_retained_run_must_prove_each_quickstart_scenario_passed(self) -> None: - audit, published, contract, evidence = docs_documents() - evidence["qualification"]["scenario_results"][-1]["outcome"] = "fail" - - with self.assertRaisesRegex(TrainError, "does not prove all five scenarios passed"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_retained_run_composer_graph_must_use_the_execution_tuple(self) -> None: - audit, published, contract, evidence = docs_documents() - evidence["qualification"]["exact_composer_graph"]["artifact_tuple"]["sdk-php"] = ( - "2.0.0-rc.6" - ) - - with self.assertRaisesRegex(TrainError, "does not prove the exact Composer install"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_retained_run_must_prove_laravel_package_discovery(self) -> None: - audit, published, contract, evidence = docs_documents() - evidence["qualification"]["exact_composer_graph"]["package_discovery"] = "fail" - - with self.assertRaisesRegex(TrainError, "does not prove the exact Composer install"): - verify_docs_documents(audit, published, contract, evidence, VERSIONS) - - def test_unresolvable_composer_tuple_cannot_complete(self) -> None: - def fail_solver(args: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]: - returncode = 0 if args[1] == "create-project" else 2 - return subprocess.CompletedProcess(args, returncode, "", "dependency conflict") - - with self.assertRaisesRegex(TrainError, "not installable in Laravel"): - solve_composer_tuple(VERSIONS, runner=fail_solver, probe_installer=lambda _root: None) - - def test_laravel_boot_evidence_is_generated_from_the_exact_tuple(self) -> None: - commands: list[list[str]] = [] - - def pass_solver(args: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]: - commands.append(args) - return subprocess.CompletedProcess(args, 0, "lock operations", "") - - evidence = solve_composer_tuple( - VERSIONS, - runner=pass_solver, - probe_installer=lambda _root: None, - ) - - self.assertEqual("pass", evidence["outcome"]) - self.assertEqual("pass", evidence["laravel_boot"]) - self.assertEqual( - {name: VERSIONS[name] for name in ("waterline", "workflow", "sdk-php")}, - evidence["artifact_tuple"], - ) - self.assertEqual("create-project", commands[0][1]) - self.assertEqual("require", commands[1][1]) - self.assertEqual(["php", "artisan", "package:discover"], commands[2][:3]) - self.assertNotIn("--dry-run", commands[1]) - - def test_laravel_package_discovery_failure_cannot_complete(self) -> None: - def discover_fail(args: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]: - returncode = 1 if args[0] == "php" else 0 - return subprocess.CompletedProcess(args, returncode, "", "missing interface") - - with self.assertRaisesRegex(TrainError, "does not boot through Laravel package discovery"): - solve_composer_tuple( - VERSIONS, - runner=discover_fail, - probe_installer=lambda _root: None, - ) - - def test_workflow_accepts_only_an_immutable_plan_identity(self) -> None: - source = WORKFLOW.read_text(encoding="utf-8") - - self.assertIn("plan_tag:", source) - self.assertIn("qualify-public", source) - self.assertNotIn("completion_evidence", source) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/verification_fixture.py b/tests/verification_fixture.py deleted file mode 100644 index 7ec0b4b..0000000 --- a/tests/verification_fixture.py +++ /dev/null @@ -1,295 +0,0 @@ -from __future__ import annotations - -import urllib.parse -from typing import Any - -from scripts.beta_candidate import ( - CLI_ASSETS, - COMPONENTS, - LEGACY_SCHEMA, - LEGACY_VERIFICATION_SCHEMA, - VERIFICATION_SCHEMA, - WATERLINE_SERVICE, - manifest_digest, -) - - -def legacy_candidate_manifest() -> dict[str, Any]: - return { - "schema": LEGACY_SCHEMA, - "candidate": "beta-continuity-foundation", - "components": { - "workflow": { - "version": "2.0.0-alpha.284", - "commit": "80bef5d9bf01f3282c088b59c433e46b8b146617", - }, - "waterline": { - "version": "2.0.0-alpha.133", - "commit": "5c311cef874601b23342aad1cdd02c9d20483a79", - }, - "server": { - "version": "0.2.663", - "commit": "ce07bf90497a1bd0a0259b3c70c2e1a63302b6b6", - }, - "cli": { - "version": "0.1.91", - "commit": "83b3d01d06ade2380c9e84dc722d485972380167", - }, - "sdk-php": { - "version": "0.1.7", - "commit": "73a9482f431ae522fcb9b8b94158a9f7f9d4f589", - }, - "sdk-python": { - "version": "0.4.99", - "commit": "46957ac89385902988598eb1d538c6f38da92ab3", - }, - "sdk-rust": { - "version": "0.1.15", - "commit": "810f9b53ea418718a5b85804e139a416692713ea", - }, - }, - } - - -def _historical_components(identities: dict[str, tuple[str, str]]) -> dict[str, dict[str, str]]: - return { - name: { - "version": version, - "commit": commit, - } - for name, (version, commit) in identities.items() - } - - -def legacy_beta_one_candidate_manifest() -> dict[str, Any]: - return { - "schema": LEGACY_SCHEMA, - "candidate": "beta-beta-1-e743e3760000", - "components": _historical_components( - { - "workflow": ("2.0.0-beta.1", "22bbf2a1469f4a38b1a6e1006ca8e46835c2fea4"), - "waterline": ("2.0.0-beta.1", "0fb3caaba1e8a77f9bfa63ba3dcb2bcbaa825c31"), - "server": ("0.2.699", "d6e8fb6c76c1d71cc7d3a1d38bdebd324150acad"), - "cli": ("0.1.95", "bc036e94604329612b65a2a9effe2e929f91f4e1"), - "sdk-php": ("0.1.16", "3b79813b1bbcb811277cc30d8dcfc359ea53f65c"), - "sdk-python": ("0.4.106", "13037ddcb1f55d72c24256591e346b991ad64273"), - "sdk-rust": ("0.1.22", "6fa98425c8ec7690ef96f8296a21407aa8d03067"), - } - ), - } - - -def legacy_beta_one_release_plan() -> dict[str, Any]: - candidate = legacy_beta_one_candidate_manifest() - return { - "schema": "durable-workflow.release-plan/v1", - "plan": "beta-1-e743e3760000", - "channel": "beta", - "foundation": { - "tag": "beta-candidate/beta-continuity-foundation", - "commit": "4995052410bd4301c5796ffba54e0b6d2f490ed1", - }, - "components": candidate["components"], - "beta_authorization": { - "tag": "beta-authorization/beta-1-e743e3760000", - "commit": "bef98bfd61b604d48459c15e968e3ace8e5124b0", - }, - } - - -def legacy_completed_candidate_manifests() -> list[dict[str, Any]]: - continuity_components = { - "workflow": ("2.0.0-alpha.289", "54320e6687d7028dfb625152a864ba218e125afe"), - "waterline": ("2.0.0-alpha.136", "60b2de727e27cc17fcb611b3650e0c100968547b"), - "server": ("0.2.666", "ffa5bf5e0d68a032550451dc31073cc83c25fc52"), - "cli": ("0.1.92", "9aafb87f2432cba0be9ed21e4e509cb9b6acda5d"), - "sdk-php": ("0.1.9", "05fe99b44062b939e4c43acc00dae457eef87af2"), - "sdk-python": ("0.4.101", "8aa0e86fe51edc1e7aba3d97ddf3dfda8009ee23"), - "sdk-rust": ("0.1.16", "31e87f4aa13a7fd255fd277a62c43c96ee1532ab"), - } - continuity_successor_components = { - **continuity_components, - "server": ("0.2.667", "305f29b3b123bb8c805a9ce7b13dd6d8485f7bfa"), - } - release_preparation_components = { - "workflow": ("2.0.0-alpha.291", "518a27492d38bd92bca3e2bb91b9ccf82da9589b"), - "waterline": ("2.0.0-alpha.137", "4c90258f077724fd1267fd8a2d6e902162a835bf"), - "server": ("0.2.689", "5772b22c81c20b2d3b49fe539fe1244f69b3e66d"), - "cli": ("0.1.93", "3fcc580722d0e1e9f5d3da21472d811697f3d3e9"), - "sdk-php": ("0.1.13", "4863f4d8ce4ac935f6187d87bfb435918cdf8653"), - "sdk-python": ("0.4.102", "0c7ea1b18a754191a72d4c4884ea83b087f27ff9"), - "sdk-rust": ("0.1.17", "68f1adf24939885bb6779918c1cc197f7f0565d7"), - } - return [ - { - "schema": LEGACY_SCHEMA, - "candidate": "alpha-alpha-continuity-drill-20260716c", - "components": _historical_components(continuity_components), - }, - { - "schema": LEGACY_SCHEMA, - "candidate": "alpha-alpha-continuity-drill-20260716d", - "components": _historical_components(continuity_successor_components), - }, - { - "schema": LEGACY_SCHEMA, - "candidate": "alpha-alpha-release-preparation-proof-20260719", - "components": _historical_components(release_preparation_components), - }, - ] - - -def candidate_verification(candidate: dict[str, Any], *, verified_at: str = "2026-07-20T21:00:00Z") -> dict[str, Any]: - results: dict[str, Any] = {} - for index, (name, identity) in enumerate(candidate["components"].items(), start=1): - component = COMPONENTS[name] - version = identity["version"] - commit = identity["commit"] - source = { - "repository": component.repository, - "tag": version, - "tag_object": commit, - "commit": commit, - "url": f"https://github.com/{component.repository}/tree/{version}", - } - - def download(filename: str, seed: int = index, component_name: str = name) -> dict[str, Any]: - return { - "url": f"https://downloads.example.test/{component_name}/{urllib.parse.quote(filename)}", - "size": 1000 + seed, - "sha256": f"{seed:064x}", - } - - if component.distribution == "composer": - encoded = "/".join(urllib.parse.quote(part, safe="") for part in component.package.split("/")) - distribution = { - "kind": "composer", - "package": component.package, - "registry": f"https://repo.packagist.org/p2/{encoded}.json", - "source_reference": commit, - "dist_reference": commit, - "dist": download("package.zip"), - } - elif component.distribution == "github-release": - distribution = { - "kind": "github-release", - "repository": component.repository, - "release_id": 100 + index, - "release_url": f"https://github.com/{component.repository}/releases/tag/{version}", - "build_attestations_verified": True, - "build_attestation_authority": { - "mode": "exact-tag", - "ref": f"refs/tags/{version}", - "commit": commit, - }, - "package_source": { - "commit": commit, - "embedded_phar_identity": f"dw {version} (commit {commit[:12]})", - }, - "assets": [ - { - "name": asset, - "asset_id": 1000 + asset_index, - **download(asset, 20 + asset_index), - } - for asset_index, asset in enumerate(sorted(CLI_ASSETS), start=1) - ], - } - elif component.distribution == "pypi": - encoded_package = urllib.parse.quote(component.package, safe="") - encoded_version = urllib.parse.quote(version, safe="") - distribution = { - "kind": "pypi", - "package": component.package, - "registry": f"https://pypi.org/pypi/{encoded_package}/{encoded_version}/json", - "source_identity": { - "source_archive": download("source.tar.gz", 40), - "source_files_compared": 12, - "wheel_files_compared": 8, - "source_commit": commit, - }, - "files": [ - { - **download("durable_workflow.whl", 41), - "filename": "durable_workflow.whl", - "package_type": "bdist_wheel", - }, - { - **download("durable_workflow.tar.gz", 42), - "filename": "durable_workflow.tar.gz", - "package_type": "sdist", - }, - ], - } - elif component.distribution == "crates.io": - encoded_package = urllib.parse.quote(component.package, safe="") - encoded_version = urllib.parse.quote(version, safe="") - distribution = { - "kind": "crates.io", - "package": component.package, - "registry": f"https://crates.io/api/v1/crates/{encoded_package}/{encoded_version}", - "archive_vcs_commit": commit, - "archive_vcs_dirty": False, - "archive": download("package.crate"), - } - elif component.distribution == "oci": - labels = { - "org.opencontainers.image.revision": commit, - "dev.durable-workflow.release.tag": version, - } - distribution = { - "kind": "oci", - "image": f"{component.package}:{version}", - "manifest_digest": f"sha256:{'a' * 64}", - "platforms": ["linux/amd64", "linux/arm64"], - "configs": [ - {"digest": f"sha256:{index + 10:064x}", "labels": labels}, - {"digest": f"sha256:{index + 20:064x}", "labels": labels}, - ], - } - else: - raise AssertionError(component.distribution) - results[name] = { - "version": version, - "commit": commit, - "source": source, - "outcome": "verified", - } - if name == "waterline": - labels = { - "org.opencontainers.image.revision": commit, - "dev.durable-workflow.release.tag": version, - } - results[name]["distributions"] = { - "embedded": distribution, - "service": { - "kind": "oci", - "image": f"{WATERLINE_SERVICE.package}:{version}", - "manifest_digest": f"sha256:{'b' * 64}", - "platforms": ["linux/amd64", "linux/arm64"], - "configs": [ - {"digest": f"sha256:{index + 30:064x}", "labels": labels}, - {"digest": f"sha256:{index + 40:064x}", "labels": labels}, - ], - }, - } - else: - results[name]["distribution"] = distribution - return { - "schema": VERIFICATION_SCHEMA, - "candidate": candidate["candidate"], - "manifest_sha256": manifest_digest(candidate), - "verified_at": verified_at, - "outcome": "verified", - "components": results, - } - - -def legacy_candidate_verification( - candidate: dict[str, Any], *, verified_at: str = "2026-07-20T21:00:00Z" -) -> dict[str, Any]: - result = candidate_verification(candidate, verified_at=verified_at) - result["schema"] = LEGACY_VERIFICATION_SCHEMA - waterline = result["components"]["waterline"] - waterline["distribution"] = waterline.pop("distributions")["embedded"] - return result diff --git a/waterline-train/README.md b/waterline-train/README.md deleted file mode 100644 index 9a9c9b0..0000000 --- a/waterline-train/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Waterline release train - -Waterline intentionally pins the PHP SDK exactly during the 2.0 prerelease. -That keeps each published Waterline package reproducible, but means a newer PHP -SDK cannot become part of the current install tuple until a sequential -Waterline prerelease exists with the new exact pin. - -`contract.json` is the machine-owned coordination policy. A conflicting SDK -advance routes a Waterline successor; it never mutates an old package or adds a -compatibility shim. The completion workflow accepts only an immutable release -plan tag. It re-reads the tag, Git record, and release mirror; verifies the -source-bound GitHub releases, Packagist packages, and Waterline service image; -runs a clean exact Composer install and Laravel package-discovery boot; and -binds the deployed docs revision, quickstart contract tuple, and retained run's -execution tuple. The retained run must itself contain passing results for all -five scenarios and exact-graph install, package-discovery, and Laravel-boot -proof. Caller-authored pass fields are not completion evidence. - -`completion-evidence-schema.json` defines the record generated from those -public checks. Completion remains unavailable while the current docs tuple or -rendered install pins, or five-scenario quickstart evidence, are absent, stale, -or bound to another tuple. diff --git a/waterline-train/completion-evidence-schema.json b/waterline-train/completion-evidence-schema.json deleted file mode 100644 index 50d4d4a..0000000 --- a/waterline-train/completion-evidence-schema.json +++ /dev/null @@ -1,237 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/waterline-train/completion-evidence-schema.json", - "title": "Waterline release-train completion evidence", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "outcome", - "plan_authority", - "artifact_tuple", - "transition", - "composer_resolution", - "public_artifacts", - "deployed_docs", - "quickstart" - ], - "properties": { - "schema": {"const": "durable-workflow.waterline-release-completion/v3"}, - "outcome": {"const": "pass"}, - "plan_authority": { - "type": "object", - "additionalProperties": false, - "required": [ - "outcome", - "tag", - "record_commit", - "sha256", - "release_id", - "release_asset_id", - "release_asset_url" - ], - "properties": { - "outcome": {"const": "verified"}, - "tag": {"pattern": "^release-plan/[a-z0-9][a-z0-9._-]{0,55}$"}, - "record_commit": {"$ref": "#/$defs/commit"}, - "sha256": {"$ref": "#/$defs/sha256"}, - "release_id": {"type": "integer", "minimum": 1}, - "release_asset_id": {"type": "integer", "minimum": 1}, - "release_asset_url": {"type": "string", "format": "uri", "pattern": "^https://"} - } - }, - "artifact_tuple": {"$ref": "#/$defs/artifactTuple"}, - "transition": { - "type": "object", - "additionalProperties": false, - "required": [ - "outcome", - "kind", - "predecessor_waterline", - "predecessor_sdk_requirement", - "successor_waterline", - "successor_sdk_requirement", - "successor_composer_sha256", - "predecessor_source_commit", - "predecessor_composer_sha256" - ], - "properties": { - "outcome": {"const": "verified"}, - "kind": {"const": "sdk-advance-with-sequential-waterline-successor"}, - "predecessor_waterline": {"$ref": "#/$defs/version"}, - "predecessor_sdk_requirement": {"$ref": "#/$defs/version"}, - "successor_waterline": {"$ref": "#/$defs/version"}, - "successor_sdk_requirement": {"$ref": "#/$defs/version"}, - "successor_composer_sha256": {"$ref": "#/$defs/sha256"}, - "predecessor_source_commit": {"$ref": "#/$defs/commit"}, - "predecessor_composer_sha256": {"$ref": "#/$defs/sha256"} - } - }, - "composer_resolution": { - "type": "object", - "additionalProperties": false, - "required": [ - "outcome", - "artifact_tuple", - "manifest_sha256", - "install_output_sha256", - "package_discovery_output_sha256", - "laravel_boot" - ], - "properties": { - "outcome": {"const": "pass"}, - "artifact_tuple": {"$ref": "#/$defs/composerTuple"}, - "manifest_sha256": {"$ref": "#/$defs/sha256"}, - "install_output_sha256": {"$ref": "#/$defs/sha256"}, - "package_discovery_output_sha256": {"$ref": "#/$defs/sha256"}, - "laravel_boot": {"const": "pass"} - } - }, - "public_artifacts": { - "type": "object", - "additionalProperties": false, - "required": ["waterline", "workflow", "sdk-php"], - "properties": { - "waterline": { - "type": "object", - "additionalProperties": false, - "required": ["github_release", "packagist", "container_image"], - "properties": { - "github_release": {"$ref": "#/$defs/verifiedObject"}, - "packagist": {"$ref": "#/$defs/nonemptyObject"}, - "container_image": {"$ref": "#/$defs/nonemptyObject"} - } - }, - "workflow": {"$ref": "#/$defs/composerArtifact"}, - "sdk-php": {"$ref": "#/$defs/composerArtifact"} - } - }, - "deployed_docs": { - "type": "object", - "additionalProperties": false, - "required": [ - "outcome", - "docs_revision", - "audit_url", - "audit_sha256", - "artifact_tuple_source_url", - "artifact_tuple_source_sha256", - "quickstart_contract_url", - "quickstart_contract_sha256", - "quickstart_evidence_url", - "quickstart_evidence_sha256", - "quickstart_evidence_id", - "quickstart_contract_artifact_tuple", - "quickstart_execution_artifact_tuple", - "artifact_tuple" - ], - "properties": { - "outcome": {"const": "pass"}, - "docs_revision": {"$ref": "#/$defs/commit"}, - "audit_url": {"const": "https://durable-workflow.com/docs-page-release-audit.json"}, - "audit_sha256": {"$ref": "#/$defs/sha256"}, - "artifact_tuple_source_url": {"type": "string", "format": "uri", "pattern": "^https://"}, - "artifact_tuple_source_sha256": {"$ref": "#/$defs/sha256"}, - "quickstart_contract_url": {"const": "https://durable-workflow.com/quickstart-execution-contract.json"}, - "quickstart_contract_sha256": {"$ref": "#/$defs/sha256"}, - "quickstart_evidence_url": { - "type": "string", - "format": "uri", - "pattern": "^https://durable-workflow\\.com/platform-conformance/evidence/[a-z0-9][a-z0-9._-]+\\.json$" - }, - "quickstart_evidence_sha256": {"$ref": "#/$defs/sha256"}, - "quickstart_evidence_id": {"pattern": "^[a-z0-9][a-z0-9._-]+$"}, - "quickstart_contract_artifact_tuple": {"$ref": "#/$defs/artifactTuple"}, - "quickstart_execution_artifact_tuple": {"$ref": "#/$defs/artifactTuple"}, - "artifact_tuple": {"$ref": "#/$defs/artifactTuple"} - } - }, - "quickstart": { - "type": "object", - "additionalProperties": false, - "required": [ - "outcome", - "evidence_id", - "evidence_url", - "evidence_sha256", - "artifact_tuple", - "contract_artifact_tuple", - "execution_artifact_tuple", - "scenarios" - ], - "properties": { - "outcome": {"const": "pass"}, - "evidence_id": {"pattern": "^[a-z0-9][a-z0-9._-]+$"}, - "evidence_url": {"type": "string", "format": "uri", "pattern": "^https://"}, - "evidence_sha256": {"$ref": "#/$defs/sha256"}, - "artifact_tuple": {"$ref": "#/$defs/artifactTuple"}, - "contract_artifact_tuple": {"$ref": "#/$defs/artifactTuple"}, - "execution_artifact_tuple": {"$ref": "#/$defs/artifactTuple"}, - "scenarios": { - "type": "array", - "prefixItems": [ - {"const": "php_user_local_server_completion"}, - {"const": "python_user_local_server_completion"}, - {"const": "rust_user_local_server_completion"}, - {"const": "operator_local_server_observation"}, - {"const": "laravel_user_embedded_completion"} - ], - "items": false, - "minItems": 5, - "maxItems": 5 - } - } - } - }, - "$defs": { - "version": { - "type": "string", - "pattern": "^2\\.0\\.0-(?:beta|rc)\\.[1-9][0-9]*$" - }, - "anyVersion": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?$" - }, - "commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, - "artifactTuple": { - "type": "object", - "additionalProperties": false, - "required": ["cli", "sdk-php", "sdk-python", "sdk-rust", "server", "waterline", "workflow"], - "properties": { - "cli": {"$ref": "#/$defs/anyVersion"}, - "sdk-php": {"$ref": "#/$defs/version"}, - "sdk-python": {"$ref": "#/$defs/anyVersion"}, - "sdk-rust": {"$ref": "#/$defs/anyVersion"}, - "server": {"$ref": "#/$defs/anyVersion"}, - "waterline": {"$ref": "#/$defs/version"}, - "workflow": {"$ref": "#/$defs/version"} - } - }, - "composerTuple": { - "type": "object", - "additionalProperties": false, - "required": ["waterline", "workflow", "sdk-php"], - "properties": { - "waterline": {"$ref": "#/$defs/version"}, - "workflow": {"$ref": "#/$defs/version"}, - "sdk-php": {"$ref": "#/$defs/version"} - } - }, - "nonemptyObject": {"type": "object", "minProperties": 1}, - "verifiedObject": { - "type": "object", - "required": ["outcome"], - "properties": {"outcome": {"const": "verified"}} - }, - "composerArtifact": { - "type": "object", - "additionalProperties": false, - "required": ["github_release", "packagist"], - "properties": { - "github_release": {"$ref": "#/$defs/verifiedObject"}, - "packagist": {"$ref": "#/$defs/nonemptyObject"} - } - } - } -} diff --git a/waterline-train/contract.json b/waterline-train/contract.json deleted file mode 100644 index 11efcaf..0000000 --- a/waterline-train/contract.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "./schema.json", - "schema": "durable-workflow.waterline-release-train/v1", - "composer_tuple": { - "waterline": "durable-workflow/waterline", - "workflow": "durable-workflow/workflow", - "sdk-php": "durable-workflow/sdk" - }, - "dependency_policy": { - "waterline_to_sdk": "exact", - "conflicting_sdk_prerelease": "sequential_waterline_successor_required", - "historical_prereleases": "immutable", - "cross_prerelease_compatibility_shim": "forbidden" - }, - "completion_requires": [ - "immutable_successor_plan_identity", - "source_bound_github_release", - "packagist_package", - "container_image", - "deployed_docs_artifact_tuple", - "exact_current_composer_laravel_boot", - "retained_five_scenario_quickstart" - ], - "quickstart_scenarios": [ - "php_user_local_server_completion", - "python_user_local_server_completion", - "rust_user_local_server_completion", - "operator_local_server_observation", - "laravel_user_embedded_completion" - ] -} diff --git a/waterline-train/schema.json b/waterline-train/schema.json deleted file mode 100644 index 2acb757..0000000 --- a/waterline-train/schema.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/durable-workflow/.github/blob/main/waterline-train/schema.json", - "title": "Waterline and PHP SDK sequential release train", - "type": "object", - "additionalProperties": false, - "required": [ - "$schema", - "schema", - "composer_tuple", - "dependency_policy", - "completion_requires", - "quickstart_scenarios" - ], - "properties": { - "$schema": {"const": "./schema.json"}, - "schema": {"const": "durable-workflow.waterline-release-train/v1"}, - "composer_tuple": { - "const": { - "waterline": "durable-workflow/waterline", - "workflow": "durable-workflow/workflow", - "sdk-php": "durable-workflow/sdk" - } - }, - "dependency_policy": { - "const": { - "waterline_to_sdk": "exact", - "conflicting_sdk_prerelease": "sequential_waterline_successor_required", - "historical_prereleases": "immutable", - "cross_prerelease_compatibility_shim": "forbidden" - } - }, - "completion_requires": { - "const": [ - "immutable_successor_plan_identity", - "source_bound_github_release", - "packagist_package", - "container_image", - "deployed_docs_artifact_tuple", - "exact_current_composer_laravel_boot", - "retained_five_scenario_quickstart" - ] - }, - "quickstart_scenarios": { - "const": [ - "php_user_local_server_completion", - "python_user_local_server_completion", - "rust_user_local_server_completion", - "operator_local_server_observation", - "laravel_user_embedded_completion" - ] - } - } -}