Skip to content

Read before changing

  1. This page · 2. Process (G0–G5 and the pre-promotion checklist) · 3. supabase/docs/PROMOTION_RUNBOOK.md · 4. HLD · LLD · Production state. The machine contract is this page's context: frontmatter; .claude/rules/domains/release.md is GENERATED from it — for this domain an edit is refused until these pages were opened. Gates: check:release · check:version · check:ef-drift · check:env-drift · check:env:prod; the /promote-to-prod skill runs the checklist. Your plan must answer: which change records the change touches; which of the 17 change classes it falls in and whether a script can see that class; what the live probe is for the ones no script sees. Related domains: tools-and-gates · load-bearing-files · edge-functions. Machine reads (the manifest's reads, in order): documentation/portal/releases/index.md · documentation/portal/releases/process.md · supabase/docs/PROMOTION_RUNBOOK.md.

Releases ​

The single source of truth for release management. Every production change — schema, migrations, policies, functions, config, storage, scheduled jobs, secrets, app builds, store submissions — is planned, documented, reviewed, approved and tracked here before it is deployed.

PageWhat it is for
HLDArchitecture, governance, roles, gates, integrations
LLDData model, state machine, automation, audit, notifications
ProcessHow to actually run a release, day to day
Production stateWhat is live on each surface right now

Release register ​

VersionTypeStatusTargetFrozenDeployedClosed
26.0.1train_biweekly🔵 scoped (G0 passed 2026-08-03)2026-08-152026-08-12——

Status legend: 🔵 pre-approval · 🟡 in flight / partially deployed · 🟢 closed · 🔴 rolled back

This row is pulled from release.json, not typed by hand — if it ever disagrees with that file, the file is right and this row is stale. That is precisely the failure this page had until 2026-08-03: it still said train_monthly / 🔵 draft / frozen 2026-08-11 after release.json had already moved to train_biweekly / scoped / 2026-08-12.

Roadmap ​

Only 26.0.1 has passed G0. Everything past it is sequencing intent, not committed scope.

Each later release gets its own G0 pass, its own scope[] referencing existing tracker ids, and its own folder — created when that release actually starts, per Starting a release below. This table exists so the cross-release sequence lives in the repo instead of only in an external planning document, which is the gap that prompted this section to be written at all. Dates beyond 26.0.1 are targets, not commitments, and will move if 26.0.1's pace does.

ReleaseTargetStatusFocusDepends on
26.0.12026-08-15 (payments dark) → 2026-08-20 activation🔵 scopedThe complete vendor money loop: Setu Card, catalog, Razorpay Route payments. Deploys once, activates payments via kill switch — no second deploy—
26.0.2~2026-09-03not startedStub retirements if they slipped (QRS-323–326) · Direct Seller + Real Estate verticals · per-merchant OG image · QR display26.0.1 catalog + card
26.1.0~2026-09-17not startedPayments operations — vendor payments dashboard, automated payout onboarding, refunds + disputes in code (manual-with-a-runbook in 26.0.1)26.0.1 money model + webhook kit
26.2.0~2026-10-01not startedAdmin panel + RBAC (ADR-0006, 0% built today) · feature flags (QRS-296)26.1.0 operational visibility
26.3.0~2026-10-15not startedDesktop DOM merchant layout (ADR-0011 amendment) + org seats26.2.0 admin, so seats have somewhere to be managed
26.4.0~2026-10-29not startedLeads + merged timeline + contacts · the remaining 6 launch verticals26.0.1 discovery-doc pattern
26.5.0~2026-11-12not startedChat + reorder-cycle tracking (the direct-seller differentiator — MLM compensation resets monthly)26.4.0 leads primitive

Why this order, not schedule padding:

  • Payments operations (26.1.0) follows the money loop, not the other way round. Automating KYC and moving refunds into code both need a live, observed payments path to automate against — building the operations layer first would mean automating a process that doesn't exist yet.
  • Admin + RBAC (26.2.0) is sequenced before desktop (26.3.0) on purpose. A payments product with no operational visibility is a support catastrophe, and org seats need an admin surface to manage them from — building the desktop layout first would have nothing to manage.
  • Desktop is deliberately not in 26.0.1. R1 merchants (festival stalls, boutiques, electricians) are phone-first; a DOM merchant UI bought nothing in August and would have cost a fortnight against a schedule with zero slack.
  • Leads/Chat (26.4.0/26.5.0) reuse the discovery-doc and reminders infrastructure built for the first three releases rather than inventing new primitives — see ADR-0009's proportionality rule (full discovery for the first domain in an archetype, a delta doc for siblings).

Metrics ​

Derived from the release records themselves, so there is nothing separate to keep up to date. Populated at G5; a release in flight contributes nothing yet.

MetricValueNotes
Deployment frequency—releases reaching deployed per month
Lead time—G0 → deployed
Change failure rate—rolled_back ÷ deployed
MTTR—rollback trigger → resolved
Scope stability—items at G1 ÷ items at G0
Gate waiver rate—waived gates ÷ total gates
Store approval latency—submitted → live, per platform
Resubmissions—per release, per platform
Open surface divergences0and their age

These are objective. They sit alongside the delivery log's self-reported narrative under that page's own rule: where the two disagree, the numbers win.

Rules that are not negotiable ​

  1. No production change without a Change Record. deploy-prod refuses to apply a migration or Edge Function that is not declared in the active release's release.json.
  2. release.json is the machine source of truth. Markdown carries narrative keyed by id, and npm run check:release asserts the two agree in both directions.
  3. Approvals bind to a commit SHA and a manifest hash. Change the manifest and the approval is void.
  4. A release is not atomic. Per-surface targets[]; deployed is derived, never asserted.
  5. All three surfaces ship in sync — Android native, iOS native, Web PWA. There is no exception path.
  6. Irreversible changes carry a forward fix, not a fictional rollback plan.

Starting a release ​

bash
cp -r documentation/portal/releases/_template documentation/portal/releases/<version>
# fill release.json: version, type, cadence, base_ref
npm run check:release

_template/ is the canonical skeleton — copy it, never improvise a folder. The leading underscore marks it as a non-content directory (same convention as backend/edge-functions/_template.md), and the gate skips it.

Release management — the operating-manual statement ​

Provenance — moved from CLAUDE.md on 2026-09-23 (QRS-1288)

This is the verbatim text of CLAUDE.md § "Release management" as of commit 00c1eca, relocated here under the context-architecture programme. Sentences of the form "this said X until [date]" are corrections recorded at the time they were made; the live rule is the corrected one. Retired vocabulary inside those corrections names what was retired and is not a live claim.

Release management [ENFORCED — non-negotiable, QRS-288] ​

documentation/portal/releases/ is the single source of truth for every production change. Nothing reaches Prod that is not planned, documented, reviewed, approved and tracked there first. Full design: portal HLD · LLD · Process.

The one idea the whole system rests on: release documentation is LOAD-BEARING. deploy-prod.yml reads release.json and refuses to deploy a production change that is not declared in it. Docs stop being a chore done afterwards and become the thing without which the deploy does not run. That inversion exists because this repo has a measured ~0 completion rate on deferred reconciliation (QRS-180), a lint gate that once passed as a green no-op (QRS-013), and a SonarQube standard documented for months and never implemented (QRS-246). A process step that isn't gated does not exist.

  • release.json is the machine SSOT. Markdown carries narrative keyed by id; check:release asserts the two agree in both directions. Never record the same fact twice — that is the bug class behind QRS-249/284/287.
  • Scope items are existing QRS-### ids. Releases never mint their own; the gate verifies each exists in tracker.md.
  • Six gates, G0-G5, each with entry criteria and an exit artifact. G4 approval binds to a commit SHA + a manifest hash — edit the manifest afterwards and the approval is void. There is one maintainer and no required-reviewers on a private Free repo, so this is a guarded self-dispatch, never peer review; do not describe it otherwise in any release note or audit answer.
  • A release is NOT atomic. Backend and web are push; the stores are submit-and-wait and can reject. Status is derived from per-surface targets[], and partially_deployed is a normal state.
  • A release is not a Build. Every store resubmission needs a new build number, so Android and iOS legitimately diverge (26.0.1 → android 26000100, ios 26000101) with the same user-visible version. A "no functional delta" claim is proven by comparing commit SHAs, never asserted.
  • Contraction is bounded by the oldest live app build. Any DROP/narrowing/EF-signature change declares requires_min_app_build; the gate fails if it exceeds the floor. This is the enforceable form of "multiple app versions are live, so expand-contract is structural".
  • Never raise a platform's min_supported_version_code above its live build — it bricks every user on that platform. The gate cross-checks against releases/production-state.md.
  • Irreversible changes carry a forward_fix, not a rollback section. A rollback plan for a burned versionCode or a completed data migration is fiction and produces false confidence.
  • Nine of the 17 change classes are invisible to every automated gate — ef_secret, storage, cron_job, auth_setting, cloudflare, third_party, and all three app classes. Every auth incident in this project came from one of them (QRS-261/273/276/285). Their evidence must be a live functional probe, never a claim that a value was set: QRS-273 was closed on such a claim and the auth log later proved sign-in had never once worked.
  • Ad-hoc scope is decided by objective triggers, not judgement — see the process page. Zero triggers ⇒ accept without ceremony; a process that treats a copy fix like a schema change gets bypassed for both. After scope freeze, only P0/P1 defects in accepted scope.
  • Gate runs in pre-push and in release-gate.yml (path-filtered to supabase/** + releases/**; deliberately NOT in ci.yml, whose paths-ignore excludes exactly those paths).