--- 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.