Skip to content

Commit cdec7b8

Browse files
refactor(release): simplify desktop distribution channels (#10163)
## Summary Simplifies Omi release distribution while closing the executable draft-PR blockers. The branch is rebased onto current `origin/main` and now includes the independently reviewed memory chronology fix that was blocking the broad backend CI lane. Beta and Stable remain two desktop distribution pointers over one immutable qualified manifest. The generalized release-record/ring system, Beta backend fleet/routing, nomination, emergency promotion, holds/receipts, and qualification-claim state are not restored. ## Trust, deployment, and routing changes - Qualification records exact Stable/Beta URLs, hashes, and Sparkle signatures for all four files; Beta and Stable promotion bind a successful trusted candidate-tag qualification run at the exact release-source SHA, never rebuild, and reject mutable release drift before pointer movement. - Evidence labels are explicit: T2/fault coverage is source-built named-bundle evidence at the frozen tag, while exact signed ZIP/DMG smoke is signing/notarization/package/storage proof. Required operational evidence remains signed `.89` upgrade, `.70` side-by-side, core journeys, soak, and same-manifest Stable promotion. - Stable controls are pinned to `main`, use actual access-token expansion, retain exact lost-response pointer idempotency, accept retained qualified repoints, and verify exactly one `identity=stable` XML item with immutable version/build/enclosure/signature facts. - `environment=prod, deploy_targets=all` rejects before checkout, cloud authentication, image publication, or mutation because transactional GKE/config rollback parity does not exist. - `environment=prod, deploy_targets=cloud-run-only` is intentionally narrow: exact no-traffic tag, VPC Cloud Run Job known-audio gate with Cloud Run IAM plus Firebase auth, traffic snapshot/restore, and unconditional cleanup of the tag, Job, temporary invoker grant, and temporary service account. It performs no GKE mutation. - Source-admitted direct GKE workflows (`gcp_backend_listen_helm.yml`, pusher, llm-gateway, agent-proxy) remain their respective production paths and report checked-out HEAD identity with fresh-`origin/main` ancestry diagnostics. - Production-family Codemagic workflows pin `https://api.omi.me/`; the startup function called by `main.dart` validates the production API and agent WSS authority. It rejects old Beta/dev/staging/arbitrary overrides while development remains configurable. - Node-20 artifact-action majors are replaced with repository-approved Node-24 majors. Resolver cache fixtures cover validated primary and LKG records under the expanded immutable Beta manifest contract. - Required-memory processing no longer owns or extends short-term TTL. The authoritative apply owner preserves persisted capture/corroboration expiry and keeps `updated_at` monotonic under clock skew; receipt chronology cannot predate capture or grant freshness. ## Product invariants affected - INV-AUTH-1 - INV-INT-1 - INV-MEM-1 Failure-Class: FC-nonrecoverable-promotion ## Verification - Combined rebased exact head: deterministic local PR preflight passed all 49 selected checks. - Combined backend selection: memory lifecycle 28 passed, authoritative memory apply store 7 passed, and `test_sync_v2.py` 163 passed. - Focused desktop release/update/probe/vector suite: 62 passed. - Memory lifecycle exact file also passed three consecutive times before folding; adjacent apply/lifecycle/model suites: 100 passed. - Deployment concurrency, direct-writer and mobile-routing mutation contracts: passed. - `actionlint` for changed release/deploy workflows and `git diff --check`: passed. - Flutter environment tests: 7 passed; targeted analysis had only the existing non-blocking `library_private_types_in_public_api` information message. - Swift debug build and `APIClientRoutingTests`: 60 passed. ## Qualification boundary Source-built named-bundle T2/fault evidence and exact signed-artifact evidence are separate layers. Signed production binaries do not expose a local automation/fault interface. Before Stable promotion, the exact signed Beta artifact still requires `.89` upgrade, `.70` side-by-side operation, real core journeys, recovery checks, and soak. Stable advances the same immutable qualified manifest without rebuilding. ## Non-executed live operations No cloud resources, releases, pointers, production app bundles, secrets, deployments, or workflows were changed or dispatched.
2 parents 9dbd226 + 914bdd3 commit cdec7b8

131 files changed

Lines changed: 2701 additions & 6623 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/AGENTS.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ These rules apply to GitHub Actions workflows and custom actions under `.github/
1111
- Deployment group names are a cross-workflow API. Keep them aligned with
1212
`.github/scripts/check-deployment-concurrency.py`; use
1313
`cancel-in-progress: false` so a newer run cannot interrupt a remote mutation
14-
or a staged validation/traffic promotion. `desktop_emergency_promote_beta.yml`
15-
is the narrow exception: GitHub's single replaceable pending slot cannot
16-
preserve a break-glass request, so each attempt has an isolated lock and the
17-
server-side exact pointer CAS is the cross-lane arbiter; retain its focused
18-
regression contract.
14+
or a staged validation/traffic promotion.
1915
- `deploy-backend-stack-<environment>` intentionally covers the four backend
2016
Cloud Run services, traffic repair, backend-listen, LLM gateway, and
2117
backend-secrets. Those paths share mutable releases, while unrelated services

.github/CODEOWNERS

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# Release-ring deployment code is production authority. Branch protection must
2-
# require this owner plus the release-ring guard check before merging changes.
1+
# Broad production authority remains protected after the retired release-ring deletion.
32
/.github/workflows/** @Git-on-my-level
4-
/.github/scripts/check_release_rings.py @Git-on-my-level
5-
/backend/scripts/release_rings.py @Git-on-my-level
6-
/backend/scripts/render_release_ring_config.py @Git-on-my-level
73
/backend/charts/backend-secrets/** @Git-on-my-level

.github/checks-manifest.yaml

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@ checks:
1414
triggers: ["all"]
1515
lanes: ["local", "ci"]
1616
reason: "manifest validity and workflow drift guard"
17-
- id: release-ring-guards
18-
command: ["python3", ".github/scripts/check_release_rings.py"]
19-
triggers: [".github/workflows/release-record.yml", ".github/workflows/deploy-release-ring.yml", ".github/scripts/check_release_rings.py", ".github/scripts/check-deployment-concurrency.py", "backend/scripts/release_rings.py", "backend/scripts/render_release_ring_config.py", "backend/deploy/release_rings.yaml"]
20-
lanes: ["local", "ci"]
21-
reason: "#10049 keeps release records immutable, deploy inputs record-only, and recovery fail-closed"
22-
- id: release-ring-guard-tests
23-
command: ["python3", ".github/scripts/test_check_release_rings.py"]
24-
triggers: [".github/scripts/check_release_rings.py", ".github/scripts/test_check_release_rings.py", ".github/workflows/release-record.yml", ".github/workflows/deploy-release-ring.yml", ".github/checks-manifest.yaml"]
25-
lanes: ["local", "ci"]
26-
reason: "release-ring structural guard must reject missing immutability, restore, and OIDC controls"
2717
- id: product-file-line-count-ratchet
2818
command: ["python3", ".github/scripts/check_product_file_line_count_ratchet.py", "--changed-files", "{changed_files}", "--base", "{base}"]
2919
triggers: ["backend/**/*.py", "desktop/macos/**/*.swift", "desktop/macos/**/*.rs", ".github/scripts/check_product_file_line_count_ratchet.py", ".github/scripts/product_file_line_count_ratchet_baseline/**/*.json"]
@@ -44,11 +34,6 @@ checks:
4434
triggers: [".github/schemas/desktop-release-evidence-v1.schema.json", ".github/scripts/desktop_release_doctor.py", ".github/scripts/desktop_release_doctor_report.py", ".github/scripts/test_desktop_release_doctor.py", ".github/workflows/desktop_release_doctor.yml"]
4535
lanes: ["local", "ci"]
4636
reason: "advisory desktop release evidence and drift report"
47-
- id: desktop-qualification-dispatch
48-
command: ["python3", ".github/scripts/test_desktop_qualification_dispatch.py"]
49-
triggers: [".github/scripts/desktop_qualification_dispatch.py", ".github/scripts/test_desktop_qualification_dispatch.py", ".github/workflows/desktop_qualify_beta.yml", "codemagic.yaml", ".github/checks-manifest.yaml"]
50-
lanes: ["local", "ci"]
51-
reason: "#10080 timeout retries must expose state and permit only one trusted qualification execution per candidate dispatch key"
5237
- id: desktop-changelog-io-tests
5338
command: ["python3", ".github/scripts/test_desktop_changelog.py"]
5439
triggers: [".github/scripts/desktop-changelog.py", ".github/scripts/test_desktop_changelog.py", ".github/checks-manifest.yaml"]
@@ -355,16 +340,47 @@ checks:
355340
triggers: [".github/workflows/gcp_backend.yml", ".github/workflows/gcp_backend_auto_dev.yml", ".github/scripts/verify_backend_release_admission.py", ".github/scripts/verify_auto_backend_release_admission.py", ".github/scripts/check_backend_deploy_source_admission.py", ".github/scripts/test_check_backend_deploy_source_admission.py", ".github/checks-manifest.yaml"]
356341
lanes: ["local", "ci"]
357342
reason: "#9991 mutation-tests workflow-run event, result, branch, SHA, repository, and manual proof drift"
343+
- id: gcp-backend-production-boundary
344+
command: ["python3", ".github/scripts/check-gcp-backend-production-boundary.py"]
345+
triggers: [".github/workflows/gcp_backend.yml", "backend/scripts/probe-transcription-candidate-from-cloud-run.sh", "backend/tests/unit/test_probe_transcription_candidate_from_cloud_run.py", ".github/scripts/check-gcp-backend-production-boundary.py", ".github/scripts/test_check_gcp_backend_production_boundary.py", ".github/checks-manifest.yaml"]
346+
lanes: ["local", "ci"]
347+
reason: "#10163 locks the supported production Cloud Run-only boundary and canonical tagged-candidate IAM audience without introducing another deployment control plane"
348+
- id: gcp-backend-production-boundary-fixtures
349+
command: ["python3", ".github/scripts/test_check_gcp_backend_production_boundary.py"]
350+
triggers: [".github/workflows/gcp_backend.yml", ".github/scripts/check-gcp-backend-production-boundary.py", ".github/scripts/test_check_gcp_backend_production_boundary.py", ".github/checks-manifest.yaml"]
351+
lanes: ["local", "ci"]
352+
reason: "#10163 mutation-tests prod/all rejection, canonical IAM audience, and tagged request-target drift"
353+
- id: direct-backend-production-admission
354+
command: ["python3", ".github/scripts/check-direct-backend-production-admission.py"]
355+
triggers: [".github/workflows/gcp_backend_agent_proxy.yml", ".github/workflows/gcp_backend_listen_helm.yml", ".github/workflows/gcp_backend_pusher.yml", ".github/workflows/gcp_llm_gateway.yml", ".github/scripts/check-direct-backend-production-admission.py", ".github/scripts/test_check_direct_backend_production_admission.py", ".github/checks-manifest.yaml"]
356+
lanes: ["local", "ci"]
357+
reason: "#10163 prevents direct production writers from deploying caller refs or mislabeling checked-out source"
358+
- id: direct-backend-production-admission-fixtures
359+
command: ["python3", ".github/scripts/test_check_direct_backend_production_admission.py"]
360+
triggers: [".github/workflows/gcp_backend_agent_proxy.yml", ".github/workflows/gcp_backend_listen_helm.yml", ".github/workflows/gcp_backend_pusher.yml", ".github/workflows/gcp_llm_gateway.yml", ".github/scripts/check-direct-backend-production-admission.py", ".github/scripts/test_check_direct_backend_production_admission.py", ".github/checks-manifest.yaml"]
361+
lanes: ["local", "ci"]
362+
reason: "#10163 mutation-tests branch bypass and GITHUB_SHA image mislabeling"
363+
- id: mobile-production-routing
364+
command: ["python3", ".github/scripts/test_check_mobile_production_routing.py"]
365+
triggers: ["codemagic.yaml", ".github/scripts/check-mobile-production-routing.py", ".github/scripts/test_check_mobile_production_routing.py", ".github/checks-manifest.yaml"]
366+
lanes: ["local", "ci"]
367+
reason: "#10163 mutation-tests production-family mobile API routing"
368+
- id: stable-pointer-precondition-cli
369+
command: ["python3", ".github/scripts/check_stable_pointer_precondition.py", "--help"]
370+
triggers: [".github/workflows/desktop_promote_prod.yml", ".github/scripts/check_stable_pointer_precondition.py", ".github/scripts/verify_stable_appcast.py", ".github/scripts/test_stable_promotion_verifiers.py", ".github/checks-manifest.yaml"]
371+
lanes: ["local", "ci"]
372+
reason: "#10163 keeps Stable retry acknowledgement and appcast verification executable"
373+
- id: stable-pointer-precondition-fixtures
374+
command: ["python3", ".github/scripts/test_stable_promotion_verifiers.py"]
375+
triggers: [".github/workflows/desktop_promote_prod.yml", ".github/scripts/check_stable_pointer_precondition.py", ".github/scripts/verify_stable_appcast.py", ".github/scripts/test_stable_promotion_verifiers.py", ".github/checks-manifest.yaml"]
376+
lanes: ["local", "ci"]
377+
reason: "#10163 mutation-sensitive Stable retry and default-channel feed fixtures"
358378

359379
exempt:
360380
- path: ".github/scripts/check-desktop-auto-beta-candidate.py"
361381
reason: "release pipeline check requires published candidate artifacts and workflow inputs"
362382
- path: ".github/scripts/check-desktop-prod-promotion-policy.py"
363383
reason: "release-policy full-tree check, not diff-scoped"
364-
- path: ".github/scripts/check-desktop-release-promotion.py"
365-
reason: "release pipeline check with workflow inputs"
366-
- path: ".github/scripts/check-desktop-release-terminology.py"
367-
reason: "release-policy full-tree check, not diff-scoped"
368384
- path: ".github/scripts/check-release-process-guards.py"
369385
reason: "release-policy full-tree check, not diff-scoped"
370386
- path: ".github/scripts/check-deployment-concurrency.py"

.github/schemas/desktop-release-manifest-v1.schema.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"dmg_url",
2020
"dmg_sha256",
2121
"ed_signature",
22+
"beta_zip_url",
23+
"beta_zip_sha256",
24+
"beta_dmg_url",
25+
"beta_dmg_sha256",
26+
"beta_ed_signature",
2227
"qualification_evidence_asset",
2328
"qualification_evidence_sha256",
2429
"qualification_tier",
@@ -46,6 +51,11 @@
4651
"dmg_url": { "$ref": "#/$defs/dmgUrl" },
4752
"dmg_sha256": { "$ref": "#/$defs/sha256" },
4853
"ed_signature": { "type": "string", "pattern": ".*\\S.*" },
54+
"beta_zip_url": { "$ref": "#/$defs/betaZipUrl" },
55+
"beta_zip_sha256": { "$ref": "#/$defs/sha256" },
56+
"beta_dmg_url": { "$ref": "#/$defs/betaDmgUrl" },
57+
"beta_dmg_sha256": { "$ref": "#/$defs/sha256" },
58+
"beta_ed_signature": { "type": "string", "pattern": ".*\\S.*" },
4959
"qualification_evidence_asset": {
5060
"type": "string",
5161
"pattern": "^qualification-evidence-[^/]+\\.json$"
@@ -157,6 +167,14 @@
157167
"dmgUrl": {
158168
"type": "string",
159169
"pattern": "^https://github\\.com/BasedHardware/omi/releases/download/v[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:%2[Bb]|\\+)[1-9][0-9]*-macos/omi\\.dmg$"
170+
},
171+
"betaZipUrl": {
172+
"type": "string",
173+
"pattern": "^https://github\\.com/BasedHardware/omi/releases/download/v[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:%2[Bb]|\\+)[1-9][0-9]*-macos/Omi\\.Beta\\.zip$"
174+
},
175+
"betaDmgUrl": {
176+
"type": "string",
177+
"pattern": "^https://github\\.com/BasedHardware/omi/releases/download/v[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:%2[Bb]|\\+)[1-9][0-9]*-macos/omi-beta\\.dmg$"
160178
}
161179
}
162180
}

.github/scripts/check-deployment-concurrency.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ class LockContract:
3737
# new deploy writer cannot silently bypass the audited lock graph.
3838
LOCK_CONTRACTS = {
3939
"desktop_backend_auto_dev.yml": LockContract("desktop-backend-auto-dev"),
40-
"desktop_promote_prod.yml": LockContract("desktop-backend-promote-prod"),
41-
"deploy-release-ring.yml": LockContract("deploy-release-ring-${{ inputs.ring }}"),
4240
"gcp_admin.yml": LockContract(
4341
"deploy-cloud-run-omi-admin-dashboard-${{ github.ref == 'refs/heads/development' && 'development' || github.ref == 'refs/heads/main' && 'prod' || format('nondeploy-{0}', github.run_id) }}"
4442
),
@@ -91,9 +89,7 @@ class LockContract:
9189
# deployable public values. It never runs Helm, kubectl apply, or a Cloud Run
9290
# mutation; classifying credential acquisition alone as a writer would hide
9391
# that distinction and force a meaningless deploy lock.
94-
READ_ONLY_WORKFLOW_EXEMPTIONS = {
95-
"release-record.yml": 'kubectl -n "${ring}-omi-backend" get configmap "${ring}-omi-backend-config" -o json',
96-
}
92+
READ_ONLY_WORKFLOW_EXEMPTIONS = {}
9793

9894
# Firestore index creation is a schema migration, not ordinary deploy work.
9995
# Keep a single auditable writer so backend readiness can stay read-only.
@@ -248,8 +244,9 @@ def validate_serving_release_vector(name: str, text: str) -> list[str]:
248244
errors.append(f"{name}: release-vector verification must use the canonical verifier")
249245
if "--environment" not in verifier_text:
250246
errors.append(f"{name}: release-vector verification must bind an environment")
251-
if name == "gcp_backend.yml" and "github.event.inputs.deploy_targets == 'all'" not in verifier_text:
252-
errors.append(f"{name}: all-tier release-vector verification must not run for cloud-run-only deploys")
247+
if name == "gcp_backend.yml":
248+
if "github.event.inputs.deploy_targets" not in verifier_text or "--cloud-run-only" not in verifier_text:
249+
errors.append(f"{name}: cloud-run-only promotion must use the Cloud Run-only release-vector contract")
253250
return errors
254251

255252

.github/scripts/check-desktop-auto-beta-candidate.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,6 @@ def validate(args: argparse.Namespace) -> dict:
131131
metadata = parse_metadata(release.get("body") or "")
132132
if metadata.get("channel") != "candidate" or metadata.get("isLive", "").lower() not in {"false", "0", "no"}:
133133
fail("automatic beta qualification requires channel: candidate and isLive: false")
134-
if metadata.get("stableCandidate", "").lower() in {"true", "1", "yes"}:
135-
fail("automatic beta qualification refuses a release already nominated for stable")
136-
137134
expected_version = match.group("version")
138135
expected_build = match.group("build")
139136
checks = _validate_smoke_contract(

0 commit comments

Comments
 (0)