test(queue): pariteitsgate tussen tool-schema's en het gedeelde vocabulaire #108

Merged
janpeter merged 1 commit from feat/queue-vocab-parity into main 2026-07-26 23:52:52 +02:00
Owner

T-129: de gate die er niet was. Sluit ST-038 en daarmee het gat dat deze hele sprint veroorzaakt heeft.

Waarom dit nodig was. Bij fase 2 hadden vier tools het modelvocabulaire overgetypt. Geen test koppelde ze aan de gedeelde lijst, en omdat src/queue/types.ts alleen het type importeerde bleef ook de typecheck groen. Dat kwam pas aan het licht toen kimi werd toegevoegd en as: 'kimi' stil geweigerd bleek te worden — door Zod, vóórdat identity-code ooit draaide.

De gate leest wat de client krijgt, niet een Zod-intern veld. __tests__/queue-vocab-parity.test.ts registreert via registerQueueTools() — dus een nieuwe queue-tool valt er automatisch onder — vangt de meta uit registerTool en zet het inputSchema om met z.toJSONSchema(..., { target:'draft-7', io:'input' }). Dat is exact wat de SDK zelf doet bij tools/list, geverifieerd tegen server/mcp.js en zod-json-schema-compat.js.

Drift wordt in beide richtingen gevangen: verzamelingsvergelijking, dus zowel een ontbrekende als een extra waarde is rood. Onafhankelijk nagemeten door een 'gemini' aan één enum toe te voegen — drie lagen vallen om.

De interessantste vondst zat in mutatie 2, en die weerlegde mijn eigen verwachting. Ik had voorgeschreven: zet de type-enum terug naar ['task','info','review_request'] en verwacht rood. Maar die literal is letterlijk QUEUE_REQUEST_TYPES — een gate die waarden vergelijkt kán daar niet op afgaan; hij zou pas afgaan zodra de gedeelde lijst beweegt, dus ná de schade. In plaats van dat glad te strijken is er een vierde, bronniveau-laag bijgebouwd: het argument van z.enum(...) moet de gedeelde constante-identifier zijn, geen literal, en die constante moet uit @shared/queue-identity.js komen. Die laag verbiedt de praktijk in plaats van alleen de uitkomst. Vindt de regex het veld niet meer, dan faalt hij — nooit stilzwijgend groen.

Plus een dekkings-sweep: élk enum-veld van élke queue-tool moet óf gebonden zijn aan een gedeelde lijst, óf expliciet als tool-eigen aangemerkt (queue_list.direction is dat — MCP-presentatievocabulaire, staat niet in de gedeelde module). Een nieuwe overtypende tool valt er dus niet buiten.

Twee stukken drift opgeruimd. queue-push.ts leidt zijn type-enum nu af uit QUEUE_REQUEST_TYPES. En queue-list.ts:40 had een handgeschreven "niet-terminaal"-lijst ['pending','claimed']; die is nu het complement van QUEUE_TERMINAL_STATUSES. Vandaag exact dezelfde twee waarden, maar een zesde niet-terminale status valt er nu niet meer stil buiten — met een eigen test, want de pariteitsgate leest inputSchema's en kan dit niet zien.

Verificatie. 1362 passed (was 1339), 29 skipped, typecheck exit 0. Integratie: 29 passed over 3 bestanden tegen scrum4me_test. Alle drie de mutaties uitgevoerd en exact teruggedraaid.

Hiermee werkt as: 'kimi' end-to-end op de MCP-kant, en is de klasse fout die dit veroorzaakte voortaan rood in plaats van stil.

🤖 Generated with Claude Code

T-129: de gate die er niet was. Sluit ST-038 en daarmee het gat dat deze hele sprint veroorzaakt heeft. **Waarom dit nodig was.** Bij fase 2 hadden vier tools het modelvocabulaire overgetypt. Geen test koppelde ze aan de gedeelde lijst, en omdat `src/queue/types.ts` alleen het *type* importeerde bleef ook de typecheck groen. Dat kwam pas aan het licht toen `kimi` werd toegevoegd en `as: 'kimi'` stil geweigerd bleek te worden — door Zod, vóórdat identity-code ooit draaide. **De gate leest wat de client krijgt, niet een Zod-intern veld.** `__tests__/queue-vocab-parity.test.ts` registreert via `registerQueueTools()` — dus een nieuwe queue-tool valt er automatisch onder — vangt de meta uit `registerTool` en zet het `inputSchema` om met `z.toJSONSchema(..., { target:'draft-7', io:'input' })`. Dat is exact wat de SDK zelf doet bij `tools/list`, geverifieerd tegen `server/mcp.js` en `zod-json-schema-compat.js`. Drift wordt in **beide** richtingen gevangen: verzamelingsvergelijking, dus zowel een ontbrekende als een extra waarde is rood. Onafhankelijk nagemeten door een `'gemini'` aan één enum toe te voegen — drie lagen vallen om. **De interessantste vondst zat in mutatie 2, en die weerlegde mijn eigen verwachting.** Ik had voorgeschreven: zet de `type`-enum terug naar `['task','info','review_request']` en verwacht rood. Maar die literal *is* letterlijk `QUEUE_REQUEST_TYPES` — een gate die waarden vergelijkt kán daar niet op afgaan; hij zou pas afgaan zodra de gedeelde lijst beweegt, dus ná de schade. In plaats van dat glad te strijken is er een vierde, bronniveau-laag bijgebouwd: het argument van `z.enum(...)` moet de gedeelde constante-identifier zijn, geen literal, en die constante moet uit `@shared/queue-identity.js` komen. Die laag verbiedt de praktijk in plaats van alleen de uitkomst. Vindt de regex het veld niet meer, dan faalt hij — nooit stilzwijgend groen. Plus een dekkings-sweep: élk enum-veld van élke queue-tool moet óf gebonden zijn aan een gedeelde lijst, óf expliciet als tool-eigen aangemerkt (`queue_list.direction` is dat — MCP-presentatievocabulaire, staat niet in de gedeelde module). Een nieuwe overtypende tool valt er dus niet buiten. **Twee stukken drift opgeruimd.** `queue-push.ts` leidt zijn `type`-enum nu af uit `QUEUE_REQUEST_TYPES`. En `queue-list.ts:40` had een handgeschreven "niet-terminaal"-lijst `['pending','claimed']`; die is nu het complement van `QUEUE_TERMINAL_STATUSES`. Vandaag exact dezelfde twee waarden, maar een zesde niet-terminale status valt er nu niet meer stil buiten — met een eigen test, want de pariteitsgate leest inputSchema's en kan dit niet zien. **Verificatie.** 1362 passed (was 1339), 29 skipped, typecheck exit 0. Integratie: 29 passed over 3 bestanden tegen `scrum4me_test`. Alle drie de mutaties uitgevoerd en exact teruggedraaid. Hiermee werkt `as: 'kimi'` end-to-end op de MCP-kant, en is de klasse fout die dit veroorzaakte voortaan rood in plaats van stil. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
test(queue): pariteitsgate tussen tool-schema's en het gedeelde vocabulaire
All checks were successful
CI / Verify (pull_request) Successful in 1m56s
ff05a479ed
Het queue-vocabulaire staat canoniek in @shared/queue-identity.js, maar niets
hield de tool-schema's daaraan vast. Fase 2 typte ['claude','codex','jp'] over
in vier tools; dat bleef groen omdat src/queue/types.ts alleen de *types*
importeert en het mock-serverpatroon de handlers rechtstreeks aanroept — Zod
draait dan nooit. Het kwam pas boven toen 'kimi' erbij kwam.

Resterende drift weggewerkt:
- queue_push.type was ['task','info','review_request'] overgetypt, exact
  QUEUE_REQUEST_TYPES. Nu afgeleid.
- queue_list filterde niet-terminale berichten op een handmatige
  ['pending','claimed']. Nu het complement van QUEUE_TERMINAL_STATUSES, zodat
  een zesde niet-terminale status niet stil buiten de default valt.

De gate (__tests__/queue-vocab-parity.test.ts) registreert de queue-tools via
registerQueueTools() — een nieuwe tool valt er dus automatisch onder — en toetst
per gebonden veld in vier lagen:

1. Waardepariteit: het inputSchema wordt via z.toJSONSchema(draft-7, io:'input')
   omgezet, dezelfde voorstelling die de SDK bij tools/list aan de client geeft,
   en als verzameling vergeleken met de gedeelde lijst. Vangt zowel een
   ontbrekende als een extra waarde, plus een dubbele.
2. parse() accepteert elke gedeelde waarde — bewijst dat laag 1 het echte
   validatiegedrag beschrijft en niet iets afgeleids.
3. parse() weigert waarden erbuiten; voor de types zijn dat de antwoord-types.
4. Bronniveau: z.enum() moet de gedeelde constante krijgen, geen literal-lijst.
   Nodig omdat een overgetypte lijst die vandaag toevallig klopt (precies het
   queue_push.type-geval) op waardepariteit groen blijft.

Plus een sweep die élk enum-veld van élke queue-tool dwingt gebonden óf
expliciet tool-eigen te zijn (queue_list.direction), zodat een verdwenen veld of
een nieuwe overtypende tool niet stilzwijgend door de dekking valt.

Mutatiebewijs — elk apart uitgevoerd en teruggedraaid:
- queue_next.as terug naar de hardcoded drie: 3 lagen rood (1, 2, 4).
- queue_push.type terug naar de hardcoded drie: alleen laag 4 rood. De lijsten
  zijn vandaag value-identiek, dus waardepariteit kán dit niet zien; daarom
  bestaat laag 4.
- queue_wait_reply.as uitgebreid met 'gemini': 3 lagen rood (1, 3, 4).

1339 -> 1362 tests groen (+22 gate, +1 queue_list); integratie 29/3 ongewijzigd.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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-mcp!108
No description provided.