diff --git a/.github/workflows/_cf-worker-delivery.yml b/.github/workflows/_cf-worker-delivery.yml index 4242f81..45a78f3 100644 --- a/.github/workflows/_cf-worker-delivery.yml +++ b/.github/workflows/_cf-worker-delivery.yml @@ -62,7 +62,7 @@ on: type: string default: '' descriptor: - description: toJSON of THIS kind's target descriptor (versioned, validated by the Delivery plan and re-validated by the engine). Carries the engine target, GitHub Environment, concurrency groups, expected physical D1, migration owner and smoke mode; never a secret. + description: toJSON of THIS kind's target descriptor (versioned, validated by the Plan job and re-validated by the engine). Carries the engine target, GitHub Environment, concurrency groups, expected physical D1, migration owner and smoke mode; never a secret. type: string required: true outputs: diff --git a/.github/workflows/_cf-worker-lifecycle.yml b/.github/workflows/_cf-worker-lifecycle.yml index f2c859d..d9a07db 100644 --- a/.github/workflows/_cf-worker-lifecycle.yml +++ b/.github/workflows/_cf-worker-lifecycle.yml @@ -35,7 +35,7 @@ defaults: jobs: lifecycle: - name: Preview lifecycle + name: ledger runs-on: ${{ inputs.runner }} timeout-minutes: 10 concurrency: diff --git a/.github/workflows/_cf-worker-persistent-dev.yml b/.github/workflows/_cf-worker-persistent-dev.yml index 7946fdd..351b1f0 100644 --- a/.github/workflows/_cf-worker-persistent-dev.yml +++ b/.github/workflows/_cf-worker-persistent-dev.yml @@ -58,7 +58,7 @@ defaults: jobs: persistent-dev: - name: Persistent Dev (Hybrid) + name: manual runs-on: ${{ inputs.runner }} timeout-minutes: 45 environment: ${{ inputs.app_name }}/dev diff --git a/.github/workflows/_cf-worker-v2.yml b/.github/workflows/_cf-worker-v2.yml index 48c9306..0e78c5f 100644 --- a/.github/workflows/_cf-worker-v2.yml +++ b/.github/workflows/_cf-worker-v2.yml @@ -7,9 +7,9 @@ name: CF Worker Pipeline v2 # # CI (_cf-worker-ci.yml) Lint | Typecheck | Unit | Integration | Build Validation | E2E in parallel -> Validation Gate (fail closed) # | -# Delivery plan enabled targets, ONE physical D1, the migration owner (pure data; runs only when CI passed) +# Plan enabled targets, ONE physical D1, the migration owner (pure data; runs only when CI passed) # | -# Database Migration ONE job: read-only safety checks first (target build, effective-config audit, exact D1 binding), then +# Migrate ONE job: read-only safety checks first (target build, effective-config audit, exact D1 binding), then # | the migration is applied ONCE. No separate Preflight stage. # Delivery x enabled kinds _cf-worker-delivery.yml, one call per kind: Deploy -> Smoke -> conditional Rollback, independent per kind # | @@ -451,11 +451,11 @@ env: jobs: # ------------------------------------------------------------------------- # CI — ONE call of _cf-worker-ci.yml (six parallel checks + the fail-closed Validation Gate). Credential-free; fork PRs run it, same-repo - # PRs are covered by the push run of the same SHA. Its verdict is the only upstream condition of the Delivery plan. + # PRs are covered by the push run of the same SHA. Its verdict is the only upstream condition of the Plan. # ------------------------------------------------------------------------- # CI — ONE call of _cf-worker-ci.yml: Lint, Typecheck, Unit, Integration, Build Validation and E2E as six independent, parallel jobs, # then the fail-closed Validation Gate. Credential-free; fork PRs run it, same-repo PRs are covered by the push run of the same SHA. - # Its result is the only upstream condition of the Delivery plan. + # Its result is the only upstream condition of the Plan. # ------------------------------------------------------------------------- ci: name: CI @@ -482,12 +482,12 @@ jobs: test_e2e_env: ${{ inputs.test_e2e_env }} # ------------------------------------------------------------------------- - # Delivery plan — pure data, no Cloudflare credentials: the enabled delivery kinds of THIS event, the ONE physical D1 they share (distinct + # Plan — pure data, no Cloudflare credentials: the enabled delivery kinds of THIS event, the ONE physical D1 they share (distinct # databases fail the plan) and its migration owner, the per-kind `on_` switches and target `descriptors`, the canonical lock `group`. # Runs only after a passed CI, so every job below that needs the plan is implicitly gated by CI. A verify call plans from phase_state. # ------------------------------------------------------------------------- plan: - name: Delivery plan + name: Plan needs: [ci] if: always() && (inputs.pipeline_phase == 'verify' || (needs.ci.result == 'success' && needs.ci.outputs.validation == 'passed' && github.ref_type == 'branch' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch'))) runs-on: ${{ inputs.runner }} @@ -533,7 +533,7 @@ jobs: # proceeds) without d1_migrations; never on pull_request, delete or a verify call. The D1 schema is never rolled back automatically. # ========================================================================= migration: - name: Database Migration + name: Migrate needs: [plan] if: always() && inputs.pipeline_phase != 'verify' && inputs.d1_migrations && needs.plan.result == 'success' && needs.plan.outputs.has_migrations == 'true' runs-on: ${{ inputs.runner }} @@ -603,7 +603,7 @@ jobs: # the single Migration job (success, or skipped when nothing is migrated) and is never touched by another kind's Deploy / Smoke / Rollback. # ========================================================================= delivery_production: - name: Delivery (production) + name: production needs: [plan, migration] if: always() && needs.plan.result == 'success' && needs.plan.outputs.on_production == 'true' && (needs.migration.result == 'success' || needs.migration.result == 'skipped') uses: $/.github/workflows/_cf-worker-delivery.yml @@ -630,7 +630,7 @@ jobs: SMOKE_PASSWORD: ${{ secrets.SMOKE_PASSWORD }} delivery_native_branch: - name: Delivery (native-branch) + name: native-branch needs: [plan, migration] if: always() && needs.plan.result == 'success' && needs.plan.outputs.on_native_branch == 'true' && (needs.migration.result == 'success' || needs.migration.result == 'skipped') uses: $/.github/workflows/_cf-worker-delivery.yml @@ -649,7 +649,7 @@ jobs: secrets: *delivery_secrets delivery_native_dev: - name: Delivery (native-dev) + name: native-dev needs: [plan, migration] if: always() && needs.plan.result == 'success' && needs.plan.outputs.on_native_dev == 'true' && (needs.migration.result == 'success' || needs.migration.result == 'skipped') uses: $/.github/workflows/_cf-worker-delivery.yml @@ -668,7 +668,7 @@ jobs: secrets: *delivery_secrets delivery_persistent_dev: - name: Delivery (persistent-dev) + name: persistent-dev needs: [plan, migration] if: always() && needs.plan.result == 'success' && needs.plan.outputs.on_persistent_dev == 'true' && (needs.migration.result == 'success' || needs.migration.result == 'skipped') uses: $/.github/workflows/_cf-worker-delivery.yml @@ -719,7 +719,7 @@ jobs: # on ANY event so a misconfigured caller is visible. (production-only keeps ignoring preview_on_branch_push=true, for compatibility.) # ------------------------------------------------------------------------- config-guard: - name: Config guard + name: Guard if: inputs.pipeline_phase != 'verify' && ((inputs.deployment_strategy == 'production-only' && (inputs.shared_dev_on_branch_push || inputs.shared_dev_native_preview_on_branch_push)) || (inputs.shared_dev_on_branch_push && inputs.shared_dev_native_preview_on_branch_push)) runs-on: ${{ inputs.runner }} timeout-minutes: 2 @@ -733,7 +733,7 @@ jobs: # branch delete -> retire the ledger, the Preview is KEPT) and `persistent-dev` (Hybrid, explicit dispatch on the approved ref only). # ------------------------------------------------------------------------- lifecycle: - name: Preview lifecycle + name: lifecycle if: inputs.pipeline_phase != 'verify' && inputs.deployment_strategy != 'production-only' && ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'delete' && github.event.ref_type == 'branch')) uses: $/.github/workflows/_cf-worker-lifecycle.yml permissions: @@ -747,7 +747,7 @@ jobs: runner: ${{ inputs.runner }} persistent-dev: - name: Persistent Dev (Hybrid) + name: persistent-dev if: inputs.pipeline_phase != 'verify' && github.event_name == 'workflow_dispatch' && inputs.dispatch_target == 'persistent-dev' && inputs.deployment_strategy == 'hybrid' && inputs.persistent_dev_ref != '' && github.ref == inputs.persistent_dev_ref uses: $/.github/workflows/_cf-worker-persistent-dev.yml permissions: diff --git a/actions/cf-deploy/action.yml b/actions/cf-deploy/action.yml index f2b0444..33eca60 100644 --- a/actions/cf-deploy/action.yml +++ b/actions/cf-deploy/action.yml @@ -60,7 +60,7 @@ inputs: description: publish stage only - the Migration job's migration_json output default: '' descriptor: - description: toJSON of this kind's target descriptor (the Delivery plan's validated record). When set, the engine verifies it against target / kind / d1_expected_id and takes the smoke mode from it. + description: toJSON of this kind's target descriptor (the Plan job's validated record). When set, the engine verifies it against target / kind / d1_expected_id and takes the smoke mode from it. default: '' call_inputs_json: description: Deferred verification only - toJSON(inputs) of the VERIFY call, compared with the deploy call's identity inputs (inputs_json) diff --git a/actions/cf-preview/delivery.mjs b/actions/cf-preview/delivery.mjs index 4dc972a..96e402f 100644 --- a/actions/cf-preview/delivery.mjs +++ b/actions/cf-preview/delivery.mjs @@ -218,7 +218,7 @@ export function checkBuildEnvOrigins(buildEnvText, targetUrl) { } // --------------------------------------------------------------------------------------------------------------------- -// Target descriptor: ONE versioned, validated record per enabled kind, produced by the Delivery plan job and consumed by the +// Target descriptor: ONE versioned, validated record per enabled kind, produced by the Plan job and consumed by the // thin per-kind workflow calls (and by every engine stage). It replaces the per-field `with:` copies (engine target, GitHub // Environment, concurrency groups, expected D1, migration owner, smoke policy). Pure data: never a secret, never a URL // (URLs are resolved against Cloudflare before each build), validated at generation AND at every consumer. diff --git a/docs/cf-worker-v2-refactor.md b/docs/cf-worker-v2-refactor.md index 6126167..8b9072a 100644 --- a/docs/cf-worker-v2-refactor.md +++ b/docs/cf-worker-v2-refactor.md @@ -85,7 +85,7 @@ checked read-only (`heaven-monorepo`, `innovation-apps`) this cannot matter — ### Migration (unchanged guarantees) -Exactly one remote-D1 job per pipeline, same order, same Environment protection: `Validation → Plan → Database Migration (resolve D1 · validate bindings · audit effective config · stale-SHA guard · apply once) → Delivery`. +Exactly one remote-D1 job per pipeline, same order, same Environment protection: `Validation → Plan → Migrate (resolve D1 · validate bindings · audit effective config · stale-SHA guard · apply once) → Delivery`. It stays inline in the public workflow: extracting it would add a workflow-call layer and re-plumb the descriptors, the inputs blob and the credentials without removing a stage or weakening anything, which the brief rules out. Preview/Dev still can never reach the Production D1 (plan + engine audit); the shared-D1 group still serialises Workers sharing one D1; distinct physical D1s are still rejected by the plan. @@ -195,7 +195,7 @@ The scratch Environments are unprotected and hold fake D1 ids, so Environment ap No call enables two distinct physical D1s in any modeled event, so the single-D1 rule does not reject any supported configuration. Not modeled: the post-build effective-config audit of Astro/adapter-generated configs (blog Shared Dev is off; it runs only in a live Migration/Deploy). -**Required Checks.** Branch protection and rulesets could not be read for the four private repositories: the API answers `403 Upgrade to GitHub Pro or make this repository public` although the token has repository admin rights (organization plan: `free`; organization rulesets: `Upgrade to GitHub Team`). This is a plan limit, not a permissions gap, and it makes enforcement unlikely, but it is not proof — a rule configured under an earlier plan would be invisible here. Treat them as **UNVERIFIED — ADMIN ACCESS REQUIRED** and have an administrator confirm in the repository settings that none of the following contexts is required (`` is the caller's job name: `Pipeline` for blog, innovation-apps and heaven-www; `Heaven ID`, `Heaven Checkin`, `Heaven ID Test Client` for heaven-monorepo; `cf` for anas-mcp). Required-check names as seen on the consumers' recent default-branch runs ("today") and in the `8ad4d40` scratch runs ("after"): +**Required Checks.** Branch protection and rulesets could not be read for the four private repositories: the API answers `403 Upgrade to GitHub Pro or make this repository public` although the token has repository admin rights (organization plan: `free`; organization rulesets: `Upgrade to GitHub Team`). This is a plan limit, not a permissions gap, and it makes enforcement unlikely, but it is not proof — a rule configured under an earlier plan would be invisible here. Treat them as **UNVERIFIED — ADMIN ACCESS REQUIRED** and have an administrator confirm in the repository settings that none of the following contexts is required (`` is the caller's job name: `Pipeline` for blog, innovation-apps and heaven-www (blog and heaven-www move to `CD`); `Heaven ID`, `Heaven Checkin`, `Heaven ID Test Client` for heaven-monorepo; `cf` for anas-mcp). Required-check names as seen on the consumers' recent default-branch runs ("today") and in the `8ad4d40` scratch runs ("after"): | Today (consumer runs) | After `8ad4d40` | | --- | --- | @@ -206,9 +206,10 @@ No call enables two distinct physical D1s in any modeled event, so the single-D1 | ` / Build Validation` | ` / CI / Build Validation` | | ` / E2E` and ` / E2E (, , )` (blog: four variants) | ` / CI / E2E` and ` / CI / E2E (, , )` | | — | ` / CI / Validation Gate` (new aggregate) | -| ` / Migration` | ` / Database Migration` | -| ` / Deploy`, `Smoke Tests`, `Revert on Verify Failure`, `Shared Dev`, `Shared Dev Smoke Tests`, `Shared Dev Revert on Verify Failure` | ` / Delivery (production\|native-branch\|native-dev\|persistent-dev) / Deploy\|Smoke Tests\|Revert on Verify Failure` | -| ` / Release`, `Config guard`, `Preview lifecycle`, `Persistent Dev (Hybrid)` | unchanged; new ` / Delivery plan` | +| ` / Migration` | ` / Migrate` | +| ` / Deploy`, `Smoke Tests`, `Revert on Verify Failure`, `Shared Dev`, `Shared Dev Smoke Tests`, `Shared Dev Revert on Verify Failure` | ` / / Deploy\|Smoke Tests\|Revert on Verify Failure` (`` = `production\|native-branch\|native-dev\|persistent-dev`) | +| ` / Release` | unchanged | +| ` / Config guard`, `Preview lifecycle`, `Persistent Dev (Hybrid)` | ` / Guard`, ` / lifecycle / ledger`, ` / persistent-dev / manual`; new ` / Plan` | Skipped jobs (unconfigured checks) report as skipped; a required context that is never emitted blocks merging. Nothing was modified in any repository's protection settings. @@ -227,7 +228,7 @@ Skipped jobs (unconfigured checks) report as skipped; a required context that is ### Remaining risks and limitations * **Runner floor 2.336.0** for every runner passed as `runner` (see above); GHES is unsupported by `$/`. Confirmed `2.338.0` on all ten org runners on 2026-11-03. -* **Check names changed** (` / CI / Lint`, ` / Database Migration`, ` / Delivery () / …`): required status checks in other consumers must be updated when they adopt this ref; they are unreadable on four of the five consumers (Free plan), see the table above. +* **Check names changed** (` / CI / Lint`, ` / Migrate`, ` / / …`): required status checks in other consumers must be updated when they adopt this ref; they are unreadable on four of the five consumers (Free plan), see the table above. * `actionlint` cannot lint `$/` yet (two message formats ignored, guarded by the contract test). * A verify call queues under the recorded group before the Plan proves it (see "Concurrency and phase_state"). * The public workflow is 769 lines, above the 600–750 target: the remaining bulk is the 60-input / 20-output public interface (≈ 300 lines), which Phase 1 may not change. diff --git a/docs/cf-worker-v2-rollout.md b/docs/cf-worker-v2-rollout.md index 9e2d9b9..e77417a 100644 --- a/docs/cf-worker-v2-rollout.md +++ b/docs/cf-worker-v2-rollout.md @@ -24,7 +24,7 @@ Status at the time of writing (2026-11-03): **PR #89 is ready to merge; the floa * **Merging runs the real pipelines.** A merge to `main` of either repository runs the Production pipeline through the *current* `v2` tag (deploy, and for blog the D1 migration on the Production D1). heaven-www's `CI/CD` has ended in `startup_failure` since 2026-10-30, and blog's `cicd.yml` has not run since 2026-10-30 (its last runs succeeded), so a merge can publish everything that has accumulated on `main` since then. Merge deliberately, when a Production deploy is acceptable, and watch the run. * The shared `CLOUDFLARE_API_TOKEN` must be allowed to manage Previews (v2 no longer has dedicated Preview credentials). 3. **No `startup_failure` from workflow-call contracts.** Evidence at `f86062d`: scratch replicas of the three fixed callers, built from the PR branches, started their jobs (1 job for Preview GC, 17–19 for the Pipeline calls) against both the v2 tag and the candidate and failed closed only on missing credentials — see "Validation evidence". -4. **Required Checks audited, or the limitation explicitly accepted.** Branch protection/rulesets of blog, heaven-www, heaven-monorepo and innovation-apps cannot be read (Free plan, `403 Upgrade to GitHub Pro`); anas-mcp has none (verified). An administrator must inspect the contexts below. **Recommendation: require only ` / CI / Validation Gate`.** Delivery jobs are skipped as whole jobs on PRs, so a required ` / Delivery (…) / Deploy` would wait forever for a status that is never reported. innovation-apps has two workflows that both name their job `Pipeline`, so a requirement on that name is ambiguous. +4. **Required Checks audited, or the limitation explicitly accepted.** Branch protection/rulesets of blog, heaven-www, heaven-monorepo and innovation-apps cannot be read (Free plan, `403 Upgrade to GitHub Pro`); anas-mcp has none (verified). An administrator must inspect the contexts below. **Recommendation: require only ` / CI / Validation Gate`.** Delivery jobs are skipped as whole jobs on PRs, so a required ` / / Deploy` would wait forever for a status that is never reported. innovation-apps has two workflows that both name their job `Pipeline`, so a requirement on that name is ambiguous. 5. **Runner compatibility** re-checked immediately before promotion (`gh api orgs/firstsun-dev/actions/runners --paginate`), all online runners ≥ `2.336.0`; decide what to do about `heavenweb-runner-10`. 6. **Single-D1 assumptions** re-run if a consumer's `wrangler` config or switches changed (the planner harness used for this audit is described in `docs/cf-worker-v2-refactor.md`). 7. **Non-production deployment validation** completed, or explicitly accepted as a release risk. Today it is **not done**: no approved disposable Cloudflare account exists (only Production-scoped org secrets), so live migrate / publish / smoke / recovery and Environment approval are unproven (see "Not demonstrated"). @@ -32,15 +32,16 @@ Status at the time of writing (2026-11-03): **PR #89 is ready to merge; the floa ### Required-check contexts to inspect -`` is the caller's job display name: `Pipeline` (blog, heaven-www, innovation-apps), `Heaven ID` / `Heaven Checkin` / `Heaven ID Test Client` (heaven-monorepo), `cf` (anas-mcp). +`` is the caller's job display name: `Pipeline` (blog, heaven-www, innovation-apps; blog and heaven-www are being renamed to `CD`), `Heaven ID` / `Heaven Checkin` / `Heaven ID Test Client` (heaven-monorepo), `cf` (anas-mcp). | Old | New | | --- | --- | | ` / Lint`, `Typecheck`, `Unit Tests`, `Integration Tests`, `Build Validation`, `E2E[ (variant)]` | ` / CI / Lint`, `Typecheck`, `Unit Tests`, `Integration Tests`, `Build Validation`, `E2E[ (variant)]` | -| — | ` / CI / Validation Gate`, ` / Delivery plan` | -| ` / Migration` | ` / Database Migration` | -| ` / Deploy`, `Smoke Tests`, `Revert on Verify Failure`, `Shared Dev`, `Shared Dev Smoke Tests`, `Shared Dev Revert on Verify Failure` | ` / Delivery (production\|native-branch\|native-dev\|persistent-dev) / Deploy\|Smoke Tests\|Revert on Verify Failure` | -| `Release`, `Config guard`, `Preview lifecycle`, `Persistent Dev (Hybrid)` | unchanged | +| — | ` / CI / Validation Gate`, ` / Plan` | +| ` / Migration` | ` / Migrate` | +| ` / Deploy`, `Smoke Tests`, `Revert on Verify Failure`, `Shared Dev`, `Shared Dev Smoke Tests`, `Shared Dev Revert on Verify Failure` | ` / / Deploy\|Smoke Tests\|Revert on Verify Failure` (`` = `production\|native-branch\|native-dev\|persistent-dev`) | +| `Release` | unchanged | +| `Config guard`, `Preview lifecycle`, `Persistent Dev (Hybrid)` | `Guard`, `lifecycle / ledger`, `persistent-dev / manual` | ## Procedure diff --git a/docs/cf-worker-v2-unified-delivery.md b/docs/cf-worker-v2-unified-delivery.md index 7a00e1a..593043e 100644 --- a/docs/cf-worker-v2-unified-delivery.md +++ b/docs/cf-worker-v2-unified-delivery.md @@ -18,10 +18,10 @@ CI call (_cf-worker-ci.yml): Lint ‖ Typecheck ‖ Unit ‖ Integration ‖ Bui │ Validation Gate every CONFIGURED job succeeds, every unconfigured one is skipped, else fail closed; output validation=passed │ - Delivery plan enabled kinds + smoke modes + ONE physical D1 (pure data, no credentials) + Plan enabled kinds + smoke modes + ONE physical D1 (pure data, no credentials) │ outputs: on_, descriptors, migration {owner kind, Environment, d1_id, kinds} │ - Database Migration ONE job, Environment of the owner (/prod on main) + Migrate ONE job, Environment of the owner (/prod on main) │ 1. each enabled target that is not the owner: build + effective-config audit + exact D1 binding (nothing remote) │ 2. the owner: build + audit + D1 binding + stale-SHA guard, then the ONE migration │ skipped when d1_migrations is false / verify phase; never rolls schema back diff --git a/scripts/cf-preview/test-jobs.py b/scripts/cf-preview/test-jobs.py index b8b7bf2..bdcfd4f 100644 --- a/scripts/cf-preview/test-jobs.py +++ b/scripts/cf-preview/test-jobs.py @@ -2,9 +2,9 @@ """Scenario groups (explicit `group_*` functions registered in GROUPS): `test-jobs.py [--list] [--group ID]...`; no flag runs all. run-job-groups.py runs each group in its own process, in parallel, against the manifest of expected groups. -Executes the REAL steps of the ONE Database Migration job (pre-mutation validation of every enabled target, then the single +Executes the REAL steps of the ONE Migrate job (pre-mutation validation of every enabled target, then the single migration), the per-kind Delivery calls (the internal reusable workflow _cf-worker-delivery.yml: deploy -> smoke -> rollback), the -Delivery plan, and the persistent-Dev job - including the +Plan, and the persistent-Dev job - including the composite delivery engine (actions/cf-deploy -> actions/cf-preview/engine.sh) - against stub gh/pnpm/curl, and feeds each job's result into the next job's `if:` and `needs.*.outputs`. EVERY job runs in a FRESH workspace (a different runner): anything the chain needs must travel through job outputs, never through local files. A reusable-workflow CALL is emulated like GitHub does: @@ -761,7 +761,7 @@ def group_migration_d1(): # a build COMMAND that writes TARGET/DEV_ENV into GITHUB_ENV still cannot redirect the migration: the engine binds the target m = deploy(FEAT, inputs=dict(D1, build_cmd='echo TARGET=prod >> "$GITHUB_ENV"; echo DEV_ENV=prod >> "$GITHUB_ENV"; echo D1_BINDING=NOPE >> "$GITHUB_ENV"')) check(m["result"] == "success" and "PREV-ID" in mig_file(m, "preview") and not (m["ws"] / "w" / ".wrangler" / "migrate.prod.json").exists(), "a build writing TARGET=prod into GITHUB_ENV cannot move the Preview migration to Production") - # ---- ONE Database Migration job: the required scenarios (PR #89 simplification), each as an explicit check + # ---- ONE Migrate job: the required scenarios (PR #89 simplification), each as an explicit check e200 = {"STUB_PROBES": "200 "} m = deploy(MAIN, inputs=D1, env=e200) mo = public_outputs(m, D1, MAIN) diff --git a/scripts/cf-preview/test-workflow-contract.py b/scripts/cf-preview/test-workflow-contract.py index f69b2ff..e02cee6 100755 --- a/scripts/cf-preview/test-workflow-contract.py +++ b/scripts/cf-preview/test-workflow-contract.py @@ -105,16 +105,24 @@ def vres(**over): KK = lambda k: k.replace("-", "_") CALL_JOBS = {f"delivery_{KK(k)}" for k in KINDS} check(set(jobs) == {"ci", "plan", "migration"} | CALL_JOBS | {"release", "config-guard", "lifecycle", "persistent-dev"}, - "public job set: CI call, Delivery plan, ONE Database Migration job, one Delivery CALL per kind (reusable workflow), Release + config-guard / lifecycle / persistent-dev handlers") + "public job set: CI call, Plan, ONE Migrate job, one Delivery CALL per kind (reusable workflow), Release + config-guard / lifecycle / persistent-dev handlers") check(set(cjobs) == set(VALIDATION) | {"gate"}, "the CI workflow has exactly the six validation jobs and the Validation Gate") check(set(djobs) == {"deploy", "smoke", "rollback"}, "the internal delivery workflow has exactly: deploy, smoke, rollback (ONE target per call; no Preflight / Migration path)") check(set(ljobs) == {"lifecycle"} and set(pjobs) == {"persistent-dev"}, "the event handlers are one small workflow each (Preview lifecycle, manual persistent Dev)") for gone in ("dev-deploy", "dev-verify", "dev-rollback", "test", "prepare", "migrate", "test-gate", "preview", "production", "verify", "revert", "deploy", "smoke", "rollback", "preflight", "gate") + tuple(VALIDATION) + tuple(f"{s_}_{KK(k)}" for s_ in ("preflight", "migration") for k in KINDS): check(gone not in jobs, f"obsolete / matrix / inlined job '{gone}' is gone from the public workflow (validation lives in _cf-worker-ci.yml; no matrix output merging, no Preflight stage, no per-kind Migration call)") -check(sum(1 for j in jobs.values() if j.get("name") == "Database Migration") == 1 and len(re.findall(r"^ migration[\w_-]*:", text_all, re.M)) == 1, "exactly ONE Migration job definition exists in the public pipeline") +check(sum(1 for j in jobs.values() if j.get("name") == "Migrate") == 1 and len(re.findall(r"^ migration[\w_-]*:", text_all, re.M)) == 1, "exactly ONE Migration job definition exists in the public pipeline") check(sum(len(re.findall(r"^ (?:migration|preflight)[\w_-]*:", open(f).read(), re.M)) for f in (CWF, DWF, LWF, PWF)) == 0, "no other workflow of the pipeline defines a Migration or Preflight job") check([cjobs[j]["name"] for j in VALIDATION] == [JOB_NAME[j] for j in VALIDATION], "validation jobs are named Lint, Typecheck, Unit Tests, Integration Tests, Build Validation, E2E") -check(cjobs["gate"]["name"] == "Validation Gate" and jobs["ci"]["name"] == "CI" and jobs["plan"]["name"] == "Delivery plan" and jobs["migration"]["name"] == "Database Migration", "CI, Validation Gate, Delivery plan and Database Migration are named jobs") +check(cjobs["gate"]["name"] == "Validation Gate" and jobs["ci"]["name"] == "CI" and jobs["plan"]["name"] == "Plan" and jobs["migration"]["name"] == "Migrate", "CI, Validation Gate, Plan and Migrate are named jobs") +# Displayed check = ` / ...`; consumers call this workflow from a job named `CD`, so names carry no repeated words. +check({k: jobs["delivery_" + KK(k)]["name"] for k in KINDS} == {k: k for k in KINDS}, "check names: each Delivery call is named just its kind (production / native-branch / native-dev / persistent-dev)") +check([djobs[j]["name"] for j in ("deploy", "smoke", "rollback")] == ["Deploy", "Smoke Tests", "Revert on Verify Failure"], "check names: inner delivery jobs stay Deploy / Smoke Tests / Revert on Verify Failure") +check((jobs["config-guard"]["name"], jobs["lifecycle"]["name"], jobs["persistent-dev"]["name"], jobs["release"]["name"]) == ("Guard", "lifecycle", "persistent-dev", "Release"), "check names: Guard, lifecycle, persistent-dev, Release") +check((ljobs["lifecycle"]["name"], pjobs["persistent-dev"]["name"]) == ("ledger", "manual"), "check names: handler inner jobs (ledger, manual) never repeat their caller's name") +_names = [j["name"] for j in jobs.values()] +check(sorted(n for n in set(_names) if _names.count(n) > 1) == ["persistent-dev"], "check names: public job names are unique except persistent-dev (auto Delivery kind vs manual dispatch handler, never on the same event)") +check(not any(re.search(r"Delivery \(|Preview lifecycle|Database Migration|Config guard|Persistent Dev \(Hybrid\)|Delivery plan", str(j.get("name", ""))) for d in (jobs, cjobs, djobs, ljobs, pjobs) for j in d.values()), "check names: none of the old verbose names remain in any v2 workflow") check(jobs["ci"]["uses"] == "$/.github/workflows/_cf-worker-ci.yml", "CI is one STATIC `uses: $/.github/workflows/_cf-worker-ci.yml` call (this repository at the running commit)") check("strategy" not in jobs["ci"], "no matrix on the CI call (E2E variants are the only matrix, inside the CI workflow)") @@ -127,8 +135,8 @@ def needs_of(name, js=None): for j in VALIDATION: check(needs_of(j, cjobs) == [], f"{j}: no needs (independent and parallel with the other five checks - no accidental serial dependency)") check(set(needs_of("gate", cjobs)) == set(VALIDATION), "Validation Gate needs exactly the six validation jobs") -check(needs_of("ci") == [] and needs_of("plan") == ["ci"], "CI has no needs; the Delivery plan needs only the CI call") -check(needs_of("migration") == ["plan"], "Database Migration needs exactly the plan (which itself requires a passed CI) - and NO Deploy / Smoke / Rollback") +check(needs_of("ci") == [] and needs_of("plan") == ["ci"], "CI has no needs; the Plan needs only the CI call") +check(needs_of("migration") == ["plan"], "Migrate needs exactly the plan (which itself requires a passed CI) - and NO Deploy / Smoke / Rollback") for k in KINDS: check(set(needs_of(f"delivery_{KK(k)}")) == {"plan", "migration"}, f"delivery_{KK(k)}: the plan and THE migration only") check(not any(n.startswith("delivery_") for k in KINDS for n in needs_of(f"delivery_{KK(k)}")), "P2-A: NO Delivery call waits for another kind's Deploy / Smoke / Rollback")