feat(schema): AgentMessage + AgentMessageArchive voor s4m-queue (fase 1) #37

Merged
janpeter merged 6 commits from feat/agent-message-schema into main 2026-07-16 11:23:27 +02:00
Owner

Fase 1b van de s4m-queue-migratie naar de scrum4me-DB. Spec + plan: scrum4me-mcp docs/superpowers/{specs,plans}/2026-07-12-s4m-queue-*.

Waarom

De losse berichtenqueue s4m-queue (eigen DB ops_dashboard) wordt onderdeel van het platform. Deze PR definieert de modellen; de SQL-migratie zelf volgt in Scrum4Me (designated migrator).

Harde eis: de tabel-DDL moet identiek blijven aan s4m-queue/migrations/001_init.sql + 002_archive.sql — de CLI (raw SQL) en het Messages-dashboard blijven op deze tabellen werken en wisselen straks alléén van connection string.

Wat er in zit

Commit
61e75bb de modellen AgentMessage + AgentMessageArchive
fd00f03 doc: archief heeft bewust géén CHECK-constraints
d00c504 doc: relatiepaar en index-map: als load-bearing
9548ec7 __tests__/agent-message-schema.test.ts — guard-test
062ec75 fix: onware verify-claim + guard op de kolomset
2df5d0e doc: notitie over spec-telling weg (spec gecorrigeerd)

De DDL is byte-identiek sinds 61e75bb — alles daarna is documentatie en guard.

DDL-equivalentie: geverifieerd, niet beredeneerd

Beide reviewers genereerden de DDL via Prisma en legden die naast de bronmigraties. De spec-reviewer tuigde daarvoor een wegwerp-Postgres 17 op, paste bron-DDL en Prisma-DDL toe in twee schema's en vergeleek via information_schema/pg_attribute op ordinal position: 34/34 kolommen equivalent op naam, type, nullability, default én volgorde.

Hij draaide bovendien de échte raw SQL van de CLI tegen de Prisma-gegenereerde tabellen: de push-INSERT (die id/meta/status/created_at weglaat en dus alle vier de defaults uitoefent), de claim-CTE verbatim uit src/db.ts, en het reply/FK-pad. Werkt.

Vier verschillen zijn representatief, geen defect: DEFAULT CURRENT_TIMESTAMP vs now() (zelfde functie), TIMESTAMPTZ(6) vs bare timestamptz (typmod 6 vs -1, beide microseconde, round-trip identiek), PK-naam agent_message_pkey (die kent Postgres zelf toe aan de inline PRIMARY KEY van de bron), en de afwezige CHECKs (bewust — die horen in de SQL-migratie).

Drie constructen zijn load-bearing en zien er niet zo uit

Dit is de kern van waarom hier een guard-test bij zit. Alle drie laten zich verwijderen met een groene prisma validate:

Weghalen Gevolg
replied_to/replies FK verdwijnt stilzwijgend uit de DDL
map: op de claim-index index heet agent_message_to_server_to_model_status_created_at_idx
onUpdate: NoAction Prisma emit ON UPDATE CASCADE i.p.v. NO ACTION

Het relatiepaar is het venijnigste: niets consumeert het (CLI en dashboard zijn raw SQL), dus het ziet eruit als twee ongebruikte velden in een model waarvan de comment zelf zegt dat alles via raw SQL loopt. Het bestaat uitsluitend om de FK te emitten. Alleen replies weghalen geeft wél P1012; het paar als geheel niet.

map: komt nergens anders in de 1326 regels voor — het ís dus de regel die eruitziet als een inconsistentie die iemand rechttrekt.

De guard-test

__tests__/agent-message-schema.test.ts, in het idioom van de twee precedenten in deze repo (claude-job-usage-schema.test.ts, model-price-rate-card-schema.test.ts). Vóór deze PR beschermde niets deze modellen (grep -rn "AgentMessage" __tests__/ → nul hits).

12 tests, elk aantoonbaar rood tegen een echte sabotage. prisma validate liet alle twaalf gewoon door:

Sabotage validate test
relatiepaar weg groen rood
map: weg groen rood
onUpdate: NoActionCascade groen rood
onDelete: SetNullCascade groen rood
@@map hernoemd groen rood
index-kolomvolgorde gewijzigd groen rood
@db.Uuid weg groen rood
meta-default weg groen rood
kolom hernoemd (from_serverfromServer) groen rood
kolom verwijderd groen rood
kolomvolgorde gewisseld groen rood
StringInt groen rood

De kolom-guard deelt één COLUMNS-lijst over beide modellen, wat 002's belofte "kolommen identiek aan agent_message" een uitvoerbare assertie maakt in plaats van een comment. De volgorde is bewust gepind: de cutover valideert information_schema.columns inclusief ordinal position, en cleanup.ts hing daaraan — een reorder is dus een echte faalmodus.

Bewuste keuzes

  • @db.Uuid / @db.Timestamptz(6) wijken af van de cuid/DateTime-huisstijl. Opzet: de DDL moet identiek blijven.
  • Géén @map per kolom, in afwijking van wat de spec oorspronkelijk vroeg: snake_case ís de huisstijl hier, dus veldnaam == kolomnaam en 34 @map-attributen zouden no-ops zijn. De spec is hierop gecorrigeerd.
  • CHECK-constraints staan niet in Prisma (die kent ze niet) maar in de SQL-migratie. Het archief krijgt er nul — ook geen type/source/status. De header van 002_archive.sql noemt bij de weglatingen alleen reply_link_matches_type en is daarmee onvolledig; dat staat nu expliciet in de doc zodat de migratie-auteur er niet drie te veel toevoegt.

Verificatie

  • npm run verify: exit 0, 21 bestanden / 231 tests (was 20/219)
  • prisma validate: groen, met sabotage-matrix aangetoond dat validate deze modellen echt uitoefent
  • prisma format laat beide nieuwe blokken byte-identiek (pre-existing drift elders met rust gelaten)

🤖 Generated with Claude Code

Fase 1b van de s4m-queue-migratie naar de scrum4me-DB. Spec + plan: `scrum4me-mcp` `docs/superpowers/{specs,plans}/2026-07-12-s4m-queue-*`. ## Waarom De losse berichtenqueue `s4m-queue` (eigen DB `ops_dashboard`) wordt onderdeel van het platform. Deze PR definieert de modellen; de SQL-migratie zelf volgt in Scrum4Me (designated migrator). **Harde eis:** de tabel-DDL moet identiek blijven aan `s4m-queue/migrations/001_init.sql` + `002_archive.sql` — de CLI (raw SQL) en het Messages-dashboard blijven op deze tabellen werken en wisselen straks alléén van connection string. ## Wat er in zit | Commit | | |---|---| | `61e75bb` | de modellen `AgentMessage` + `AgentMessageArchive` | | `fd00f03` | doc: archief heeft bewust géén CHECK-constraints | | `d00c504` | doc: relatiepaar en index-`map:` als load-bearing | | `9548ec7` | `__tests__/agent-message-schema.test.ts` — guard-test | | `062ec75` | fix: onware verify-claim + guard op de kolomset | | `2df5d0e` | doc: notitie over spec-telling weg (spec gecorrigeerd) | **De DDL is byte-identiek sinds `61e75bb`** — alles daarna is documentatie en guard. ## DDL-equivalentie: geverifieerd, niet beredeneerd Beide reviewers genereerden de DDL via Prisma en legden die naast de bronmigraties. De spec-reviewer tuigde daarvoor een wegwerp-Postgres 17 op, paste bron-DDL en Prisma-DDL toe in twee schema's en vergeleek via `information_schema`/`pg_attribute` op ordinal position: **34/34 kolommen equivalent** op naam, type, nullability, default én volgorde. Hij draaide bovendien de échte raw SQL van de CLI tegen de Prisma-gegenereerde tabellen: de push-`INSERT` (die id/meta/status/created_at weglaat en dus alle vier de defaults uitoefent), de claim-CTE verbatim uit `src/db.ts`, en het reply/FK-pad. Werkt. Vier verschillen zijn representatief, geen defect: `DEFAULT CURRENT_TIMESTAMP` vs `now()` (zelfde functie), `TIMESTAMPTZ(6)` vs bare `timestamptz` (typmod 6 vs -1, beide microseconde, round-trip identiek), PK-naam `agent_message_pkey` (die kent Postgres zelf toe aan de inline `PRIMARY KEY` van de bron), en de afwezige CHECKs (bewust — die horen in de SQL-migratie). ## Drie constructen zijn load-bearing en zien er niet zo uit Dit is de kern van waarom hier een guard-test bij zit. Alle drie laten zich verwijderen met een **groene** `prisma validate`: | Weghalen | Gevolg | |---|---| | `replied_to`/`replies` | FK verdwijnt stilzwijgend uit de DDL | | `map:` op de claim-index | index heet `agent_message_to_server_to_model_status_created_at_idx` | | `onUpdate: NoAction` | Prisma emit `ON UPDATE CASCADE` i.p.v. `NO ACTION` | Het relatiepaar is het venijnigste: niets consumeert het (CLI en dashboard zijn raw SQL), dus het ziet eruit als twee ongebruikte velden in een model waarvan de comment zelf zegt dat alles via raw SQL loopt. Het bestaat uitsluitend om de FK te emitten. Alleen `replies` weghalen geeft wél P1012; het paar als geheel niet. `map:` komt nergens anders in de 1326 regels voor — het ís dus de regel die eruitziet als een inconsistentie die iemand rechttrekt. ## De guard-test `__tests__/agent-message-schema.test.ts`, in het idioom van de twee precedenten in deze repo (`claude-job-usage-schema.test.ts`, `model-price-rate-card-schema.test.ts`). Vóór deze PR beschermde niets deze modellen (`grep -rn "AgentMessage" __tests__/` → nul hits). 12 tests, elk aantoonbaar rood tegen een echte sabotage. `prisma validate` liet **alle twaalf** gewoon door: | Sabotage | validate | test | |---|---|---| | relatiepaar weg | groen | rood | | `map:` weg | groen | rood | | `onUpdate: NoAction` → `Cascade` | groen | rood | | `onDelete: SetNull` → `Cascade` | groen | rood | | `@@map` hernoemd | groen | rood | | index-kolomvolgorde gewijzigd | groen | rood | | `@db.Uuid` weg | groen | rood | | `meta`-default weg | groen | rood | | kolom hernoemd (`from_server` → `fromServer`) | groen | rood | | kolom verwijderd | groen | rood | | kolomvolgorde gewisseld | groen | rood | | `String` → `Int` | groen | rood | De kolom-guard deelt één `COLUMNS`-lijst over beide modellen, wat 002's belofte "kolommen identiek aan agent_message" een uitvoerbare assertie maakt in plaats van een comment. De volgorde is bewust gepind: de cutover valideert `information_schema.columns` inclusief ordinal position, en `cleanup.ts` hing daaraan — een reorder is dus een echte faalmodus. ## Bewuste keuzes - `@db.Uuid` / `@db.Timestamptz(6)` wijken af van de cuid/`DateTime`-huisstijl. Opzet: de DDL moet identiek blijven. - **Géén `@map` per kolom**, in afwijking van wat de spec oorspronkelijk vroeg: snake_case ís de huisstijl hier, dus veldnaam == kolomnaam en 34 `@map`-attributen zouden no-ops zijn. De spec is hierop gecorrigeerd. - CHECK-constraints staan niet in Prisma (die kent ze niet) maar in de SQL-migratie. Het archief krijgt er **nul** — ook geen type/source/status. De header van `002_archive.sql` noemt bij de weglatingen alleen `reply_link_matches_type` en is daarmee onvolledig; dat staat nu expliciet in de doc zodat de migratie-auteur er niet drie te veel toevoegt. ## Verificatie - `npm run verify`: exit 0, 21 bestanden / 231 tests (was 20/219) - `prisma validate`: groen, met sabotage-matrix aangetoond dat validate deze modellen echt uitoefent - `prisma format` laat beide nieuwe blokken byte-identiek (pre-existing drift elders met rust gelaten) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(schema): verwijder notitie over spec-telling (spec is gecorrigeerd)
All checks were successful
CI / Verify (pull_request) Successful in 37s
2df5d0eefb
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
s4m-codex-reviewer left a comment

Verdict: REQUEST_CHANGES

Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.

Findings

  • error__tests__/agent-message-schema.test.ts:1: de nieuwe test importeert node:fs. De actieve productdoc Product-doc write closure in @shared (dep-clean, ESM) beschrijft de purity gate als een brede ban op bare/node: builtins, met alleen een gerichte uitzondering voor product-doc-*.ts. Daardoor is dit waarschijnlijk niet conform de product-standaard en kan npm run verify rood worden via scripts/verify-no-deps.sh. Los dit op zonder verboden builtin-import, of pas gate + productdoc expliciet mee aan als tests bewust buiten deze regel moeten vallen.

Samenvatting

De Prisma-modellen zelf volgen de gedocumenteerde rol van scrum4me-shared als canonieke schema-bron en vermijden migraties in deze repo. De extra schema-regressietests zijn inhoudelijk zinvol, maar de nieuwe node:fs dependency botst met de gedocumenteerde dependency-hygiene afspraak.

# Verdict: REQUEST_CHANGES Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden. ## Findings - **error** — `__tests__/agent-message-schema.test.ts:1`: de nieuwe test importeert `node:fs`. De actieve productdoc `Product-doc write closure in @shared (dep-clean, ESM)` beschrijft de purity gate als een brede ban op bare/`node:` builtins, met alleen een gerichte uitzondering voor `product-doc-*.ts`. Daardoor is dit waarschijnlijk niet conform de product-standaard en kan `npm run verify` rood worden via `scripts/verify-no-deps.sh`. Los dit op zonder verboden builtin-import, of pas gate + productdoc expliciet mee aan als tests bewust buiten deze regel moeten vallen. ## Samenvatting De Prisma-modellen zelf volgen de gedocumenteerde rol van `scrum4me-shared` als canonieke schema-bron en vermijden migraties in deze repo. De extra schema-regressietests zijn inhoudelijk zinvol, maar de nieuwe `node:fs` dependency botst met de gedocumenteerde dependency-hygiene afspraak.
Author
Owner

Reactie op de REQUEST_CHANGES: de node:fs-finding klopt niet

De finding stelt dat __tests__/agent-message-schema.test.ts:1 (import { readFileSync } from 'node:fs') de purity gate schendt en npm run verify rood kan maken. Dat is nagetrokken en weerlegd — hieronder de vier controles.

1. De gate scant lib/, niet __tests__/

scripts/verify-no-deps.sh zegt het in zijn eigen eerste regel:

# Fail if lib/ contains forbidden imports.
...
LIB_DIR="${LIB_DIR:-lib}"

Elke grep -rEn in het script draait over "$LIB_DIR". __tests__/ valt buiten scope. Dat is ook de bedoeling van de regel: de gate bewaakt het dependency-oppervlak van wat er verscheept wordt. Een test die prisma/schema.prisma als tekst inleest, kan lib/ niet vervuilen — hij importeert er niets uit en wordt niet meegebundeld.

2. Beide precedent-tests doen exact hetzelfde en staan al op main

$ git show origin/main:__tests__/claude-job-usage-schema.test.ts | head -1
import { readFileSync } from 'node:fs'

$ git show origin/main:__tests__/model-price-rate-card-schema.test.ts | head -1
import { readFileSync } from 'node:fs'

De nieuwe test volgt bewust dat gevestigde idioom. Was dit een overtreding, dan stond main al rood sinds die twee tests landden.

3. lib/ is dep-clean

$ grep -rEn "from ['\"]node:" lib --include='*.ts' | grep -v "product-doc-"
(geen resultaten)

De enige node:-imports in lib/ zijn node:crypto/node:path in product-doc-*.ts — precies de gedocumenteerde uitzondering.

4. npm run verify is groen, gemeten op deze branch

OK: lib/ is dep-clean
Test Files  21 passed (21)
     Tests  231 passed (231)
exit 0

Inclusief bash scripts/verify-no-deps.sh én bash __tests__/scripts/verify-no-deps.test.sh, die allebei in het verify-script zitten.

Conclusie

De finding is een hypothese ("waarschijnlijk niet conform", "kan rood worden") die met één commando te toetsen was. De productdoc beschrijft de gate zoals hij geïmplementeerd is — over lib/ — en niet als een repo-brede ban op node:-builtins.

Geen wijziging. De test aanpassen om node:fs te vermijden zou hem óf onmogelijk maken (het schema is een bestand; je moet het lezen) óf laten afwijken van het patroon van de twee precedenten, zonder enige winst in dependency-hygiëne.

Wat wél terecht is in de review: de modellen volgen de canonieke-schema-rol van deze repo en houden migraties erbuiten. Dat is inderdaad de bedoeling — de SQL-migratie landt in Scrum4Me als designated migrator (fase 1c).


Ter aanvulling, want het is de kern van deze PR: prisma validate liet alle twaalf sabotages door die de guard-test wél vangt — relatiepaar weg (FK verdwijnt stil), map: weg (index hernoemd), onUpdate: NoActionCascade, kolom hernoemd/verwijderd/van type gewisseld. Die test is precies wat er tussen een stille Prisma-default en een gebroken CLI op runtime staat.

🤖 Generated with Claude Code

## Reactie op de REQUEST_CHANGES: de `node:fs`-finding klopt niet De finding stelt dat `__tests__/agent-message-schema.test.ts:1` (`import { readFileSync } from 'node:fs'`) de purity gate schendt en `npm run verify` rood kan maken. Dat is nagetrokken en weerlegd — hieronder de vier controles. ### 1. De gate scant `lib/`, niet `__tests__/` `scripts/verify-no-deps.sh` zegt het in zijn eigen eerste regel: ```bash # Fail if lib/ contains forbidden imports. ... LIB_DIR="${LIB_DIR:-lib}" ``` Elke `grep -rEn` in het script draait over `"$LIB_DIR"`. `__tests__/` valt buiten scope. Dat is ook de bedoeling van de regel: de gate bewaakt het **dependency-oppervlak van wat er verscheept wordt**. Een test die `prisma/schema.prisma` als tekst inleest, kan `lib/` niet vervuilen — hij importeert er niets uit en wordt niet meegebundeld. ### 2. Beide precedent-tests doen exact hetzelfde en staan al op `main` ``` $ git show origin/main:__tests__/claude-job-usage-schema.test.ts | head -1 import { readFileSync } from 'node:fs' $ git show origin/main:__tests__/model-price-rate-card-schema.test.ts | head -1 import { readFileSync } from 'node:fs' ``` De nieuwe test volgt bewust dat gevestigde idioom. Was dit een overtreding, dan stond `main` al rood sinds die twee tests landden. ### 3. `lib/` is dep-clean ``` $ grep -rEn "from ['\"]node:" lib --include='*.ts' | grep -v "product-doc-" (geen resultaten) ``` De enige `node:`-imports in `lib/` zijn `node:crypto`/`node:path` in `product-doc-*.ts` — precies de gedocumenteerde uitzondering. ### 4. `npm run verify` is groen, gemeten op deze branch ``` OK: lib/ is dep-clean Test Files 21 passed (21) Tests 231 passed (231) exit 0 ``` Inclusief `bash scripts/verify-no-deps.sh` én `bash __tests__/scripts/verify-no-deps.test.sh`, die allebei in het verify-script zitten. ### Conclusie De finding is een hypothese ("waarschijnlijk niet conform", "kan rood worden") die met één commando te toetsen was. De productdoc beschrijft de gate zoals hij geïmplementeerd is — over `lib/` — en niet als een repo-brede ban op `node:`-builtins. **Geen wijziging.** De test aanpassen om `node:fs` te vermijden zou hem óf onmogelijk maken (het schema is een bestand; je moet het lezen) óf laten afwijken van het patroon van de twee precedenten, zonder enige winst in dependency-hygiëne. Wat wél terecht is in de review: de modellen volgen de canonieke-schema-rol van deze repo en houden migraties erbuiten. Dat is inderdaad de bedoeling — de SQL-migratie landt in Scrum4Me als designated migrator (fase 1c). --- *Ter aanvulling, want het is de kern van deze PR: `prisma validate` liet **alle twaalf** sabotages door die de guard-test wél vangt — relatiepaar weg (FK verdwijnt stil), `map:` weg (index hernoemd), `onUpdate: NoAction` → `Cascade`, kolom hernoemd/verwijderd/van type gewisseld. Die test is precies wat er tussen een stille Prisma-default en een gebroken CLI op runtime staat.* 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/scrum4me-shared!37
No description provided.