From c7d6600bfe32f7e4cd6f4684c4394847b8b3e98b Mon Sep 17 00:00:00 2001 From: mthines Date: Sun, 18 Oct 2026 14:06:56 +0200 Subject: [PATCH 1/3] ci: create the orgs-smoke user on preview instead of seeding it by hand rebuild-preview.yml's db reset wipes the fixed smoke user every week, and the orgs REST smoke then skipped until someone re-seeded it from an admin shell. smoke-preview (deploy.yml, preview.yml) now runs the idempotent seed-smoke-user.mjs before its JWT mint, and rebuild-preview re-runs it right after the reset. The service-role key is the preview environment's, set on that one step, and the step refuses the production ref. --- .github/workflows/deploy.yml | 43 ++++++++++++++++++++----- .github/workflows/preview.yml | 31 ++++++++++++++++-- .github/workflows/rebuild-preview.yml | 28 ++++++++++------ docs/deployment.md | 46 ++++++++++++++------------- scripts/seed/seed-preview.mjs | 5 ++- scripts/smoke/seed-smoke-user.mjs | 10 +++--- 6 files changed, 114 insertions(+), 49 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 24230cd0f..a66ce48e7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,9 +47,9 @@ # write-heavy REST smoke runs against preview only, never production (see the # note in smoke-production for why): # SUPABASE_ANON_KEY, LOREKIT_SMOKE_EMAIL, LOREKIT_SMOKE_PASSWORD. -# The fixed smoke user those secrets name must already exist on the project — seed it -# once with `node scripts/smoke/seed-smoke-user.mjs` (see docs/deployment.md → "Seed -# the orgs-smoke user"); the mint signs in as it, it is never auto-created. +# smoke-preview (re)creates the fixed smoke user those secrets name before it mints +# a JWT, with the preview environment's SUPABASE_SERVICE_ROLE_KEY, so it survives a +# preview rebuild (see docs/deployment.md → "Seed the orgs-smoke user"). # Optional repo-level secrets for the website e2e suite (packages/web-e2e, # docs/smoke-tests.md): LOREKIT_TESTING_USER_NAME + LOREKIT_TESTING_USER_PW sign # in a production test account for the signed-in pages (unset → those tests @@ -647,9 +647,36 @@ jobs: URL: https://${{ secrets.SUPABASE_PROJECT_REF }}.supabase.co/functions/v1/mcp TOKEN: ${{ secrets.LOREKIT_SMOKE_TOKEN }} + # The fixed smoke user lives in the preview project's auth schema, which + # rebuild-preview.yml wipes. Re-create it before the mint so the orgs suite + # heals on its own: seed-smoke-user.mjs signs in first and touches the Auth + # admin API only when that fails (user missing, or its password drifted). + # The service-role key is scoped to this ONE step and comes from the + # `preview` environment, so it can only ever reach the preview project. + # Best-effort like the mint: a failure leaves the orgs suite skipped. + - name: Ensure the fixed smoke user exists on preview + if: env.HAVE_ORG_SMOKE_CREDS == 'true' + env: + PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + LOREKIT_SMOKE_EMAIL: ${{ secrets.LOREKIT_SMOKE_EMAIL }} + LOREKIT_SMOKE_PASSWORD: ${{ secrets.LOREKIT_SMOKE_PASSWORD }} + run: | + if [ "$PROJECT_REF" = "pqokxlhvnosogizsjztg" ]; then + echo "::error::SUPABASE_PROJECT_REF is the production project — refusing to seed the smoke user there." + exit 1 + fi + if [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then + echo "::warning::SUPABASE_SERVICE_ROLE_KEY is not set on the preview environment — cannot (re)create the fixed smoke user." + exit 0 + fi + SUPABASE_URL="https://$PROJECT_REF.supabase.co" node scripts/smoke/seed-smoke-user.mjs \ + || echo "::warning::Could not seed the fixed smoke user on preview — the orgs REST suite will be skipped." + # Mint a user JWT so smoke-rest can include the orgs suite. Fixed-user mode - # (LOREKIT_SMOKE_EMAIL/PASSWORD) — never provisions users in a real tenant, - # unlike CI's ephemeral mode against a throwaway local stack. Runs only when + # (LOREKIT_SMOKE_EMAIL/PASSWORD), the user the step above ensures — unlike + # CI's ephemeral mode against a throwaway local stack. Runs only when # the optional credentials are configured; otherwise the orgs suite is left # out and announced by smoke-rest, never a green suite that tested nothing. # @@ -659,8 +686,8 @@ jobs: # sign-in disabled on the project) — do not let that red the pipeline and # block promotion to production. On failure we warn loudly and leave the # `jwt` output empty, so smoke-rest runs the memories suite only and - # announces the orgs skip. To ENABLE the orgs suite, seed a confirmed - # email+password user on this project matching LOREKIT_SMOKE_EMAIL/PASSWORD. + # announces the orgs skip. The step above (re)creates the user, so a + # failure here means that seed failed too — read its warning. # (`if ` suspends `set -e` for the mint, so a failure lands in `else`.) - name: Mint a user JWT for the orgs REST smoke (optional) id: smokejwt @@ -675,7 +702,7 @@ jobs: echo "::add-mask::$JWT" echo "jwt=$JWT" >> "$GITHUB_OUTPUT" else - echo "::warning::Could not mint a smoke JWT — the orgs REST suite is skipped this run. Seed a confirmed email+password smoke user (matching LOREKIT_SMOKE_EMAIL/PASSWORD) on this project to enable it." + echo "::warning::Could not mint a smoke JWT — the orgs REST suite is skipped this run. See the 'Ensure the fixed smoke user exists' step above." fi # REST Edge Functions end-to-end against the live preview stack — the diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index a1b523b01..839a9d0bc 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -380,8 +380,35 @@ jobs: URL: https://${{ secrets.SUPABASE_PROJECT_REF }}.supabase.co/functions/v1/mcp TOKEN: ${{ secrets.LOREKIT_SMOKE_TOKEN }} + # The fixed smoke user lives in the preview project's auth schema, which + # rebuild-preview.yml wipes. Re-create it before the mint so the orgs suite + # heals on its own: seed-smoke-user.mjs signs in first and touches the Auth + # admin API only when that fails (user missing, or its password drifted). + # The service-role key is scoped to this ONE step and comes from the + # `preview` environment, so it can only ever reach the preview project. + # Best-effort like the mint: a failure leaves the orgs suite skipped. + - name: Ensure the fixed smoke user exists on preview + if: env.HAVE_ORG_SMOKE_CREDS == 'true' + env: + PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + LOREKIT_SMOKE_EMAIL: ${{ secrets.LOREKIT_SMOKE_EMAIL }} + LOREKIT_SMOKE_PASSWORD: ${{ secrets.LOREKIT_SMOKE_PASSWORD }} + run: | + if [ "$PROJECT_REF" = "pqokxlhvnosogizsjztg" ]; then + echo "::error::SUPABASE_PROJECT_REF is the production project — refusing to seed the smoke user there." + exit 1 + fi + if [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then + echo "::warning::SUPABASE_SERVICE_ROLE_KEY is not set on the preview environment — cannot (re)create the fixed smoke user." + exit 0 + fi + SUPABASE_URL="https://$PROJECT_REF.supabase.co" node scripts/smoke/seed-smoke-user.mjs \ + || echo "::warning::Could not seed the fixed smoke user on preview — the orgs REST suite will be skipped." + # Mint a user JWT so smoke-rest can include the orgs suite. Fixed-user mode - # (LOREKIT_SMOKE_EMAIL/PASSWORD) — never provisions users in a real tenant. + # (LOREKIT_SMOKE_EMAIL/PASSWORD), the user the step above ensures. # Runs only when the optional credentials are configured; otherwise the # orgs suite is left out and announced by smoke-rest, never a green suite # that tested nothing. @@ -406,7 +433,7 @@ jobs: echo "::add-mask::$JWT" echo "jwt=$JWT" >> "$GITHUB_OUTPUT" else - echo "::warning::Could not mint a smoke JWT — the orgs REST suite is skipped this run. Seed a confirmed email+password smoke user (matching LOREKIT_SMOKE_EMAIL/PASSWORD) on this project to enable it." + echo "::warning::Could not mint a smoke JWT — the orgs REST suite is skipped this run. See the 'Ensure the fixed smoke user exists' step above." fi # REST Edge Functions end-to-end against the live preview stack — the diff --git a/.github/workflows/rebuild-preview.yml b/.github/workflows/rebuild-preview.yml index 89bf7153b..38fc4cd19 100644 --- a/.github/workflows/rebuild-preview.yml +++ b/.github/workflows/rebuild-preview.yml @@ -143,16 +143,26 @@ jobs: - name: Reset preview database from migrations run: supabase db reset --linked --yes - # `db reset` wipes the seeded orgs-smoke user along with everything else, - # so the orgs REST smoke self-skips (announced, non-fatal) until it is - # re-seeded. Re-seeding needs the service-role key (the Auth admin API) and - # is DELIBERATELY not done in CI — the recurring smoke path never carries - # that key (see docs/deployment.md → "Seed the orgs-smoke user"). Surface a - # reminder rather than smuggling the key into a recurring workflow. - - name: Remind to re-seed the orgs-smoke user + # `db reset` wipes the fixed orgs-smoke user along with everything else, so + # re-create it here (smoke-preview also ensures it before every mint, so a + # failure here heals on the next smoke run). The service-role key is scoped + # to this step; the job's `preview` environment and the production-ref guard + # above mean it can only reach the preview project. Best-effort: the reset + # itself succeeded, and the orgs REST suite only skips until a seed works. + - name: Re-seed the orgs-smoke user + env: + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + LOREKIT_SMOKE_EMAIL: ${{ secrets.LOREKIT_SMOKE_EMAIL }} + LOREKIT_SMOKE_PASSWORD: ${{ secrets.LOREKIT_SMOKE_PASSWORD }} run: | - echo "::notice::Preview DB was reset — re-seed the orgs-smoke user out-of-band with scripts/smoke/seed-smoke-user.mjs (needs the service-role key; not run in CI). Until then the orgs REST smoke self-skips." - echo "::notice::The reset also wiped any demo data from a prior '/preview --seed'. Once the smoke user is re-seeded, repopulate it with a '/preview --seed' comment on any open PR (see docs/deployment.md → 'Seed the preview with demo data') — not done here, for the same service-role-key reason." + if [ -z "$SUPABASE_SERVICE_ROLE_KEY" ] || [ -z "$LOREKIT_SMOKE_EMAIL" ] || [ -z "$LOREKIT_SMOKE_PASSWORD" ]; then + echo "::warning::SUPABASE_SERVICE_ROLE_KEY / LOREKIT_SMOKE_EMAIL / LOREKIT_SMOKE_PASSWORD not all set — the orgs-smoke user was not re-created." + else + SUPABASE_URL="https://$PROJECT_REF.supabase.co" node scripts/smoke/seed-smoke-user.mjs \ + || echo "::warning::Could not re-seed the orgs-smoke user — smoke-preview retries before its next mint." + fi + echo "::notice::Preview DB was reset, which also wiped any '/preview --seed' demo data. Repopulate it with a '/preview --seed' comment on any open PR (see docs/deployment.md → 'Seed the preview with demo data')." - name: Deploy edge functions (mcp + health) # Retry up to 3 times with 10 s back-off to tolerate transient OOM diff --git a/docs/deployment.md b/docs/deployment.md index 36ce1ac43..4b22924b3 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -692,12 +692,10 @@ repair`), and it's where you confirm whether the pinned Supabase CLI prompts on drive it non-interactively with whatever confirmation flag the installed CLI exposes. -A reset wipes **everything**, including the seeded orgs-smoke user (see "Seed the -orgs-smoke user" below), so the orgs REST smoke self-skips until you re-seed it. -Re-seeding needs the service-role key and is deliberately **not** wired into the -workflow (the recurring smoke path never carries that key) — the workflow only -emits a reminder. After a rebuild, run `scripts/smoke/seed-smoke-user.mjs` from a -trusted admin shell against the preview project. +A reset wipes **everything**, including the orgs-smoke user (see "Seed the +orgs-smoke user" below). The workflow re-creates that user right after the reset, +and `smoke-preview` ensures it again before every JWT mint, so the orgs REST smoke +keeps running across rebuilds with no manual step. ### Failure notifications (Discord) @@ -914,9 +912,8 @@ never blocked. Set all three to enable it: The org endpoints require a real Supabase **user JWT** (`lk_*` tokens and the service-role key are rejected), so `smoke-preview` mints one per run by signing -in as this fixed user. That user must already exist on the project — -seed it once with [`scripts/smoke/seed-smoke-user.mjs`](#seed-the-orgs-smoke-user) -below. +in as this fixed user. On **preview** the pipeline creates that user itself — +see [Seed the orgs-smoke user](#seed-the-orgs-smoke-user) below. Repo-level secrets (not environment-scoped): `SUPABASE_ACCESS_TOKEN` (a Supabase personal access token); the three **Vercel** secrets the web jobs use — @@ -945,26 +942,31 @@ Variables), both optional: #### Seed the orgs-smoke user -The orgs smoke signs in as a fixed user; it never creates one (a smoke run must -not provision users in a real tenant). Create that user **once per project** -with the idempotent seed script — it creates + email-confirms the user, resets a -drifted password on re-run, and verifies the sign-in works end-to-end before -exiting 0: +The orgs smoke signs in as a fixed user. On the **preview** project the pipeline +creates it: `smoke-preview` (in `deploy.yml` and `preview.yml`) runs the +idempotent seed script right before it mints the JWT, and `rebuild-preview.yml` +runs it right after `db reset`. The script signs in first and touches the Auth +admin API only when that fails (the user is missing after a reset, or its +password drifted from `LOREKIT_SMOKE_PASSWORD`). It then creates and +email-confirms the user, or resets its password, and verifies the sign-in. + +The seed needs the service-role key (the Auth admin API). It is the `preview` +environment's `SUPABASE_SERVICE_ROLE_KEY`, set on that one step only, so it can +never reach production; the step also refuses outright when the project ref is +the production one. Seeding is best-effort, like the mint: a failure warns and the +orgs suite skips that run. + +Production is never seeded by CI. To seed it (or any project by hand), run the +script from a trusted admin shell: ```bash -SUPABASE_URL=https://.supabase.co \ -SUPABASE_SERVICE_ROLE_KEY= \ +SUPABASE_URL=https://.supabase.co \ +SUPABASE_SERVICE_ROLE_KEY= \ LOREKIT_SMOKE_EMAIL= \ LOREKIT_SMOKE_PASSWORD='' \ node scripts/smoke/seed-smoke-user.mjs ``` -Run it from a trusted admin shell — **not in CI**. Creating a confirmed user -needs the service-role key (the Auth admin API), which the recurring deploy -smoke path deliberately does not carry; CI only ever uses the anon key + -email/password to mint the JWT. Re-run with the production ref + key to seed the -production project too. - #### Seed the preview with demo data A fresh `/preview` deploy pushes migrations and edge functions, but the diff --git a/scripts/seed/seed-preview.mjs b/scripts/seed/seed-preview.mjs index 094091eed..1b8e302da 100644 --- a/scripts/seed/seed-preview.mjs +++ b/scripts/seed/seed-preview.mjs @@ -37,9 +37,8 @@ * node scripts/seed/seed-preview.mjs --user-email … --reset # wipe this seed's rows first * * The target user must already exist (seed it with - * scripts/smoke/seed-smoke-user.mjs first) — this script never creates one, - * for the same reason the smoke suites don't: provisioning a user is a - * separate, deliberate, out-of-CI act. + * scripts/smoke/seed-smoke-user.mjs first — on preview, smoke-preview and + * rebuild-preview.yml already do) — this script never creates one. */ import { spawnSync } from 'node:child_process'; import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; diff --git a/scripts/smoke/seed-smoke-user.mjs b/scripts/smoke/seed-smoke-user.mjs index 46587e891..bedb7f24f 100755 --- a/scripts/smoke/seed-smoke-user.mjs +++ b/scripts/smoke/seed-smoke-user.mjs @@ -22,11 +22,11 @@ * * ## Where to run it * - * ONCE per project (preview, then production), from a trusted admin shell — - * NOT in CI. Creating a confirmed user requires the service-role key (the Auth - * admin API), which the recurring deploy smoke path deliberately does NOT carry - * (CI only ever uses the anon key + email/password to mint a JWT). Keep the - * service-role key on your machine / a secret store, not in a workflow. + * PREVIEW: CI runs it — `smoke-preview` (deploy.yml, preview.yml) before every + * JWT mint and rebuild-preview.yml after every `db reset`, because a reset wipes + * the user. The service-role key is the `preview` environment's, set on that one + * step, and the step refuses the production ref. + * PRODUCTION (or any project by hand): from a trusted admin shell, never CI. * * ## What it does (idempotent, self-healing) * From 14254054bb98747467ecd6ecdd6080562dc32ef6 Mon Sep 17 00:00:00 2001 From: mthines Date: Sun, 18 Oct 2026 15:22:03 +0200 Subject: [PATCH 2/3] ci: ensure the smoke user before /preview --seed; document the service-role key for the orgs smoke --- .github/workflows/preview.yml | 29 ++++++++++++++++++++++++++++- docs/deployment.md | 6 ++++-- scripts/smoke/seed-smoke-user.mjs | 6 ++++-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 839a9d0bc..bb0a132e9 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -16,6 +16,9 @@ # SUPABASE_ACCESS_TOKEN, LOREKIT_SMOKE_TOKEN # repository level: VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID # optional: DISCORD_WEBHOOK_URL +# optional (orgs REST smoke + --seed): LOREKIT_SMOKE_EMAIL, LOREKIT_SMOKE_PASSWORD +# (repo); SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY +# (preview environment — the key (re)creates the smoke user) # # Security: issue_comment reads from main's workflow file (not the PR branch). # PR head is checked out by pinned SHA, not a user-controllable ref. @@ -203,6 +206,30 @@ jobs: with: node-version: 20 + # The seed targets the fixed smoke user, and this job runs in parallel + # with `smoke` — so it cannot rely on smoke's ensure step having run. + # Ensure the user here too (idempotent: a sign-in first, the admin API + # only when that fails), so `/preview --seed` works straight after a + # rebuild-preview.yml reset. + - name: Ensure the fixed smoke user exists on preview + env: + PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + LOREKIT_SMOKE_EMAIL: ${{ secrets.LOREKIT_SMOKE_EMAIL }} + LOREKIT_SMOKE_PASSWORD: ${{ secrets.LOREKIT_SMOKE_PASSWORD }} + run: | + if [ "$PROJECT_REF" = "pqokxlhvnosogizsjztg" ]; then + echo "::error::SUPABASE_PROJECT_REF is the production project — refusing to seed the smoke user there." + exit 1 + fi + if [ -z "$LOREKIT_SMOKE_EMAIL" ] || [ -z "$LOREKIT_SMOKE_PASSWORD" ] || [ -z "$SUPABASE_ANON_KEY" ] || [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then + echo "Smoke-user credentials incomplete — not (re)creating the fixed smoke user." + exit 0 + fi + SUPABASE_URL="https://$PROJECT_REF.supabase.co" node scripts/smoke/seed-smoke-user.mjs \ + || echo "::warning::Could not seed the fixed smoke user on preview — the demo-data seed below may fail." + # The seed targets the fixed smoke user (LOREKIT_SMOKE_EMAIL) — the same # account the orgs REST smoke signs in as — so it is optional in the # same way that suite is: unset → skip, announced, never fail the run. @@ -213,7 +240,7 @@ jobs: LOREKIT_SMOKE_EMAIL: ${{ secrets.LOREKIT_SMOKE_EMAIL }} run: | if [ -z "$LOREKIT_SMOKE_EMAIL" ]; then - echo "::warning::--seed requested but LOREKIT_SMOKE_EMAIL is not set — skipping. Seed it once with scripts/smoke/seed-smoke-user.mjs to enable /preview --seed." + echo "::warning::--seed requested but LOREKIT_SMOKE_EMAIL is not set — skipping. Set LOREKIT_SMOKE_EMAIL/PASSWORD (repo) and SUPABASE_ANON_KEY/SUPABASE_SERVICE_ROLE_KEY (preview environment) to enable /preview --seed." exit 0 fi node scripts/seed/seed-preview.mjs --user-email "$LOREKIT_SMOKE_EMAIL" diff --git a/docs/deployment.md b/docs/deployment.md index 4b22924b3..95fbbb74a 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -902,11 +902,12 @@ right values: **Optional — the orgs REST smoke suite (`smoke-preview` only).** Unset → the suite is announced-skipped and the memories suite still runs; the deploy is -never blocked. Set all three to enable it: +never blocked. Set all four to enable it: | Secret | `preview` environment | |--------|-----------------------| | `SUPABASE_ANON_KEY` | preview anon (publishable) key | +| `SUPABASE_SERVICE_ROLE_KEY` | preview service-role key — lets CI (re)create the smoke user after a reset; without it the suite skips again after the next weekly rebuild | | `LOREKIT_SMOKE_EMAIL` | fixed smoke user's email | | `LOREKIT_SMOKE_PASSWORD` | fixed smoke user's password | @@ -1003,7 +1004,8 @@ node scripts/seed/seed-preview.mjs --user-email --reset # wipe this The target user must already exist — seed it first with [`seed-smoke-user.mjs`](#seed-the-orgs-smoke-user) above; this script never -creates one, for the same reason the smoke suites don't. It refuses the +creates one. On preview, CI keeps that user in place (the `smoke-preview` jobs, +`/preview --seed`, and rebuild-preview.yml all run `seed-smoke-user.mjs`). It refuses the **production** project ref unconditionally (no override flag), matching [Rebuilding the shared preview project](#rebuilding-the-shared-preview-project)'s own belt-and-suspenders check. Every seeded memory's key is prefixed diff --git a/scripts/smoke/seed-smoke-user.mjs b/scripts/smoke/seed-smoke-user.mjs index bedb7f24f..877cb3ed1 100755 --- a/scripts/smoke/seed-smoke-user.mjs +++ b/scripts/smoke/seed-smoke-user.mjs @@ -9,7 +9,8 @@ * the actor as `auth.uid()` — `lk_*` tokens and the service-role key are both * rejected). `mint-smoke-jwt.mjs` mints that user's JWT in fixed-user mode by * signing in with `LOREKIT_SMOKE_EMAIL`/`LOREKIT_SMOKE_PASSWORD`, but it never - * *creates* the user (a smoke run must not provision users in a real tenant). + * *creates* the user — minting is a read-only sign-in; provisioning is this + * script's job, run as its own step. * * So the user has to exist up front. Doing that by hand in the dashboard is * error-prone in exactly the way that bites silently: an unconfirmed email or a @@ -23,7 +24,8 @@ * ## Where to run it * * PREVIEW: CI runs it — `smoke-preview` (deploy.yml, preview.yml) before every - * JWT mint and rebuild-preview.yml after every `db reset`, because a reset wipes + * JWT mint, preview.yml's `seed` job before `/preview --seed`, and + * rebuild-preview.yml after every `db reset`, because a reset wipes * the user. The service-role key is the `preview` environment's, set on that one * step, and the step refuses the production ref. * PRODUCTION (or any project by hand): from a trusted admin shell, never CI. From f4b7438c1a24a65260eef93a17ed447aefb81097 Mon Sep 17 00:00:00 2001 From: mthines Date: Sun, 18 Oct 2026 15:25:39 +0200 Subject: [PATCH 3/3] docs: the demo-data reseed no longer waits on a manual smoke-user seed --- docs/deployment.md | 6 +++--- scripts/seed/seed-preview.mjs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/deployment.md b/docs/deployment.md index 95fbbb74a..a1ddc54cb 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1015,9 +1015,9 @@ close to idempotent: memories upsert on `(user_id, scope, key)`, so a reseed without `--reset` refreshes the same rows rather than duplicating them. A [Rebuilding the shared preview project](#rebuilding-the-shared-preview-project) -run wipes any seeded demo data along with everything else (and the smoke -user, per the notice above) — reseed with `/preview --seed` on any open PR -once the smoke user is re-seeded. +run wipes any seeded demo data along with everything else. The smoke user comes +back on its own (the rebuild re-creates it, and so does `/preview --seed`'s own +job), so reseed with `/preview --seed` on any open PR. ### Recommended branch protection diff --git a/scripts/seed/seed-preview.mjs b/scripts/seed/seed-preview.mjs index 1b8e302da..53ee86cbd 100644 --- a/scripts/seed/seed-preview.mjs +++ b/scripts/seed/seed-preview.mjs @@ -37,8 +37,9 @@ * node scripts/seed/seed-preview.mjs --user-email … --reset # wipe this seed's rows first * * The target user must already exist (seed it with - * scripts/smoke/seed-smoke-user.mjs first — on preview, smoke-preview and - * rebuild-preview.yml already do) — this script never creates one. + * scripts/smoke/seed-smoke-user.mjs first — on preview, smoke-preview, + * preview.yml's `seed` job and rebuild-preview.yml already do) — this script + * never creates one. */ import { spawnSync } from 'node:child_process'; import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';