CRUMB a card from devarno-cloud

Launch Gates (gates/v1) and the Demo Tenant

smo1 intermediate 7 min read

ELI5

smo1 keeps a checklist of twelve things that must be true before real customers pay. The checklist never says “done” by itself: a script looks for the evidence file each item points to and works out the state. Nine items have evidence; three are waiting on the owner. Separately, a support demo account with fifteen realistic links can be wiped and rebuilt with one command, and the command refuses to touch any account that is not the demo one.

Technical Deep Dive

Source: smo1-io .petrova/gates.yaml:1-196, .github/workflows/docs-invariants.yml:300-304, .github/scripts/gates.py, docs/launch/2026-10-09/session-ledger.md rows 2026-10-17T17:05Z to 2026-10-18T07:30Z and 2026-10-20T15:40Z, docs/decisions/2026-10-23-support-runbooks-out-of-draft.md, docs/decisions/2026-10-11-smo1-alert-routing.md, docs/decisions/2026-10-12-polar-org-split-trigger.md (origin/main 1f19667); purr-api cmd/seed-demo/main.go:1-75, internal/demoseed/plan.go:1-30, internal/demoseed/seed.go:50-70, Makefile:124-130 (bb6cfc2), read 2026-10-25.

How gate state is derived

stateDiagram-v2
[*] --> Evaluate
Evaluate --> met : discharged_by names a file that exists
Evaluate --> blocked : a blocked_by entry has no existing cleared_by, or names an unmet gate
Evaluate --> ready : neither
ready --> met : evidence file lands
blocked --> ready : blocker cleared

The file holds intent and evidence pointers only; the validator refuses a status field (gates.yaml:1-11). gates.py --self-test and a derivation run inside docs-invariants.yml, which since 2026-10-24 runs in the nightly batch rather than on every push (smo1-018). Each met gate also carries an assert: {contains: ...} string that must appear in its evidence file.

The twelve gates

GateTitleDerived
G-5-1Payment reconciles end to endmet (Polar sandbox, 2026-10-11 to 14)
G-5-2Customer receives Polar receipt emailmet
G-5-3smo1.io/<slug> served by the Workermet (smo1.io/88mph → 302)
G-5-4Clicks reach analyticsmet
G-5-5Capacity at launch scalemet (148,779 requests, 0 errors)
G-5-6Production alertingmet
G-5-7Truthful public copymet
G-5-8Demo tenant + 15-minute demo in productionmet (2026-10-18)
G-5-9Support runbooks walked liveblocked: ruling support-runbooks-out-of-draft; decision drafted 2026-10-23, status: open, sign-off PENDING
G-5-10CI green on mainmet
G-5-11Decision docs countersignedblocked: alert-routing (status: draft) and Polar org-split trigger (status: proposed) countersigns pending
G-5-12Polar live modeblocked on G-5-1 to G-5-11 and seven POLAR_* production variables

The file’s target is “first live Polar payment reconciled to one smo1 user and one tier”. The accepted 2026-10-24 TOLL decision removes every POLAR_* setting, so G-5-12 and the target no longer describe the path to revenue. Nothing on main re-targets them yet; expect a gates change that swaps G-5-12 for TOLL production readiness (smo1-014).

Demo tenant

flowchart LR
A["make seed-demo CONFIRM=yes"] --> B{"DEMO_USER_ID and DEMO_USER_EMAIL set ?"}
B -->|No| X1[refuse]
B -->|Yes| C["print target host and user, require --yes"]
C --> D{"existing users row with that id has a different email ?"}
D -->|Yes| X2[refuse, touch nothing]
D -->|No| E["one transaction: optional reset of rows owned by DEMO_USER_ID, then BuildPlan insert"]
E --> F[~15 links, synthetic clicks, business tier]
  • BuildPlan is pure and deterministic: fixed seed 20261010, per-link RNG streams, times relative to “now”, username smo1-demo (plan.go:1-30).
  • make reset-demo CONFIRM=yes deletes everything the demo user owns, then seeds. Without CONFIRM=yes both targets print the database host, user and action, then exit with “refusing to run without —yes … nothing was changed”. seed-demo on an already-seeded tenant is a no-op.
  • The first production run (2026-10-17) failed on schema drift (user_achievements.unlocked_at NOT NULL), rolled back cleanly, and passed after migration 000012: “seeded 15 links and 1052 clicks”, business tier, level 4, 640 XP. The demo user was created by an Airlock invite of demo@smo1.io.

Key Terms

  • gates/v1 — eva-hq gate schema: intent plus evidence pointers, state derived
  • discharged_by — the finding or decision file whose existence makes a gate met
  • cleared_by — the file that records a blocker as cleared
  • demoseed — purr-api package that plans and writes the demo tenant

Q&A

Q: Where does a gate’s state come from if the file has no status field? A: From .github/scripts/gates.py, which checks whether each gate’s discharged_by file exists (met), whether any blocker lacks an existing cleared_by or names an unmet gate (blocked), and otherwise reports ready.

Q: Which gates were not met as of the gates file on main, and why? A: G-5-9 (support runbooks still DRAFT; the promoting decision awaits countersign), G-5-11 (two decision countersigns pending) and G-5-12 (Polar live mode, blocked on all others and on unset POLAR_* variables, and now contradicted by the accepted TOLL decision).

Q: How does seed-demo avoid damaging a real customer’s account? A: It requires both DEMO_USER_ID and DEMO_USER_EMAIL, prints the target and refuses without --yes, refuses if a users row with that id has a different email, deletes only rows owned by that id, and does everything in one transaction that rolls back on any error.

Examples

Before a demo call, support runs make reset-demo CONFIRM=yes against production after the railway-target-disambiguation check. Afterwards the dashboard shows the same fifteen links and mood spread every time, so the talk track (“all seven moods, Loaf scores below Zoomies”) does not drift between calls.

neighbors on the map