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

# Review - Bootstrap-wizard plan v3.2

## Conclusie

V3.2 is een stevige verbetering. De grote architectuurfout uit v2 is opgelost: er is nu één executor-model met een aparte `bootstrap-service`, geen app-side fire-and-forget. Ook snake_case tables, het bestaande SSE payload-contract, `lease_until`, owner/slug en tag-pinning zijn goed verwerkt.

Nog niet direct implementeren zonder de punten hieronder te verwerken. De belangrijkste resterende blokkades zitten in claim-identiteit, deploybaarheid van het gedeelde package, en recovery wanneer GitHub-repo-aanmaak/push half slaagt.

## Bevindingen

### P1 - Claim-query gebruikt een niet-bestaand `claimed_by` veld

Het claim-protocol zet `claimed_by = ${WORKER_ID}` op `claude_jobs`. Het huidige `ClaudeJob`-model heeft `claimed_by_token_id`, `claimed_at` en `lease_until`, maar geen `claimed_by`. Dit faalt in SQL/migratie tenzij je een nieuw veld toevoegt.

Fix: kies expliciet:

- Re-use `claimed_by_token_id` met een dedicated service `ApiToken`, of
- voeg `claimed_by_worker_id String?` / `claimed_by_service String?` toe, of
- laat claim-identiteit weg en vertrouw op `lease_until`.

Mijn voorkeur: voeg `claimed_by_worker_id String?` toe voor `bootstrap-service`, zodat je logs en recovery kunt correleren zonder `ApiToken`-semantiek te misbruiken.

### P1 - `file:../bootstrap-service/...` dependency maakt de app niet deploybaar

V3.2 kiest voor een shared package onder `~/Development/bootstrap-service/packages/bootstrap-actions/` en een lokale `file:` link vanuit de Scrum4Me-app. Dat werkt lokaal, maar niet in een normale Vercel/GitHub build van de Scrum4Me repo: de sibling-directory zit niet in de repository checkout.

Fix voor MVP:

- Zet `packages/bootstrap-actions/` in de Scrum4Me repo, want dit package bevat geen secrets.
- Laat `bootstrap-service` dit package consumeren via git/package release, of tijdelijk via copied source met een sync-script.
- Of publiceer meteen naar GitHub Packages en pin een versie.

Niet doen: de app afhankelijk maken van een sibling path buiten de repo.

### P1 - Crash-recovery na externe GitHub-mutaties is nog onvoldoende

De happy path en catch-path verwijderen een aangemaakte repo bij errors, maar er is geen duurzaam checkpoint als de service crasht nadat de repo is aangemaakt en voordat `SUCCEEDED` is opgeslagen. Stale recovery markeert dan alleen DB-statussen `FAILED`; de GitHub repo kan blijven bestaan als orphan.

Fix: voeg expliciete externe side-effect checkpoints toe op `BootstrapRun`:

- `github_repo_created_at`
- `github_repo_id`
- `github_repo_full_name`
- `push_completed_at`

Stale recovery kan dan beslissen: compensating delete proberen, of `FAILED_NEEDS_CLEANUP`/manual intervention markeren. Zonder dit is rollback niet betrouwbaar.

### P1 - Stale recovery moet strikt op `BOOTSTRAP_REPO` filteren

De stale-recovery beschrijving update `claude_jobs` waar status `CLAIMED/RUNNING` en `lease_until < NOW`. Dat mag niet generiek op alle job kinds draaien, want de bestaande Claude/sprint runner gebruikt dezelfde tabel.

Fix: filter altijd `kind = 'BOOTSTRAP_REPO'`, en update alleen de bijbehorende `bootstrap_runs`. Laat bestaande cleanup voor andere job kinds ongemoeid.

### P1 - Transaction-array kan geen generated `jobId` doorgeven aan `BootstrapRun`

De atomische enqueue pseudo-code gebruikt `prisma.$transaction([claudeJob.create(...), bootstrapRun.create({ claude_job_id }))])`. Als `jobId` door Prisma wordt gegenereerd, is die waarde in array-form niet beschikbaar voor de tweede create.

Fix: gebruik een transaction callback en pregenereer IDs, of maak eerst de job in de transaction en gebruik de returned ID voor de run. Bijvoorbeeld `const jobId = createId()` vooraf en beide records met expliciete IDs schrijven.

### P2 - Cancel kan alsnog door succes worden overschreven

`cancelBootstrapAction` zet `ClaudeJob.status='CANCELLED'`; de service "detecteert per-action". Dat is goed, maar `syncSuccess` moet ook conditioneel zijn. Anders kan een cancel tussen de laatste checkpoint en success-sync alsnog eindigen als `DONE/SUCCEEDED`.

Fix: voor terminal transitions eerst current job/run status lezen of conditional `updateMany` gebruiken. Als `CANCELLED`, geen success meer schrijven.

### P2 - `last_bootstrap_run_id` mist relationele details

Het plan noemt `Product.last_bootstrap_run_id String?`, maar niet de Prisma relation naar `BootstrapRun` met `onDelete: SetNull`. Voeg die expliciet toe, inclusief relation name om ambiguiteit met `Product.bootstrap_runs` te voorkomen.

### P2 - Action permissions staan op option-niveau, maar risico kan action-niveau zijn

`risk_level` en `requires_role` staan nu op `BootstrapOption`, terwijl `RUN_BASH_TEMPLATE` een action-kind is. Als een optie meerdere acties bevat, moet de optie-risk altijd afgeleid worden uit de zwaarste action, of je hebt action-level permissions nodig.

Fix: ofwel permissions verplaatsen naar `BootstrapAction`, of `BootstrapOption.risk_level`/`requires_role` server-side afleiden en niet handmatig laten driften.

### P2 - Houd ID-strategie consistent met de codebase

Nieuwe modellen gebruiken `@default(uuid())`, terwijl bestaande Scrum4Me-tabellen vrijwel overal `@default(cuid())` gebruiken. Technisch kan UUID, maar het wijkt af zonder duidelijke reden.

Fix: gebruik `cuid()` tenzij er een externe reden is voor UUID.

### P2 - Fine-grained GitHub PATs passen niet netjes in alleen `repo` scope

De verificatie verwacht `repo` in `x-oauth-scopes`. Dat is prima voor classic PATs, maar fine-grained PATs werken met repository permissions en tonen niet altijd hetzelfde scope-model.

Fix: maak MVP expliciet "classic PAT met `repo` scope" of ondersteun fine-grained tokens met aparte permission checks. Zet dit ook in de settings UI-copy.

### P2 - `.env.example` en deployment docs ontbreken in de filelijst

`BOOTSTRAP_ENCRYPTION_KEY` wordt verplicht in de app en service. Voeg `.env.example`, deployment runbook en bootstrap-service README setup toe aan de scope, anders breken lokale onboarding en CI/deploy snel.

## Aanbevolen aanpassing

Verwerk vóór implementatie minimaal:

1. Vervang `claimed_by` door een bestaand of nieuw veld.
2. Verplaats het shared package naar de Scrum4Me repo of publiceer het.
3. Voeg GitHub side-effect checkpoints toe.
4. Filter stale recovery hard op `kind='BOOTSTRAP_REPO'`.
5. Maak enqueue transaction-ID handling concreet.

Daarna is het plan implementatieklaar genoeg om naar `docs/plans/M8-bootstrap-wizard.md` te verplaatsen.
