Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
#
Expand All @@ -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 <cmd>` 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
Expand All @@ -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
Expand Down
60 changes: 57 additions & 3 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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"
Expand Down Expand Up @@ -380,8 +407,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.
Expand All @@ -406,7 +460,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
Expand Down
28 changes: 19 additions & 9 deletions .github/workflows/rebuild-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 31 additions & 27 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -904,19 +902,19 @@ 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 |

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 —
Comment thread
mthines marked this conversation as resolved.
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 —
Expand Down Expand Up @@ -945,26 +943,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://<preview-ref>.supabase.co \
SUPABASE_SERVICE_ROLE_KEY=<preview-service-role-key> \
SUPABASE_URL=https://<project-ref>.supabase.co \
SUPABASE_SERVICE_ROLE_KEY=<service-role-key> \
LOREKIT_SMOKE_EMAIL=<email> \
LOREKIT_SMOKE_PASSWORD='<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
Expand Down Expand Up @@ -1001,7 +1004,8 @@ node scripts/seed/seed-preview.mjs --user-email <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
Comment thread
mthines marked this conversation as resolved.
**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
Expand All @@ -1011,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

Expand Down
6 changes: 3 additions & 3 deletions scripts/seed/seed-preview.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +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) — 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,
* 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';
Expand Down
Loading
Loading