preview-db-migrationsbreakdown.mdscope.md behind it (conversation-based cut,
borrowing the intake formats the way the audit skills do)Vercel preview deployments point at a preview database on the production Atlas cluster, and
production points at sustentus-prod — that half is wired. The GitHub half is not: the
preview environment in GitHub has no MONGODB_URI/MONGODB_DATABASE_NAME secrets (only
production values exist), so in the migrate-preview job both resolve empty, the
production-DB guard passes vacuously, and the run step takes its documented exit 0 skip
path — a green check that migrated nothing. The preview database Vercel actually serves
has therefore never been migrated or seeded by CI, which is exactly Jamie's symptom: preview
URLs where the changes "aren't there". Production has not been bitten yet; the merge-time
black box is that preview proves nothing. The agreed end state is option (a): the one shared
preview DB done properly — GitHub preview secrets pointing at the same database Vercel
preview uses, migrations applied loudly on every schema-touching PR, a missing secret failing
red instead of skipping green, and the DB carrying the baseline seed plus the demo storyline
fixtures so a preview URL is genuinely testable.
Resolved with Jamie on 2026-08-12:
db-migrate-preview concurrency group) stays an accepted tradeoff.db:migrate status-vs-production merge check was
offered and declined.db:seed) and the demo storyline
fixtures. Both databases live on the same Atlas cluster; preview and production use
separate Clerk instances, so the storyline needs preview-side identities before it can
be seeded.Dev-infra, not a product surface: the db-migrate.yaml workflow and GitHub preview
environment (stub 1), the db:seed baseline (stub 2), and the demo-tenant machinery shipped
by the demo-tenant batch replayed against the preview Clerk instance and preview DB
(stub 3). No service-journey step or feature-role-matrix entity changes behaviour;
what changes is whether any of them can be verified on a preview URL before merge.
db:migrate status against prod at Verify/Ship) —
offered, declined.migrate-production job's semantics or the migration engine
(ts-migrate-mongoose, prune/autosync behaviour) beyond what stub 1 incidentally touches.Context budget: conversation-based cut (no scope.md); claims about the skip path and guard
were verified against .github/workflows/db-migrate.yaml, packages/services/scripts/db-seed.ts
and the shipped demo-tenant work — cited in the stubs' Notes for Define.
_done/preview-db-seed.mddb:seed — the idempotent baseline every environment needs (global reference data:
statuses, action types; per-tenant defaults: SLA definitions, settings) — is never run in
CI at all. Even once stub 1 has the preview DB migrating correctly, its collections can be
schema-correct and still functionally empty: workflow transitions, action types and tenant
defaults missing, so preview URLs render half-broken for reasons that have nothing to do
with the PR under test.
Run the baseline seed as part of the preview migration job:
db:seed step to migrate-preview in .github/workflows/db-migrate.yaml,
after db:migrate up, using the same preview-environment secrets. The script is already
safe by design — "existing data is never overwritten"
(packages/services/scripts/db-seed.ts) — so running it on every schema-touching PR
converges instead of accumulating.workflow_dispatch preview run from stub 1 seeds as
well as migrates.migrate-preview run, the preview DB contains the baseline reference data
and tenant defaults; the job summary shows the created/skipped counts.migrate-production is untouched — whether production seeding belongs in CI is a
separate decision, not smuggled in here.db:seed seeds — this stub wires the existing script into the existing
job, nothing more.Smallest stub of the batch — likely a few workflow lines plus docs. Keep the failure
semantics consistent with stub 1: a seed failure fails the job (no || true). The
pnpm install --filter @sustentus/services... closure already present in the job covers
tsx and the script's imports, so no install changes expected. If the runbook page from
stub 1 exists by then, extend it rather than adding a second page. touches: guess —
.github/workflows/db-migrate.yaml, apps/docs (runbook page from stub 1).
_done/preview-db-wiring.mdThe migrate-preview job has passed green on every PR while migrating nothing. The GitHub
preview environment has no MONGODB_URI/MONGODB_DATABASE_NAME secrets, so both resolve
empty, the production-DB guard passes vacuously, and the run step's exit 0 skip path turns
"not configured" into a green check (.github/workflows/db-migrate.yaml:92–98). Meanwhile
the Vercel preview scope points at a real preview database that CI never touches — so
preview URLs run new code against an unmigrated, unseeded DB, and "it's green" means
nothing at merge time.
Wire the truth and make lying impossible:
preview environment secrets (MONGODB_URI,
MONGODB_DATABASE_NAME) to the same database the Vercel preview scope's
MONGODB_URI points at — read the value out of Vercel's preview-scope env settings, don't
mint a second preview DB. This is a human step in the GitHub/Vercel UIs; the stub's code
half lands after it. Same Atlas cluster as production, distinct dbName — the existing
guard (which refuses sustentus-prod) stays as-is.migrate-preview, an empty MONGODB_URI becomes
exit 1 with a pointer at the preview-environment secrets, not exit 0. A missing secret
must never be a green check again.workflow_dispatch run migrate-preview too (today the job's
if restricts it to pull_request, and a dispatch only reaches migrate-production), so
a preview DB that lagged behind — e.g. after app-only PRs — can be caught up without
pushing a dummy commit.packages/services/** shows migrate-preview connecting to the preview
DB and applying/confirming migrations — the job summary names the database and lists
the outcome.migrate-preview fails red with an actionable
message — verified once on a branch before the secrets land, or by temporarily
unsetting.sustentus-prod target.workflow_dispatch can run the preview migration on demand; migrate-production's
dispatch behaviour is unchanged.apps/docs via docs-sync at Ship.migrate-production beyond leaving its dispatch path intact.pull_request path filter — catch-up is the dispatch run, not per-PR churn.Sequencing inside the one PR matters: the loud-fail change merged before the secrets exist
would turn every services PR red — so the runbook step (configure secrets, confirm via a
dispatch or a scratch PR) precedes the merge, and the PR description records that the
secrets were verified present. The guard comment at db-migrate.yaml:80–84 describes
today's transitional state and should be rewritten once the state is no longer
transitional. DEMO_TENANT_CLERK_ORG_ID stays deliberately absent from migrate-preview
in this stub — stub 3 owns the preview demo tenant and will revisit
(db-migrate.yaml:113–119). touches: guess — .github/workflows/db-migrate.yaml,
apps/docs (runbook page), GitHub preview environment settings (human), Vercel
preview-scope env read-only (human).
_done/preview-demo-storyline.mdA migrated, baseline-seeded preview DB still has no tenants, no users and no data — you
can't sign in to a preview URL and test the feature. The demo-tenant batch already built
the answer for production: a provisioned isDemo tenant, one user per persona, and a
client-credible storyline the seeder reproduces (#790, #795). None of it exists on the
preview side, and it can't be copied across: preview runs a separate Clerk instance, so
production's org and user identities mean nothing there.
Replay the demo-tenant provisioning against the preview stack:
demo-tenant-foundation did for production — reusing its
provisioning path wherever it isn't hard-wired to the production instance.isDemo: true. The flag-demo-tenant migration will
already have run as a recorded no-op on the preview DB (its
DEMO_TENANT_CLERK_ORG_ID env var is deliberately absent from migrate-preview), so
Define must pick the flagging path: a preview-scoped env var set before first migration
is ordering-fragile — prefer an explicit re-runnable step.view-as reaches
all six persona dashboards.isDemo: true, so demo guard rails and demo-only affordances
behave on preview exactly as on production.isDemo tenant.demo-reset-ops ships a converging reset,
per-PR runs would accumulate).demo-seed-storyline (#795).demo-reset-ops (demo-tenant batch,
unshipped) owns reset; once it lands, pointing it at preview is a tweak-lane follow-up.Cross-scope dependency, recorded here per the cut contract: this stub builds on the
demo-tenant batch — demo-tenant-foundation (#790, shipped) and demo-seed-storyline
(#795, shipped) are prerequisites and are done; demo-reset-ops is not shipped, which
is why per-PR storyline seeding is out of scope above. Verify how foundation's provisioning
binds to Clerk (env-driven instance keys vs hardcoded) before promising reuse — if it's
env-driven, this stub is mostly ops + a thin invocation path. The demo-data trigger lives
at apps/web/app/(app)/admin/demo-data/actions.ts; the seeder and storyline under
packages/services/src/db/services/demo-data/. Preview Clerk users need real credentials
Jamie can use — store them wherever the team keeps demo credentials today, never in the
repo. touches: guess — preview Clerk instance (human/ops),
packages/services/src/db/services/demo-data/** (only if the invocation path needs a
headless entry), .github/workflows/db-migrate.yaml (only if Define picks the env-var
flagging path), apps/docs (runbook).