---
title: "Review - Bootstrap-wizard plan v3.3"
status: draft
date: 2026-05-14
source_plan: "/Users/janpetervisser/.claude/plans/als-ik-een-nieuwe-virtual-turtle.md"
---

# Review - Bootstrap-wizard plan v3.3

## Conclusie

V3.3 verwerkt de v3.2-review goed. De claim-identiteit, shared package locatie, GitHub side-effect checkpoints, stale-recovery filter, action-level permissions, classic PAT-keuze en env/docs zijn nu expliciet. Dit plan is dicht bij implementatieklaar.

Nog verwerken vóór uitvoering: de status-sync voorbeeldcode is nog niet echt transactioneel, stale-recovery zet runs te breed op `FAILED_NEEDS_CLEANUP`, en er staat nog een niet-bestaande ID-generator in het enqueue-voorbeeld.

## Bevindingen

### P1 - Status-sync is nog niet transactioneel genoeg

De sectie heet "transactional + post-commit NOTIFY", maar `syncSuccess` doet eerst `bootstrapRun.updateMany(...)` buiten een transaction en daarna pas een transaction met `claudeJob.updateMany(...)` en `product.update(...)`. Als de tweede transaction faalt, staat de run al op `SUCCEEDED`. Als de job-update `count=0` oplevert, wordt het product alsnog bijgewerkt en wordt alsnog `DONE` genotify'd.

Fix: doe run-update, job-update en product-update in één `prisma.$transaction(async tx => ...)`, check beide `updateMany.count` waarden, en notify pas na een volledig geslaagde commit. Zet ook `lease_until` en `claimed_by_worker_id` terminal op `null`.

### P1 - Stale recovery zet alle verlopen runs op `FAILED_NEEDS_CLEANUP`

De SQL zet alle bijbehorende `bootstrap_runs` op `FAILED_NEEDS_CLEANUP`, terwijl de tekst zegt dat dit alleen moet wanneer `github_repo_full_name IS NOT NULL`. Voor runs zonder externe side effects hoort status `FAILED` te zijn.

Fix: split recovery in twee updates:

- `FAILED_NEEDS_CLEANUP` alleen waar `github_repo_full_name IS NOT NULL` of `github_repo_created_at IS NOT NULL`.
- `FAILED` waar beide leeg zijn.

Hou de `kind='BOOTSTRAP_REPO'` filter; die is goed.

### P1 - Enqueue gebruikt `@paralleldrive/cuid2`, maar die dependency bestaat niet

Het plan importeert `createId` uit `@paralleldrive/cuid2`, maar deze repo heeft die dependency niet. De bestaande schema's gebruiken Prisma `cuid()` defaults; applicatiecode genereert die IDs nu niet zelf.

Fix: gebruik de transaction callback-vorm en laat Prisma de IDs genereren, of voeg expliciet een dependency toe en leg vast dat alle nieuwe ID-validatie `z.string().cuid()` blijft accepteren. Mijn voorkeur: transaction callback, geen nieuwe ID-library.

### P2 - Nieuwe non-null arrayvelden op `User` hebben defaults nodig

`github_pat_scopes String[]` is niet nullable en heeft geen default. Op een bestaande database met users maakt dat de migration lastig of onmogelijk zonder backfill.

Fix: maak dit `github_pat_scopes String[] @default([])` of gebruik `Json?` als je fine-grained tokenmetadata later flexibeler wilt opslaan.

### P2 - NOTIFY-status casing moet expliciet API-lowercase zijn

De voorbeelden sturen `status: 'DONE'` en `status: 'QUEUED'`. Bestaande helpers mappen jobstatussen naar lowercase API-strings (`done`, `queued`, etc.). Sommige bestaande paden sturen al lowercase via `jobStatusToApi`.

Fix: spreek af dat NOTIFY payloads API-lowercase gebruiken, en DB-writes UPPER_SNAKE houden. Dus `status: 'done'` in payload, `status: 'DONE'` in DB.

### P2 - Stale recovery hoort niet pas fase 2 te zijn

De service gebruikt leases in MVP, maar de verificatie noemt stale recovery "in fase-2". Zonder recovery kan een crash een job langdurig in `CLAIMED`/`RUNNING` laten hangen.

Fix: neem minimale stale recovery op in Sprint 1d: markeer verlopen `BOOTSTRAP_REPO` jobs en runs correct als `FAILED` of `FAILED_NEEDS_CLEANUP`.

### P2 - Org-owner preflight moet endpoint-gedreven zijn

Voor classic PAT MVP is `repo` scope helder, maar repo creation in een org hangt ook af van de daadwerkelijke org-permissions. Scope-check alleen is niet genoeg.

Fix: laat `RepoOwnerPicker` alleen owners tonen waarvoor de concrete Octokit preflight slaagt, en behandel de response als authority. Documenteer dat org-eigenaarschap/permissies via GitHub worden gevalideerd, niet afgeleid uit alleen scopes.

## Aanbevolen minimale patch op het plan

1. Herschrijf `syncSuccess/syncFailed/syncRunning` als één transaction callback met count-checks.
2. Split stale recovery in `FAILED` vs `FAILED_NEEDS_CLEANUP`.
3. Vervang pre-generated `createId()` door een transaction callback of voeg de dependency expliciet toe.
4. Voeg `@default([])` toe aan `github_pat_scopes`.
5. Maak NOTIFY statuswaarden lowercase.

Daarna is v3.3 goed genoeg om naar `docs/plans/M8-bootstrap-wizard.md` te promoveren.
