docs(10): revise phase plan per plan-checker feedback (3 blockers, 2 warnings)

This commit is contained in:
Lucas Berger
2026-06-13 14:07:22 -04:00
parent b24fbbfde7
commit a944dcd881
4 changed files with 100 additions and 62 deletions
@@ -94,27 +94,27 @@ Output: Edited `schema.ts`, a generated `0001_v1_1_foundation.sql` migration fil
<read_first>
- apps/api/src/db/migrations/0000_baseline.sql (existing migration format — `--> statement-breakpoint` between DDL statements; the format the generated file must follow)
- apps/api/drizzle.config.ts (migration output dir + env-driven DB credentials)
- apps/api/package.json (the `db:generate` / `db:migrate` scripts)
- apps/api/package.json (the `db:generate` / `db:migrate` scripts`db:generate` = `drizzle-kit generate`, `db:migrate` = `drizzle-kit migrate`)
- .planning/phases/10-admin-role-settings/10-RESEARCH.md §Pattern 3 (generate+migrate workflow, exact commands, DB_HOST=127.0.0.1 dev override) + §Pitfall 4 (why never push)
- MEMORY note [[drizzle-mariadb-push-unsafe]] context in 10-CONTEXT.md Claude's Discretion (generate+migrate, never push)
- MEMORY note [[drizzle-mariadb-push-unsafe]] context in 10-CONTEXT.md Claude's Discretion (generate+migrate, never push) + [[api-integration-test-db]] (DB_HOST=127.0.0.1 + .env creds for a reachable dev MariaDB)
</read_first>
<action>
BLOCKING — this must run AFTER Task 1 (schema.ts complete) and BEFORE any plan that reads the new columns. Bring up the dev MariaDB if not already bound on 3306 (per [[dev-stack-bringup]]: dev compose override exposes 3306). Then:
1. Run `pnpm --filter @familysync/api db:generate` to produce `apps/api/src/db/migrations/0001_v1_1_foundation.sql` (drizzle-kit names it; the actual filename may differ — commit whatever drizzle-kit emits as the next sequential migration) and update `meta/_journal.json`. DO NOT hand-write the SQL.
2. INSPECT the generated SQL: it MUST be only `ALTER TABLE ... ADD COLUMN` / `ADD UNIQUE` / `CREATE TABLE` statements (additive). If it contains any `DROP TABLE`, `DROP COLUMN`, or `TRUNCATE`, STOP — that is the false-destructive-diff trap; do NOT apply it, and do NOT fall back to `db:push`. Re-derive from schema.ts.
3. Apply with `DB_HOST=127.0.0.1` (+ dev DB_USER/DB_PASSWORD/DB_NAME from .env): `cd apps/api && set -a; source ../../.env; set +a; DB_HOST=127.0.0.1 pnpm db:migrate`. NEVER `db:push`.
4. Verify the live columns/table exist via a mysql2 query (not just tsc): assert `is_admin` on `users`, `provider_type` + the unique index on `member_credentials`, `reminder_lead_minutes` on `calendar_events`, and the `app_config` table.
BLOCKING — this must run AFTER Task 1 (schema.ts complete) and BEFORE any plan that reads the new columns. You MUST actually RUN both commands in this task; describing them is not enough, and tsc/build passing is NOT sufficient proof that the migration ran (Drizzle types come from schema.ts regardless of whether the DB was migrated). Bring up the dev MariaDB if not already bound on 3306 (per [[dev-stack-bringup]]: dev compose override exposes 3306). Then:
1. RUN `pnpm --filter @familysync/api db:generate` to produce the next sequential migration `.sql` under `apps/api/src/db/migrations/` (drizzle-kit names it `0001_v1_1_foundation.sql` or a similar sequential name — commit whatever drizzle-kit emits) and update `meta/_journal.json`. DO NOT hand-write the SQL.
2. INSPECT the generated SQL: it MUST be only `ALTER TABLE ... ADD COLUMN` / `ADD UNIQUE` / `CREATE TABLE` statements (additive). If it contains any `DROP TABLE`, `DROP COLUMN`, or `TRUNCATE`, STOP — that is the false-destructive-diff trap ([[drizzle-mariadb-push-unsafe]]); do NOT apply it, and do NOT fall back to `db:push`. Re-derive from schema.ts.
3. RUN `pnpm --filter @familysync/api db:migrate` against the reachable dev DB with `DB_HOST=127.0.0.1` + the dev DB_USER/DB_PASSWORD/DB_NAME/DB_PORT from `.env` (per [[api-integration-test-db]]): `set -a; source .env; set +a; DB_HOST=127.0.0.1 pnpm --filter @familysync/api db:migrate`. NEVER `db:push` / `drizzle-kit push`.
4. Verify the live columns/table exist via a mysql2 query (not just tsc): assert `is_admin` on `users`, `provider_type` + the unique index on `member_credentials`, `reminder_lead_minutes` on `calendar_events`, and the `app_config` table each return a row.
</action>
<verify>
<automated>cd /home/luc/Projects/familysync && set -a; source .env 2>/dev/null; set +a; DB_HOST=127.0.0.1 node -e "const m=require('mysql2/promise');(async()=>{const c=await m.createConnection({host:'127.0.0.1',port:Number(process.env.DB_PORT||3306),user:process.env.DB_USER||'familysync',password:process.env.DB_PASSWORD||'',database:process.env.DB_NAME||'familysync'});const[u]=await c.query(\"SHOW COLUMNS FROM users LIKE 'is_admin'\");const[mc]=await c.query(\"SHOW COLUMNS FROM member_credentials LIKE 'provider_type'\");const[ce]=await c.query(\"SHOW COLUMNS FROM calendar_events LIKE 'reminder_lead_minutes'\");const[ac]=await c.query(\"SHOW TABLES LIKE 'app_config'\");if(u.length&&mc.length&&ce.length&&ac.length){console.log('MIGRATION OK');process.exit(0)}console.error('MISSING',{u:u.length,mc:mc.length,ce:ce.length,ac:ac.length});process.exit(1)})().catch(e=>{console.error(e.message);process.exit(1)})"</automated>
</verify>
<acceptance_criteria>
- A new migration `.sql` file exists under `apps/api/src/db/migrations/` (sequential after `0000_baseline.sql`) and `meta/_journal.json` references it.
- The generated SQL contains NO `DROP TABLE`, `DROP COLUMN`, or `TRUNCATE` statement: `grep -v '^--' <migration.sql> | grep -ciE 'drop (table|column)|truncate'` returns 0.
- The live dev MariaDB query above prints `MIGRATION OK` and exits 0 — `users.is_admin`, `member_credentials.provider_type`, the `member_credentials` unique on `user_id`, `calendar_events.reminder_lead_minutes`, and the `app_config` table all exist.
- `db:push` was NOT run (no push in command history for this task).
- Both commands were actually RUN this task: `pnpm --filter @familysync/api db:generate` produced a new migration `.sql` file under `apps/api/src/db/migrations/` (sequential after `0000_baseline.sql`) with a matching `meta/_journal.json` entry, and `pnpm --filter @familysync/api db:migrate` (with `DB_HOST=127.0.0.1` + `.env` creds) applied it to the live dev DB. tsc/build passing is explicitly NOT accepted as proof.
- The generated migration `.sql` contains NO `DROP`/`TRUNCATE` statement: `grep -v '^--' <migration.sql> | grep -ciE 'drop (table|column)|truncate'` returns 0 (guards the false-destructive-diff trap).
- The live dev MariaDB now has the columns/table: the mysql2 query above prints `MIGRATION OK` and exits 0 — `SHOW COLUMNS FROM users LIKE 'is_admin'`, `SHOW COLUMNS FROM member_credentials LIKE 'provider_type'`, `SHOW COLUMNS FROM calendar_events LIKE 'reminder_lead_minutes'`, and `SHOW TABLES LIKE 'app_config'` each return a row, plus the `member_credentials` unique on `user_id` exists.
- `db:push` / `drizzle-kit push` was NOT run (no push in command history for this task).
</acceptance_criteria>
<done>The v1.1 migration is generated (additive-only), committed, and applied to the live dev DB; all new columns/table verified present by a real DB query.</done>
<done>The v1.1 migration is generated (additive-only) by an actual db:generate run, committed, and applied to the live dev DB by an actual db:migrate run; all new columns/table verified present by a real DB query (not tsc).</done>
</task>
<task type="execute">
@@ -175,7 +175,7 @@ This plan creates the following new symbols/files (excluded from drift verificat
<verification>
- `pnpm --filter @familysync/api exec tsc --noEmit` passes (schema typechecks).
- The live DB query in Task 2 prints `MIGRATION OK`.
- The live DB query in Task 2 prints `MIGRATION OK` (proof the migration actually ran — tsc alone is NOT proof).
- The generated migration SQL is additive-only (no DROP/TRUNCATE).
- `grep -c is_admin apps/pwa/e2e/global-setup.ts` >= 1.
</verification>
@@ -190,3 +190,5 @@ This plan creates the following new symbols/files (excluded from drift verificat
<output>
Create `.planning/phases/10-admin-role-settings/10-01-SUMMARY.md` when done.
</output>
</content>
</invoke>