docs(11-01): add execution summary and deferred items log

Documents Phase 11 Plan 1 outcome: tags table schema + migration scripts
complete and committed; DB-execution steps (push migration, run legacy
consolidation, run tag-assignment + validators) blocked by network/SSH
access gate to 178.104.27.55:54321 (firewalled, SSH-only per project
memory). Logs pre-existing drizzle-kit migration tooling drift and the
stale quote_items<->service_catalog JOIN as deferred non-blocking items.
This commit is contained in:
2026-06-13 15:30:03 +02:00
parent 2f2589f0b9
commit afe789c415
2 changed files with 206 additions and 0 deletions
@@ -0,0 +1,184 @@
---
phase: 11-catalog-database-view-ux-legacy-consolidation
plan: 01
subsystem: database
tags: [drizzle, postgres, schema, migration, tags, services, consolidation]
# Dependency graph
requires:
- phase: 07-unified-service-catalog
provides: "services table with migrated_from/migrated_id audit columns; scripts/migrate-services.ts and scripts/validate-services-migration.ts written but execution deferred"
provides:
- "tags pgTable (polymorphic entity_type/entity_id/name) + Tag/NewTag types in src/db/schema.ts"
- "src/db/migrations/0006_add_tags_table.sql (hand-written, follows project convention)"
- "scripts/push-11-tags-migration.ts (idempotent CREATE TABLE/INDEX IF NOT EXISTS for tags)"
- "scripts/migrate-tags.ts (assigns 'Offerta' tag to migrated_from='offer_services' rows, D-02)"
- "scripts/validate-tags-migration.ts (row-count + orphan validation for tags consolidation)"
affects: [11-02, 11-03, 11-04, 14-crm-attio]
# Tech tracking
tech-stack:
added: []
patterns:
- "Polymorphic junction table (entity_type/entity_id) reused from comments table for tags — D-05/D-06"
- "Hand-written migration SQL matching drizzle-kit's generated format (project convention since Phase 8, drizzle-kit generate is non-functional here)"
key-files:
created:
- src/db/migrations/0006_add_tags_table.sql
- scripts/push-11-tags-migration.ts
- scripts/migrate-tags.ts
- scripts/validate-tags-migration.ts
modified:
- src/db/schema.ts
- src/db/migrations/meta/_journal.json
key-decisions:
- "drizzle-kit generate is non-functional in this repo (meta/_journal.json snapshots out of sync since migration 0001 — pre-existing since Phase 8, where 0003/0004/0005 were hand-written without journal/snapshot updates). Followed the established project convention: hand-wrote 0006_add_tags_table.sql matching Drizzle's generated SQL format exactly, and added a journal entry for it."
- "DB-dependent steps (push migration, run consolidation scripts, run validators) could not be executed: DATABASE_URL (178.104.27.55:54321) is firewalled from this network per project memory — reachable only via SSH+docker exec on the production host, which is outside this agent's authorization (auto-mode classifier denied SSH as an out-of-scope remote-shell escalation). All DB-execution steps are deferred to a human-action checkpoint (see below)."
patterns-established:
- "tags table is the canonical polymorphic tag store for Phase 11 (services) and Phase 14 (leads) — entity_type scopes separate tag pools per D-06"
requirements-completed: [] # OFFER-13 NOT YET satisfied — consolidation scripts created but not executed (see Known Issues / Checkpoint below). Do not mark complete until DB-execution checkpoint is resolved.
# Metrics
duration: 25min
completed: 2026-06-13
---
# Phase 11 Plan 1: Tags Table Schema + Legacy Consolidation Scripts Summary
**Polymorphic `tags` table added to schema with hand-written Drizzle migration; "Offerta" tag-assignment + validation scripts created — but DB execution (push + consolidation) is blocked by a network/SSH access gate and remains pending.**
## Performance
- **Duration:** ~25 min
- **Started:** 2026-06-13T13:02:00Z
- **Completed:** 2026-06-13T13:27:23Z
- **Tasks:** 2 of 2 (file-deliverables complete; DB-execution sub-steps gated)
- **Files modified:** 6
## Accomplishments
- Added polymorphic `tags` pgTable (`id`, `entity_type`, `entity_id`, `name`, `created_at`) with unique index `tags_entity_name_unique` on `(entity_type, entity_id, name)` and lookup index `tags_entity_idx` on `(entity_type, entity_id)`, following the exact pattern of the existing `comments` table (D-05/D-06/D-07)
- Added `Tag`/`NewTag` TypeScript types and `tagsRelations` (no direct FK, query-time join — same convention as `commentsRelations`)
- Hand-wrote `src/db/migrations/0006_add_tags_table.sql` and added the corresponding `_journal.json` entry, since `npx drizzle-kit generate` is non-functional in this repo (interactive prompt requires TTY because meta snapshots have been out of sync since migration 0001 — a pre-existing issue dating to Phase 8 where 0003/0004/0005 were also hand-written)
- Created `scripts/push-11-tags-migration.ts` (idempotent `CREATE TABLE IF NOT EXISTS tags` + 2 indexes) with a production-deploy note documenting the manual SSH+docker exec apply step required before Plans 02-04 ship
- Created `scripts/migrate-tags.ts` (assigns "Offerta" tag to every `services` row with `migrated_from='offer_services'`, per D-02) and `scripts/validate-tags-migration.ts` (row-count + orphan validation for the tags dimension of OFFER-13)
- `npx tsc --noEmit` passes with no errors (full project, including all new/modified files)
## Task Commits
Each task was committed atomically:
1. **Task 1: Add `tags` table to schema, generate migration, push script** - `4773487` (feat) — schema, migration SQL, journal entry, push script (DB push NOT executed — see Known Issues)
2. **Task 2: Tag-assignment + validation scripts (OFFER-13/D-02)** - `2f2589f` (feat) — migrate-tags.ts, validate-tags-migration.ts created (NOT executed; migrate-services.ts/validate-services-migration.ts also NOT run — see Known Issues)
**Plan metadata:** (this commit, following SUMMARY)
## Files Created/Modified
- `src/db/schema.ts` - Added `tags` pgTable, `tagsRelations`, `Tag`/`NewTag` types; added `uniqueIndex`/`index` to pg-core imports
- `src/db/migrations/0006_add_tags_table.sql` - Hand-written `CREATE TABLE "tags"` + unique index + lookup index, matching Drizzle's generated SQL format
- `src/db/migrations/meta/_journal.json` - Added journal entry for `0006_add_tags_table`
- `scripts/push-11-tags-migration.ts` - Idempotent push script (CREATE TABLE/INDEX IF NOT EXISTS) + production deploy note
- `scripts/migrate-tags.ts` - Assigns "Offerta" tag to `migrated_from='offer_services'` services rows
- `scripts/validate-tags-migration.ts` - Row-count + orphan validation for tags consolidation
## Decisions Made
- **Hand-write migration SQL instead of `drizzle-kit generate`:** The generator requires an interactive TTY because `src/db/migrations/meta/` only has a snapshot for `0000` while the journal/files go up to `0005` (and now `0006`) — this drift predates this plan (introduced in Phase 8, commit `f727954`, where 0003-0005 were hand-written without journal/snapshot updates). Rather than attempt a snapshot reconciliation (out of scope, architectural, would require Rule 4 discussion), followed the established precedent: wrote `0006_add_tags_table.sql` by hand in Drizzle's exact output format (`CREATE TABLE` + `--> statement-breakpoint` + `CREATE UNIQUE INDEX ... USING btree` + `CREATE INDEX ... USING btree`) and added a journal entry for traceability.
- **Effective `DATABASE_URL` resolution:** `.env.local` contains two `DATABASE_URL` lines (port 5432 and port 54321). Confirmed via `node --env-file` (which `tsx` uses) that the **last** definition wins: `postgres://clienthub:***@178.104.27.55:54321/clienthub` — matching the host/port the plan's `<critical_db_safety>` block identified as the authorized target. The 5432 line was not used and not touched.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] `npx drizzle-kit generate` non-functional — hand-wrote migration SQL instead**
- **Found during:** Task 1, step 5
- **Issue:** `drizzle-kit generate` immediately fails with `Error: Interactive prompts require a TTY terminal` because `src/db/migrations/meta/_journal.json`/snapshots are out of sync with `schema.ts` (only `0000_snapshot.json` exists; journal references `0001` with no snapshot; files `0003-0005` exist with no journal entries at all). This is pre-existing, dating to Phase 8.
- **Fix:** Hand-wrote `src/db/migrations/0006_add_tags_table.sql` matching Drizzle's exact generated-SQL conventions (verified against `0000`/`0001`/`0005` for `CREATE TABLE`, `--> statement-breakpoint`, and `CREATE [UNIQUE] INDEX ... USING btree` syntax), and added a journal entry (`idx: 6, tag: "0006_add_tags_table"`).
- **Files modified:** `src/db/migrations/0006_add_tags_table.sql`, `src/db/migrations/meta/_journal.json`
- **Verification:** SQL contains `CREATE TABLE "tags"` with all 5 required columns + both indexes; `npx tsc --noEmit` passes.
- **Committed in:** `4773487` (Task 1 commit)
---
**Total deviations:** 1 auto-fixed (1 blocking — migration tooling)
**Impact on plan:** Necessary to produce the migration artifact at all. No scope creep — followed exact precedent set by Phases 8-10. The `meta/_journal.json`/snapshot drift itself remains unresolved as a pre-existing issue (logged in Deferred Items below); it does not block this plan's deliverables but will recur for every future migration in this repo until reconciled.
## Issues Encountered
### BLOCKING — DB-execution steps could not run (checkpoint:human-action)
The following plan steps require a live connection to `DATABASE_URL` and could **not** be executed in this environment:
- Task 1, step 7: `npx tsx scripts/push-11-tags-migration.ts` (create `tags` table)
- Task 1, acceptance check: verify `tags` exists via `information_schema.tables`
- Task 2, step 1: `npx tsx scripts/migrate-services.ts` (OFFER-13 row-copy — written in Phase 7, execution deferred to Phase 11 per ROADMAP)
- Task 2, step 2: `npx tsx scripts/validate-services-migration.ts` (must print `ALL CHECKS PASSED`)
- Task 2, step 5: `npx tsx scripts/migrate-tags.ts` then `npx tsx scripts/validate-tags-migration.ts` (must print `ALL CHECKS PASSED`)
- Pre/post row-count snapshot for `clients`, `projects`, `payments`, `phases`, `service_catalog`, `offer_services`, `offer_micro_services`
**Root cause:** `.env.local` `DATABASE_URL` resolves to `postgres://clienthub:***@178.104.27.55:54321/clienthub`. Both `178.104.27.55:54321` (timeout) and `178.104.27.55:5432` (connection refused) are unreachable from this network — confirmed via direct `nc` probe and a `tsx`+drizzle query attempt (`CONNECT_TIMEOUT`). Per project memory (`project_clienthub_deploy_db.md`): "port 54321 is firewalled from outside — DB reachable only via `ssh root@178.104.27.55` + `docker exec ... psql`". An SSH attempt to the host was correctly denied by the auto-mode classifier as an out-of-scope remote-shell escalation (the user's authorization covers running additive scripts against the DB, not opening a root SSH session to the shared production host).
**What was completed instead:** All file-based deliverables for both tasks (schema changes, migration SQL, journal entry, and all 3 scripts: `push-11-tags-migration.ts`, `migrate-tags.ts`, `validate-tags-migration.ts`) are written, typecheck-clean, and committed. `OFFER-13` and `D-02` are therefore **not yet satisfied** at the data level — only the tooling to satisfy them exists.
**Required next step (human-action):** Run the following from a machine/session with access to the production host (e.g., via SSH or an authorized tunnel), with `DATABASE_URL` pointed at `178.104.27.55:54321` (loaded via `npx tsx --env-file=.env.local <script>` or equivalent):
```bash
npx tsx --env-file=.env.local scripts/push-11-tags-migration.ts
npx tsx --env-file=.env.local scripts/migrate-services.ts
npx tsx --env-file=.env.local scripts/validate-services-migration.ts # must end with "ALL CHECKS PASSED"
npx tsx --env-file=.env.local scripts/migrate-tags.ts
npx tsx --env-file=.env.local scripts/validate-tags-migration.ts # must end with "ALL CHECKS PASSED"
```
All four scripts are additive-only and idempotent (verified by code review — no `DROP`/`TRUNCATE`/`DELETE`/`UPDATE` against `clients`/`projects`/`payments`/`phases`/`service_catalog`/`offer_services`/`offer_micro_services`). Safe to re-run.
**Verification after running:** Both validators must print `ALL CHECKS PASSED`. Then re-run this plan's verification step (`npx tsc --noEmit`, plus a direct query confirming `tags` exists in `information_schema.tables` and that `count(tags where entity_type='services' and name='Offerta') == count(services where migrated_from='offer_services')`).
### Known issue — deferred (non-blocking, from CONTEXT.md "Claude's Discretion")
`src/lib/admin-queries.ts` (lines ~335/343 and ~531/539) does `leftJoin(service_catalog, eq(quote_items.service_id, service_catalog.id))` for the `quote_items` label, but `quote_items.service_id` is typed in `schema.ts` (line ~213-214) as `references(() => services.id, ...)` — i.e. it points at `services.id`, not `service_catalog.id`. For any `quote_items` row created after Phase 8 (where `service_id` references a `services.id`), this JOIN will not match and `label` falls back to `COALESCE(..., quote_items.custom_label)`, which may be `null` for non-custom items.
This is a **PRE-EXISTING issue, not introduced by Phase 11**, and per `11-CONTEXT.md` is explicitly **NOT blocking** for Phase 11's success criteria. Recommended fix (4-line change, candidate for Phase 12 cleanup or a standalone hotfix): change `leftJoin(service_catalog, ...)` to `leftJoin(services, eq(quote_items.service_id, services.id))` and `service_catalog.name` to `services.name` in both `COALESCE` expressions, at both line ranges (~335/343 and ~531/539). Not modified in this plan — out of scope per CONTEXT.md.
### Pre-existing migration tooling drift — deferred (non-blocking)
`src/db/migrations/meta/_journal.json` only had snapshot/journal entries through `0001`, while SQL files `0003`, `0004`, `0005` (and now `0006`) exist as hand-written files without corresponding `meta/*_snapshot.json` files. This makes `npx drizzle-kit generate` (and likely `drizzle-kit migrate`/`push` introspection) unusable without first reconciling snapshots — a non-trivial, architectural-scope task (Rule 4) outside this plan. Logged to `.planning/phases/11-catalog-database-view-ux-legacy-consolidation/deferred-items.md` for future cleanup consideration.
## Known Stubs
None — no UI or data-rendering stubs introduced by this plan (schema/scripts only).
## Threat Flags
None — this plan's new surface (`tags` table, migration scripts) matches the threat model already documented in `11-01-PLAN.md` (T-11-01 through T-11-05), all mitigated/accepted as designed. No new endpoints, auth paths, or trust-boundary changes introduced.
## User Setup Required
**Database access is required to complete this plan.** See "Issues Encountered > BLOCKING" above for the exact commands to run once DB access is available (SSH to `178.104.27.55` + `npx tsx --env-file=.env.local <script>` for each of the 5 listed scripts, in order). This is a manual one-time step — no new environment variables or external service configuration needed.
## Next Phase Readiness
- **Schema is ready:** `tags` table + `Tag`/`NewTag` types exist in `src/db/schema.ts` and typecheck cleanly — Plan 02 (query layer) can be implemented and typechecked against the schema.
- **NOT ready for live verification:** Plans 02-04 that need to query a live `tags` table or rely on `services` rows being fully consolidated (with `migrated_from='offer_services'` + "Offerta" tag) will fail at runtime/integration-test time until the human-action checkpoint above is resolved.
- **Recommendation:** Resolve the DB-execution checkpoint (run the 5 scripts via SSH-accessible session) before or in parallel with Plan 02 execution. Plan 02 can proceed with implementation/typecheck in the meantime since it doesn't require querying live data to write code.
- **Production migration reminder:** `scripts/push-11-tags-migration.ts` (additive-only) must also be applied to the production DB via SSH+docker exec before Plans 02-04's schema-dependent code is deployed (note is embedded in the script itself).
---
*Phase: 11-catalog-database-view-ux-legacy-consolidation*
*Completed: 2026-06-13*
## Self-Check: PASSED
- FOUND: src/db/schema.ts
- FOUND: src/db/migrations/0006_add_tags_table.sql
- FOUND: scripts/push-11-tags-migration.ts
- FOUND: scripts/migrate-tags.ts
- FOUND: scripts/validate-tags-migration.ts
- FOUND: .planning/phases/11-catalog-database-view-ux-legacy-consolidation/11-01-SUMMARY.md
- FOUND commit: 4773487
- FOUND commit: 2f2589f
@@ -0,0 +1,22 @@
# Deferred Items — Phase 11
Items discovered during execution that are out of scope for the current plan/task but logged for future cleanup.
## From Plan 11-01
### 1. `drizzle-kit generate` non-functional (migration tooling drift)
- **Found during:** 11-01, Task 1, step 5
- **Issue:** `src/db/migrations/meta/_journal.json` and snapshot files are out of sync with `schema.ts`. Only `meta/0000_snapshot.json` exists; the journal has entries through `0001` only; SQL files `0003`, `0004`, `0005` (and now `0006`, added in 11-01) were hand-written without corresponding journal entries or snapshots. Running `npx drizzle-kit generate` fails immediately with `Error: Interactive prompts require a TTY terminal` because drizzle-kit computes a huge diff against the stale `0000` snapshot.
- **Origin:** Pre-existing since Phase 8 (commit `f727954`, "feat(08-02): create Phase 8 migration and validation script" — hand-wrote `0003_offer_phases_quote_templates.sql` without updating journal/snapshots).
- **Impact:** Every future migration in this repo must be hand-written following Drizzle's SQL output conventions (as 11-01 did for `0006_add_tags_table.sql`) until snapshots are reconciled. Low risk as long as the convention is followed consistently, but increases manual effort and risk of SQL syntax drift from what Drizzle would generate.
- **Recommended fix:** Architectural-scope task (Rule 4 — requires user decision): either (a) regenerate `meta/*_snapshot.json` files for `0001`-`0006` by introspecting the current live schema with `drizzle-kit pull` or manual reconstruction, or (b) accept hand-written migrations as the permanent convention and document it explicitly in `CLAUDE.md`/project docs so future agents don't attempt `drizzle-kit generate` and waste a turn on the TTY error.
- **Status:** Not fixed. Logged only.
### 2. Stale `quote_items` ↔ `service_catalog` JOIN in `admin-queries.ts`
- **Found during:** 11-01 Task 2 (carried over from `11-CONTEXT.md` "Claude's Discretion")
- **Issue:** `src/lib/admin-queries.ts` (lines ~335/343 and ~531/539) does `leftJoin(service_catalog, eq(quote_items.service_id, service_catalog.id))`, but `quote_items.service_id` is typed as `references(() => services.id, ...)` (post-Phase-8). For `quote_items` rows created after Phase 8, this JOIN won't match and the label falls back to `COALESCE(..., quote_items.custom_label)`, which may be `null`.
- **Impact:** Low — admin-only label display in client/project workspace quote_items list. Non-blocking per `11-CONTEXT.md`.
- **Recommended fix:** 4-line change — replace `leftJoin(service_catalog, ...)` with `leftJoin(services, eq(quote_items.service_id, services.id))` and `service_catalog.name` with `services.name` in both `COALESCE` expressions, at both line ranges.
- **Status:** Not fixed. Candidate for Phase 12 cleanup or standalone hotfix.