Wave 1: schema push (service_id nullable + custom_label). Wave 2 (parallel): catalog CRUD page + quote builder tab. Wave 3: E2E human verification checkpoint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.8 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03 | 01 | execute | 1 |
|
true |
|
|
Purpose: All subsequent plans (Wave 2) reference custom_label and insert rows with service_id = null. Without this push, the DB will reject those inserts with a column-not-found or NOT NULL constraint error.
Output: Updated src/db/schema.ts and a successful drizzle-kit push confirmation.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@/Users/simonecavalli/IAMCAVALLI/.planning/PROJECT.md @/Users/simonecavalli/IAMCAVALLI/.planning/ROADMAP.md ```typescript export const quote_items = pgTable("quote_items", { id: text("id").primaryKey().$defaultFn(() => nanoid()), client_id: text("client_id") .notNull() .references(() => clients.id, { onDelete: "cascade" }), service_id: text("service_id") .notNull() // <-- REMOVE .notNull() .references(() => service_catalog.id, { onDelete: "restrict" }), quantity: numeric("quantity", { precision: 10, scale: 2 }).notNull(), unit_price: numeric("unit_price", { precision: 10, scale: 2 }).notNull(), subtotal: numeric("subtotal", { precision: 10, scale: 2 }).notNull(), // custom_label missing — ADD after subtotal }); ```export const quote_items = pgTable("quote_items", {
id: text("id").primaryKey().$defaultFn(() => nanoid()),
client_id: text("client_id")
.notNull()
.references(() => clients.id, { onDelete: "cascade" }),
service_id: text("service_id")
.references(() => service_catalog.id, { onDelete: "restrict" }), // nullable — no .notNull()
quantity: numeric("quantity", { precision: 10, scale: 2 }).notNull(),
unit_price: numeric("unit_price", { precision: 10, scale: 2 }).notNull(),
subtotal: numeric("subtotal", { precision: 10, scale: 2 }).notNull(),
custom_label: text("custom_label"), // new field
});
export const quoteItemsRelations = relations(quote_items, ({ one }) => ({
client: one(clients, { fields: [quote_items.client_id], references: [clients.id] }),
service: one(service_catalog, {
fields: [quote_items.service_id],
references: [service_catalog.id],
}),
}));
export type QuoteItem = typeof quote_items.$inferSelect;
// QuoteItem.service_id will be: string | null
// QuoteItem.custom_label will be: string | null
Change 1 — Remove .notNull() from service_id (per D-03 from CONTEXT.md):
Before:
service_id: text("service_id")
.notNull()
.references(() => service_catalog.id, { onDelete: "restrict" }),
After:
service_id: text("service_id")
.references(() => service_catalog.id, { onDelete: "restrict" }),
Change 2 — Add custom_label field after the subtotal line:
custom_label: text("custom_label"),
No other changes to the file. The quoteItemsRelations block does NOT need to change.
After the edit, run npx tsc --noEmit to confirm zero TypeScript errors before pushing.
cd /Users/simonecavalli/IAMCAVALLI && grep -v '^//' src/db/schema.ts | grep -c 'custom_label: text("custom_label")'
Expected: 1
<automated>cd /Users/simonecavalli/IAMCAVALLI && grep -A3 'service_id: text("service_id")' src/db/schema.ts | grep -c 'notNull'</automated>
Expected: 0 (notNull must be gone from service_id)
<automated>cd /Users/simonecavalli/IAMCAVALLI && npx tsc --noEmit 2>&1 | head -20</automated>
Expected: no output (zero errors)
`src/db/schema.ts` compiles with zero TypeScript errors. `service_id` has no `.notNull()`. `custom_label: text("custom_label")` is present in the quote_items table definition.
Task 2: [BLOCKING] Push schema changes to Neon database
- /Users/simonecavalli/IAMCAVALLI/.env.local (verify DATABASE_URL is set before running push)
- /Users/simonecavalli/IAMCAVALLI/drizzle.config.ts (verify push config points to correct schema)
— (no source files modified; runs drizzle-kit against live DB)
Run drizzle-kit push with the .env.local DATABASE_URL loaded:
cd /Users/simonecavalli/IAMCAVALLI
set -a && source .env.local && set +a && npx drizzle-kit push
When prompted to confirm schema changes, accept all changes. The push will:
- DROP NOT NULL constraint from
quote_items.service_id - ADD COLUMN
custom_label texttoquote_items
If the push fails with "column already exists" for custom_label, the column was already added in a prior run — this is safe to ignore. Verify the column exists by checking the push output or running a quick query.
Do NOT skip this task. Wave 2 plans cannot execute correctly without the DB columns existing.
cd /Users/simonecavalli/IAMCAVALLI && set -a && source .env.local && set +a && npx drizzle-kit push 2>&1 | tail -5
Expected: Output contains "No changes" or "Changes applied" — either confirms the schema is in sync.
drizzle-kit push exits without error. The live Neon DB has quote_items.service_id as nullable and quote_items.custom_label text column present.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Schema file → Neon DB | drizzle-kit push executes DDL against the live database; misconfigured DATABASE_URL would push to wrong environment |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-03-01-01 | Tampering | drizzle-kit push | mitigate | Always load DATABASE_URL from .env.local (not hardcoded); verify .env.local exists before running push |
| T-03-01-02 | Denial of Service | Neon DB DDL | accept | Schema changes are additive (ADD COLUMN, DROP NOT NULL) — no data loss risk; onDelete: "restrict" prevents orphaned quote_items |
| </threat_model> |
<success_criteria>
src/db/schema.tshas nullableservice_idandcustom_label: text("custom_label")in quote_items- TypeScript compiles with zero errors
- drizzle-kit push confirms schema is synced to Neon DB
- Wave 2 plans can safely reference
custom_labeland insert rows withservice_id = null</success_criteria>