You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Audit found that several columns declared in 0002_fresh_george_stacy.sql
never landed on prod because that migration uses CREATE TABLE IF NOT EXISTS
for tables already created by 0000. Side effects observed:
- escrow_transactions was missing retry_count/last_error in prod, so the
Phase-2 on-chain-failure rollback in releaseEscrow tried to UPDATE
nonexistent columns and itself errored, leaving escrows stuck at
RELEASING with no retry metadata.
- portfolio_items was missing category, completion_time, requester_rating,
is_pinned, display_order — surfaces silently coerced to NULL.
- agent_ratings score columns were integer in prod, real in schema. The
weighted overallScore = q*0.3 + s*0.2 + ... computation got truncated
on every rating insert.
- pgvector dim mismatch: nomic-embed-text-v1.5 produces 768-dim vectors
but schema/code declared vector(1536). Embedding writes are wrapped in
.catch(console.error), so every embed silently 500'd, leaving every
description_embedding NULL and degrading invitation matching to a no-op.
Changes:
- packages/api/src/services/embeddings.ts: drop padTo1536, return 768
native; export EMBEDDING_DIM constant.
- packages/api/src/db/schema.ts: vector(1536) → vector(768) on agents,
agent_skills, tasks.
- packages/api/drizzle/0005_next_dragon_lord.sql: corrective migration.
ADD COLUMN IF NOT EXISTS for the missing escrow/portfolio columns,
ALTER COLUMN ... USING ::real for the score widenings, DROP+ADD for
the vector dim change (pgvector cannot ALTER dimensions in place).
Idempotent against fresh databases that already have the columns.
- CLAUDE.md: rewrite the schema-change workflow with the lessons —
review generated SQL for IF NOT EXISTS pitfalls, audit prod drift
before assuming migrations match prod state, document destructive-op
guards and the integration-test gate. Add Observability section
covering traceOp + initTelemetry wiring.
Verified: tracked migrations 0000-0005 apply cleanly to a fresh DB
(escrow_transactions has retry_count/last_error, vectors are 768,
agent_ratings scores are real). All 130 unit tests + 8 integration
tests green post-migration.
PostgreSQL 16 with pgvector. Schema defined in `packages/api/src/db/schema.ts` using Drizzle ORM.
33
+
PostgreSQL 16 with pgvector. Schema defined in `packages/api/src/db/schema.ts` using Drizzle ORM. Embeddings use the nomic-embed-text-v1.5 model — pgvector columns are `vector(768)` and must match exactly.
2. Run `pnpm --filter @swarmdock/api db:generate` to create a SQL migration in `packages/api/drizzle/`
48
-
3. Review the generated SQL, then commit it alongside the schema change
49
-
4. On deploy, `start.sh` runs the tracked migrations automatically via `db:migrate`
50
-
51
-
**IMPORTANT: Migrations auto-deploy.** The Dockerfile copies `packages/api/drizzle/` into the production image and `start.sh` runs `db:migrate` on every container start. Any new migration files pushed to `main` will be applied to production on next deploy. Always verify schema changes compile (`pnpm type-check`) and review generated SQL before pushing.
-`pnpm --filter @swarmdock/api db:studio` — open Drizzle Studio
44
+
45
+
### Schema change workflow
46
+
47
+
1. Edit `packages/api/src/db/schema.ts`.
48
+
2. Run `pnpm --filter @swarmdock/api db:generate` to create a SQL file in `packages/api/drizzle/`.
49
+
3.**Review the generated SQL.** drizzle-kit's defaults can be wrong:
50
+
- It emits `CREATE TABLE IF NOT EXISTS` for new tables. If a same-named table already exists in any environment, the new columns silently do not apply — replace with `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` for any column added to an existing table.
51
+
-`ALTER COLUMN ... SET DATA TYPE vector(N)` between different dimensions is a no-op or errors in pgvector. Replace with `DROP COLUMN IF EXISTS` + `ADD COLUMN ... vector(N)` (data loss is real — guard or backfill).
52
+
- Type widenings (e.g. integer → real) need an explicit `USING <expr>::real` cast or Postgres rejects the change.
53
+
4.**Audit prod drift before assuming migrations match prod state.** drizzle generates from the snapshot in `drizzle/meta/`, which is whatever the schema looked like at the previous generate — not what is actually in the prod DB. To check, dump prod and a fresh schema-derived DB, then `comm -23` the column lists:
54
+
```bash
55
+
render psql swarmdock-db -c "COPY (SELECT table_name||'|'||column_name||'|'||udt_name FROM information_schema.columns WHERE table_schema='public' ORDER BY 1,2) TO STDOUT"> /tmp/prod-cols.txt
If prod is missing columns the schema expects, hand-write a corrective `ALTER TABLE` migration before shipping any new feature that depends on them.
60
+
5. Verify the migration end-to-end against a fresh DB: `DROP DATABASE`, run `db:migrate` on it, then run integration tests.
61
+
6. Commit the migration alongside the schema change.
62
+
63
+
### Production deploy
64
+
65
+
**Migrations auto-apply on every container restart.** The Dockerfile copies `packages/api/drizzle/` into the image and `start.sh` runs `db:migrate` before booting the API. Any new migration file merged to `main` will execute against prod within minutes of the next Render deploy. Before pushing:
66
+
67
+
-`pnpm type-check` and `pnpm --filter @swarmdock/api test` must be green.
68
+
-`pnpm --filter @swarmdock/api test:integration` must be green (real-Postgres exercise of the schema and escrow state machine).
69
+
- Migration is reviewed for destructive operations (`DROP COLUMN`, `ALTER TYPE` without `USING`, `TRUNCATE`).
70
+
71
+
If a migration needs to backfill or run in a long-running step, it must be split: ship the schema change first (idempotent, additive), backfill from a worker or one-off script, then ship the constraint/cleanup in a follow-up migration.
# Real-Postgres integration tests (escrow state machine, FOR UPDATE locks,
115
+
# 3-phase commit). Creates `swarmdock_test` DB and applies the live schema
116
+
# via drizzle-kit push; truncates between tests. Requires `docker compose
117
+
# up -d postgres`.
118
+
pnpm --filter @swarmdock/api test:integration
119
+
120
+
# Smoke test API
86
121
curl http://localhost:3100/api/v1/health
87
122
```
88
123
124
+
## Observability
125
+
126
+
OpenTelemetry traces are wired via `packages/api/src/lib/telemetry.ts`:
127
+
128
+
- HTTP middleware spans on every request (`packages/api/src/middleware/otel.ts`).
129
+
-`traceOp(name, attrs, fn)` helper wraps high-value business ops: `escrow.fund`, `escrow.release`, `escrow.refund`, `identity.issueAAT`, `quality.verify`. Add new spans by importing `traceOp` and wrapping the function body.
130
+
- Both `index.ts` and `worker.ts` call `initTelemetry()` at the very top before any other import — required for the auto-instrumentation to monkey-patch correctly.
131
+
- Export is gated on `OTEL_EXPORTER_OTLP_ENDPOINT`. Local default points at the `jaeger` service in docker-compose; UI at http://localhost:16686. Production env var is currently unset, so spans are dropped — set it on the Render web + worker services to enable.
132
+
89
133
## Related Projects
90
134
91
135
-**SwarmClaw** (`../swarmclaw`) — Agent runtime and control plane. See `SWARMDOCK.md` for integration.
0 commit comments