Skip to Content

← All archived runs

Run: preview-db-seed

run.md

Run: preview-db-seed

  • branch: claude/pipeline-preview-db-seed-xrudap
  • pr: #801

03_define/output/spec.md

Spec: Preview baseline seed — reference data on every preview migration

  • slug: preview-db-seed
  • personas: Admin
  • touches: .github/workflows/db-migrate.yaml, apps/docs, packages/services/scripts/db-seed.ts
  • complexity: trivial
  • demo: none

Problem

Stub 1 (preview-db-wiring, merged as #799) made migrate-preview migrate a real database and fail red when it can't. That fixes the schema half only: db:seed — the idempotent baseline every environment needs — has never run in CI at all. A preview database can therefore be perfectly migrated and still functionally empty, so a preview URL renders half-broken for reasons that have nothing to do with the PR under test, and the reviewer can't tell the two apart. This is stub 2 of 3 in the preview-db-migrations batch, and the last piece of generic baseline before stub 3 layers the demo storyline on top — the merge-time confidence that Refine the Bridge / 2026-Q2 Objective 3 (Validate Technical Infrastructure & Payout Flow) is asking previews to provide.

Proposed change

Run the existing baseline seed as part of the existing preview migration job — no new script, no change to what gets seeded.

  • A Seed baseline data step in migrate-preview, after Run migrations, running pnpm --filter @sustentus/services db:seed with the same environment the migration step already resolves. The install closure (pnpm install --filter @sustentus/services...) already covers tsx and the script's imports, so no install change.
  • Failure semantics match stub 1: no || true. A seed failure fails the job red with an ::error:: naming the resolved database. And because Run migrations exits non-zero on failure, a failed migration means the seed step never runs — nothing seeds against a half-migrated schema.
  • Surface the outcome in the job summary, next to the migration outcome and in the same shape: the created/already-present counts the script already prints for reference data and tenant defaults, plus the raw output in a collapsed block, put through the same connection-string scrub the migration output gets (sed -E 's#mongodb(\+srv)?://…#mongodb://<redacted>#g').
  • The catch-up path is covered for free: the workflow_dispatch preview run from stub 1 is the same job, so a dispatched catch-up seeds as well as migrates. No separate wiring.
  • migrate-production is not touched. Whether production seeding belongs in CI is a separate decision; this run does not smuggle it in.
  • Runbook: extend the existing apps/docs preview-database page (created by stub 1) rather than adding a second page — what the seed covers, what it deliberately doesn't (below), and how to read the counts. Written via docs-sync at Ship, in this PR.
  • One comment-only correction in packages/services/scripts/db-seed.ts: its docstring claims the script seeds "statuses", which it does not (see below). The runbook this run writes must not repeat that, and the docstring is the thing it would be written from. Comment text only — no behaviour, no change to what is seeded.

What the baseline seed actually is (resolved here, because the stub inherited a stale docstring)

  • Global reference data — the 13 action types in src/db/seed/reference-data.ts. Statuses are not seeded: they live in packages/services/src/db/workflows/workflows.json and are read in-memory (src/db/services/status/), so nothing status-driven ever depended on this step. The stub's "statuses, action types" phrasing tracks the stale docstring being corrected above, and its rough criterion about "status-driven surfaces (lead lifecycle)" is replaced below by one this change can actually deliver.
  • Per-tenant defaults — SLA definitions per stage and seedable tenant settings, iterated over the tenants that exist in the database. The shared preview DB has none until stub 3 creates the demo tenant, so Tenant defaults: 0 created, 0 already present is the correct and expected reading on this run; once stub 3 lands, the next preview migration fills that tenant's defaults with no further change here. The criteria below are written against that reality, not against a hoped-for non-zero count.

Acceptance criteria

  • migrate-preview runs db:seed after db:migrate up, against the same resolved preview database, on both the pull_request and the workflow_dispatch (target: preview) paths.
  • The job summary reports the seed outcome alongside the migration outcome, showing the created/already-present counts for both reference data and tenant defaults.
  • A second consecutive migrate-preview run reports 0 created for reference data with all 13 action types already present — idempotence observed in CI, not assumed.
  • A db:seed failure fails the job red (no || true), with an ::error:: naming the resolved database.
  • When db:migrate up fails, the seed step does not run.
  • The seed summary block contains no connection URI or credential fragment — the same scrub applied to the migration output.
  • migrate-production is unchanged — its jobs, steps, guards and triggers are byte-for-byte identical to main.
  • The preview-database runbook page in apps/docs covers the seed step: what it seeds, that statuses come from workflows.json and are not seeded, that tenant defaults apply only to tenants that already exist, and how to read the counts.

Out of scope

  • The demo storyline fixtures and anything Clerk-related — stub 3 (preview-demo-storyline).
  • Seeding production from CI, and any change to migrate-production.
  • Changing what db:seed seeds — no new reference data, no new tenant defaults, no change to seedDatabase()'s behaviour or its create-if-missing semantics. The one edit inside packages/services is the docstring correction named above.
  • Moving statuses out of workflows.json into the database, or seeding them.
  • Gating the Vercel preview build on the seed, or on the migration job — eventual consistency was accepted at the cut.
  • Widening the pull_request path filter; the dispatch catch-up remains the answer to a lagging preview DB.
  • A second docs page — the stub-1 runbook is extended, not duplicated.

Open questions

  • none.

Context budget: no scope.md or design notes exist for this scope — the preview-db-migrations breakdown records a conversation-based cut with doc: none, so Define proceeds from the stub, as its contract allows. The initiative/objective came from the stub rather than from business/{initiatives,okrs}, and no feature-role-matrix/service-journey page was read: the breakdown places this batch in dev-infra with no product-surface behaviour change. Overrun: the seed implementation was read (scripts/db-seed.ts, src/db/seed/*) beyond "a few targeted greps", because the stub's description of what the baseline seed contains turned out to be wrong and two acceptance criteria depended on it.

04_build/output/notes.md

Build notes: preview-db-seed

  • commits: feat: preview-db-seed — seed the preview database after every migration
  • demo: none (no Design stage for this scope — dev-infra, no product surface)

What changed

  • .github/workflows/db-migrate.yaml: a Seed baseline data step appended to migrate-preview, after Run migrations. Runs pnpm --filter @sustentus/services db:seed with the environment the job already resolves, set -o pipefail, no || true. Its summary block mirrors the migration step's shape — outcome line, the script's per-group N created, M already present counts lifted verbatim by sed, then the raw output in a collapsed block through the same mongodb(+srv)://… scrub. A non-zero exit emits ::error:: naming the resolved database and fails the job. The header comment gains a line saying preview seeds as well as migrates, and that production seeding is a separate decision. Purely additive: 45 insertions, 0 deletions, all in the preview job and the header.
  • packages/services/scripts/db-seed.ts: docstring correction only — it claimed the script seeds "statuses", which it has never done. Rewritten to name action types as the global group and to say outright that statuses live in workflows.json and are read in-memory. No behaviour change.
  • apps/docs/.../preview-database/page.mdx: a The baseline seed section (what runs, when, the create-if-missing guarantee, the two-group table) with the two easy misreadings called out — statuses are not seeded, and tenant defaults need a tenant to exist. Reading the result now covers both summary blocks and explains what the counts mean across consecutive runs; the catch-up section notes a preview dispatch re-seeds too.
  • apps/docs/.../ci-cd/page.mdx, apps/docs/.../database/page.mdx: kept in step — the migrate-preview bullet and the summary paragraph now mention the seed step and its failure semantics; the Seeding section says CI runs it for preview and not for production.

No tests: the diff is workflow YAML, comments and MDX. Nothing here is unit-assertable, and the acceptance criteria are observed on this PR's own Migrate preview database check.

Acceptance criteria status

  • migrate-preview runs db:seed after db:migrate up on both paths — the step is inside migrate-preview, whose if: already admits pull_request and workflow_dispatch with target: preview; nothing path-specific was added, so the dispatch route gets it for free.
  • Job summary reports the seed outcome with counts for both groups — the sed -n -E …p lifts Reference data: and Tenant defaults: from the script's stdout into the summary as bold rows next to the migration block.
  • A second consecutive run reports 0 created for reference data — implemented; observed on this PR's check runs, which is Verify's read (see below).
  • A seed failure fails the job red with an ::error:: naming the database — no || true; status is captured and re-raised after the summary is written, same pattern as migrations.
  • When db:migrate up fails, the seed step does not run — the migration step's exit "$status" fails the job, and a failed step skips the rest by default. No extra guard needed.
  • No connection URI or credential fragment in the seed summary block — the identical sed -E 's#mongodb(\+srv)?://[^[:space:]"]*#…#g' scrub is applied to seed.log.
  • migrate-production unchanged — git diff main on the workflow is 45 insertions and 0 deletions, none of them inside that job; parsing the YAML confirms its step list is identical.
  • The runbook covers the seed step, the statuses caveat, the tenant-defaults caveat and how to read the counts — all in the new The baseline seed section and the rewritten Reading the result.

Notes for Verify

  • The interesting criteria are observed, not read. This PR touches packages/services/** and the workflow file, so it matches the pull_request path filter and migrate-preview runs on it — the new step exercises itself. Read the check's job summary for criteria 2, 3 and 6, and re-run the job once for criterion 3's second-run reading.

  • First run on this PR (green, job 94485792224), against sustentus-preview:

    Reference data: 3 created, 10 already present
    Tenant defaults: 168 created, 2 already present
    

    Both numbers contradict what this section predicted before the run, and the correction is the interesting part:

    • Reference data 3/10, not 13/0. Action types auto-create on first use (src/db/seed/reference-data.ts), so ordinary preview traffic had already produced 10 of them organically. The seed filled the three that no code path had happened to touch. Nothing is wrong; the "13 created on a virgin database" figure only ever applied to a database nothing had run against.
    • Tenant defaults 168/2, not 0/0. The shared preview database does already carry tenants, and they were missing effectively their entire defaults set — 168 SLA and setting rows created on one run. That is this feature's premise showing up as a number: the database was schema-correct and functionally empty, and nobody could tell from a green migration check. Verify should read this as the change working, not as a surprise to chase.
  • Criterion 3 is the one still open. Re-run the job; the second run should report 0 created for both groups, with 13 reference-data rows and 170 tenant-default rows already present. A non-zero second reading is a real failure — it would mean the seed is not converging.

  • Worth eyeballing the rendered job summary, not just the log: the counts reach it through a sed match on the script's stdout, so a future change to those log lines' wording would silently drop them from the summary while the step still passes.

  • One stale comment left alone deliberately. packages/services/src/db/seed/index.ts carries the same "statuses, action types" phrasing this run corrected in scripts/db-seed.ts. The spec bounded the packages/services edit to that one file, so it was not touched — a one-line follow-up, not a defect in this diff.

  • The step name Seed baseline data is new; the check name is still Migrate preview database, so nothing in branch protection or the stub-1 runbook's references needs changing.

05_verify/output/verify.md

Verify: preview-db-seed

  • production-readiness: run — ready, no blockers. 3 findings fixed on branch, 2 recorded below.
  • code-review: low effort (spec complexity: trivial), run locally because CI's Claude review is gated on vars.ENABLE_CLAUDE_REVIEW == 'true' and its check reported skipped on this PR — no findings.
  • security-review: not required — the diff contains no auth, payments, PII or route-policy surface. Not silently skipped: the one security-relevant path here is the database credential reaching $GITHUB_STEP_SUMMARY, and production-readiness covered it against the live run (below).
  • playwright: TODO — manual DoD smoke performed instead.

DoD smoke (on the preview — each line says who verified it)

Evidence is the Migrate preview database check on this PR's own commits: the diff touches packages/services/** and the workflow file, so it matches the pull_request path filter and the new step exercises itself against sustentus-preview. No signed-in surface exists in this diff, so there is no operator half to wait on — stated plainly rather than left implied.

  • db:seed runs after db:migrate up on both the pull_request and workflow_dispatch (target: preview) paths — the step sits inside migrate-preview, whose if: already admits both events; nothing path-specific was added (agent, traced in diff + observed on the PR's own pull_request run)
  • Job summary reports the seed outcome with created/already-present counts for both groups — observed on job 94485792224 (agent)
  • A second consecutive run reports 0 created — observed on the run for ecf3d8f: Reference data: 0 created, 13 already present / Tenant defaults: 0 created, 170 already present, in 16s. Idempotence seen in CI, not assumed (agent)
  • A db:seed failure fails the job red with an ::error:: naming the database — traced: no || true, status captured and re-raised after the summary is written (agent, code path)
  • When db:migrate up fails the seed step does not run — the migration step's exit "$status" fails the job and a failed step skips the rest (agent, code path)
  • No connection URI or credential fragment in the seed summary — two independent defences confirmed against the live run: MONGODB_URI is a GitHub secret and renders *** in logs and summaries alike, and the block applies the same mongodb(+srv):// scrub as the migration step. The observed output contained no connection string at all (agent + production-readiness)
  • migrate-production unchanged — verified programmatically, not by eye: the text from migrate-production: to EOF is 4555 bytes on both origin/main and HEAD and compares byte-equal (production-readiness)
  • The preview-database runbook covers the seed, the statuses caveat, the tenant-defaults caveat and how to read the counts — rendered on the docs preview (agent)
  • auth: not touched — no route, middleware, Clerk or ROUTE_POLICIES surface in the diff
  • payments: not touched
  • notifications: none expected — the diff adds no notification path

Findings & cleanup

Fixed on branch (commit dc3ac2b) — all three are errors in this run's own prose, not in its behaviour. Two of them are the same mistake I had already corrected once in 04_build/notes.md and failed to carry back into the artifacts that ship:

  1. The workflow comment asserted something this branch's own run disproves. It stated as fact that the shared preview database has no tenants, so tenant defaults read 0 created, 0 already present. The first run created 168 tenant-default rows and the latest reports 170 already present — roughly 10 tenants exist in sustentus-preview today. Rewritten as the conditional it should always have been, and to say that a jump after a new tenant or seed entry is expected.
  2. The runbook's failure heuristic was backwards. It told the operator that a non-zero created count on a previously-seeded database means something removed rows. Three routine events produce one without any deletion — a new tenant, a new action type, a new seedable setting — and this PR's own first run (3 created) demonstrates a fourth: action types auto-create on first use, so the first seed only fills gaps. Replaced with the three explanations and the actual worth-investigating case.
  3. The step builds indexes, and the comment didn't say so. connectDB never sets autoIndex: false, so compiling the four models the seed needs also builds their declared indexes against the preview database. Benign and pre-existing — the app runtime does the same on every boot — but it is the first time CI does it, and index/migration lockstep is exactly what preview exists to expose. One honest line added rather than a behaviour change.

Accepted, recorded not fixed:

  1. Seed cost is linear in tenant count and fully sequential (tenant-defaults.ts:119-152, nested loops, two awaits per row). 170 rows ≈ 16s inside a 10-minute job timeout that also covers install and migration. Fine now; it becomes a question as the demo-storyline work adds tenants. Fix when it bites: one find per collection per tenant, or a bulkWrite of upserts. Out of scope here — the spec explicitly excludes changing what db:seed does.
  2. Rollback is asymmetric, and worth stating rather than inferring. The workflow change reverts cleanly by revert-commit; the rows already written to sustentus-preview do not revert with it. No migration is involved and the rows are harmless idempotent baseline data, so there is genuinely nothing to undo — but that should be said out loud, not assumed.

For intake, not this run:

  • packages/services/src/db/seed/index.ts:13-14 still carries the "statuses, action types" phrasing this run corrected in scripts/db-seed.ts. Deliberately out of the spec's bounded scope.
  • packages/services/src/db/migrations/1784200000000-view-as-audit-indexes.ts:6 claims the ViewAsAudit model "registers with autoIndex off (house idiom)" — that model never sets it, and the only schema that does is user.ts. The house idiom is documented but not implemented.

Note on the diff base

git diff main...HEAD in this checkout reports 162 files / ~11k insertions: local main is stale (67e3353) while the branch was cut from origin/main (f1c703b). The reviews were run against origin/main...HEAD9 files, 312 insertions. Anyone re-running Verify here should use origin/main as the base, or they will review a batch of already-merged work as if it were this PR.

Context budget: within the Inputs table. The production-readiness pass read beyond it by design — that is its job — into src/db/seed/**, db/connection.ts and the model files, to check boundedness and tenant scoping against source rather than against the spec's claims.

06_ship/output/investor-update.md

A migrated preview database is now a populated one

Who it's for: Admin — anyone testing a preview URL What shipped: Preview migrations now run the baseline seed too, so preview databases carry reference data and tenant defaults. Why it matters: Refine the Bridge / Q2 Objective 3, Validate Technical Infrastructure & Payout Flow — a green preview check finally means a testable preview.

The first run filled 168 missing rows; the next reported 0 created.

Dig deeper: <merged-PR URL>

06_ship/output/release.md

Ship: preview-db-seed

  • pr: #801 · merged: pending — Ready to merge is ticked; the squash follows this commit's checks
  • CI: green on 6b4e0ceQuality ✅, Migrate preview database ✅ (third consecutive run), Audit database ✅, advisory pipeline checks ✅; Migrate production database correctly skipped on a pull_request event. No check went red at any point in this run.
  • technical docs: preview database runbook gains a The baseline seed section (what it seeds, create-if-missing, and the two easy misreadings — statuses come from workflows.json, tenant defaults need a tenant) and a rewritten Reading the result; technical/development/ci-cd and technical/development/database updated so neither still describes migrate-preview as migration-only. Verified complete: those three are the only pages in apps/docs that mention the changed reality.
  • business docs: no business docs impact — CI configuration; no user-visible behaviour, persona capability or service-journey step changes.
  • release notes: ship-note only — internal dev-infra, nothing an end user can see or do differently, so no changelog entry (Ship step 3, infra branch).
  • sent: ship note queued for #product-update by the merge

Acceptance check (vs spec)

  • db:seed runs after db:migrate up on both the pull_request and workflow_dispatch (target: preview) paths — one step inside migrate-preview, which already admits both events
  • Job summary reports the seed outcome with created/already-present counts for both groups — observed on the PR's own check runs
  • A second consecutive run reports 0 created — observed: Reference data: 0 created, 13 already present / Tenant defaults: 0 created, 170 already present
  • A seed failure fails the job red with an ::error:: naming the database — no || true
  • A failed migration means the seed step never runs
  • No connection URI or credential fragment in the seed summary — same scrub as the migration block; confirmed against the live run
  • migrate-production unchanged — byte-equal from migrate-production: to EOF against origin/main (4555 bytes both sides)
  • The runbook covers the seed, both caveats, and how to read the counts

Notes

Verify's production-readiness pass found three factual errors in this run's own prose — two of them the same mistake already corrected once in the build notes and not carried back into the artifacts that ship. All three were fixed on branch in dc3ac2b before the merge; the detail is in 05_verify/output/verify.md.

Two items were accepted rather than fixed, and are worth carrying forward: the seed's cost is linear in tenant count and fully sequential (170 rows ≈ 16s today, a question once the demo-storyline work adds tenants), and rollback is asymmetric — reverting the workflow does not remove the rows already written to sustentus-preview. Nothing needs undoing there; the rows are idempotent baseline data.

Left deliberately out of scope, for intake: src/db/seed/index.ts still carries the "statuses, action types" phrasing corrected here in scripts/db-seed.ts, and migrations/1784200000000-view-as-audit-indexes.ts claims an autoIndex house idiom that only user.ts actually implements.