# Review — M8 bootstrap-wizard plan v3.4 Datum: 2026-05-14 Bronplan: `docs/plans/M8-bootstrap-wizard.md` Scope: plan-review, geen implementatie uitgevoerd. Ik heb ook kort vergeleken met bestaande repo-contracten zoals `prisma/schema.prisma`, `lib/job-status.ts`, `tsconfig.json` en `package.json`. ## Conclusie De aanbevelingen uit de vorige review zijn grotendeels goed verwerkt. Ik zie geen P1-blocker meer in de laatste versie. De belangrijkste restpunten zitten in GitHub owner-permissies, catalog-hash determinisme en acceptatie-tests. ## Findings ### [P2] Org-owner preflight belooft meer zekerheid dan de beschreven checks kunnen leveren Referentie: `docs/plans/M8-bootstrap-wizard.md:50`, `docs/plans/M8-bootstrap-wizard.md:540-567` Het plan zegt dat `RepoOwnerPicker` alleen owners toont waarvoor een concrete repo-create-preflight slaagt. De uitgewerkte check doet echter `GET /orgs/{org}` plus membership-check. Dat bewijst lidmaatschap/zichtbaarheid, niet dat de PAT daadwerkelijk een private repo in die org mag maken. GitHub documenteert voor org-repo creation dat de authenticated user org-lid moet zijn en dat classic PATs `repo` nodig hebben voor private repositories. Daarnaast kunnen org-instellingen repo creation beperken; de org API exposeert velden zoals `members_can_create_repositories` en `members_allowed_repository_creation_type`. De huidige plan-check gebruikt die velden niet en kan daardoor false positives of false negatives geven. Aanbevolen wijziging: - Noem dit expliciet een best-effort owner discovery, niet een harde create-permission proof. - Valideer collision met `GET /repos/{owner}/{repo}`. - Laat de echte create-call in de service de finale autorisatie zijn en vertaal `403/422` naar een duidelijke wizard-fout. - Als je org-policy vooraf wilt meenemen: lees org creation settings waar beschikbaar, maar behandel ontbrekende rechten/SSO/admin-scope als onbekend in plaats van owner automatisch te verbergen. Bronnen: GitHub REST docs voor [repositories](https://docs.github.com/en/rest/repos/repos) en [organizations](https://docs.github.com/en/rest/orgs/orgs). ### [P2] `syncRunning` mist expliciete timestamp-contracten Referentie: `docs/plans/M8-bootstrap-wizard.md:230`, `docs/plans/M8-bootstrap-wizard.md:418-420`, `docs/plans/M8-bootstrap-wizard.md:965-968` Het plan specificeert voor `syncRunning` alleen de status-overgang `PENDING -> RUNNING` en `CLAIMED -> RUNNING`. De modellen hebben `started_at`, en de verificatie sorteert later op `started_at`. Als `syncRunning` die velden niet atomair vult, worden metrics, UI-sortering en acceptatiequeries onbetrouwbaar. Aanbevolen wijziging: - Zet in dezelfde transaction `bootstrap_runs.started_at = now` en `claude_jobs.started_at = now`. - Gebruik dezelfde `now`-waarde voor run en job. - Voeg een unit/integration-test toe voor `CLAIMED/PENDING -> RUNNING` inclusief `started_at`. ### [P2] `catalog_version` is nog niet deterministisch genoeg gespecificeerd Referentie: `docs/plans/M8-bootstrap-wizard.md:603-634` `recipe_hash` is goed uitgewerkt, maar `catalog_version` blijft te vaag: `SELECT md5(string_agg(...)) FROM bootstrap_options ...` is zonder expliciete ordering niet deterministisch en lijkt alleen options te hashen. Catalog changes in categories, actions, params, roles, risk levels, `enabled`, `archived` of `supports_dry_run` kunnen dan gemist worden. Aanbevolen wijziging: - Gebruik dezelfde canonical JSON-aanpak als `recipe_hash`. - Hash categories, options en actions samen. - Sorteer expliciet op category `display_order/slug`, option `display_order/slug`, action `execution_order/id`. - Include minstens: selection type, required/default flags, enabled/archived, action kind, action params, dry-run support, side effects, risk level en required role. - Gebruik `sha256`, niet ad-hoc `md5(string_agg(...))`. ### [P2] De E2E-verificatiequery leest `lease_until` uit de verkeerde tabel Referentie: `docs/plans/M8-bootstrap-wizard.md:965-968` De query selecteert `lease_until > NOW()` uit `bootstrap_runs`, maar `lease_until` staat op `claude_jobs`. Deze acceptatiestap faalt zodra iemand het letterlijk uitvoert en kan lease-regressies maskeren. Aanbevolen wijziging: ```sql SELECT br.status, br.repo_url, br.recipe_hash, cj.lease_until > NOW() AS lease_active FROM bootstrap_runs br JOIN claude_jobs cj ON cj.id = br.claude_job_id ORDER BY br.started_at DESC NULLS LAST, br.created_at DESC LIMIT 1; ``` ### [P3] Startup stale-recovery uitleg is inconsistent met de worker-id definitie Referentie: `docs/plans/M8-bootstrap-wizard.md:93`, `docs/plans/M8-bootstrap-wizard.md:149-151` De worker-id bevat hostname, pid en start timestamp. Een herstartende service heeft dus niet dezelfde `claimed_by_worker_id`. De SQL in het plan is gelukkig globaal en kind-gefilterd, maar de uitleg zegt dat dezelfde service-instance zichzelf herkent via de oude hostname. Aanbevolen wijziging: - Beschrijf startup recovery als globale recovery voor verlopen `BOOTSTRAP_REPO` leases. - Niet filteren op `claimed_by_worker_id` bij stale recovery. - Bewaar `claimed_by_worker_id` alleen voor renewal/observability. ### [P3] Vendor-copy drift-mitigatie staat alleen als risico, niet als concrete sprint-taak Referentie: `docs/plans/M8-bootstrap-wizard.md:749-751`, `docs/plans/M8-bootstrap-wizard.md:1023-1028` Het plan erkent terecht dat vendor-copy drift tussen Scrum4Me en `bootstrap-service` gevaarlijk is. De mitigatie, een schema-hash CI-check, staat alleen bij accepted risks en niet bij fasering of verificatie. Aanbevolen wijziging: - Maak de hash-check onderdeel van Sprint 1a of Sprint 1d. - Laat `bootstrap-service` bij startup loggen welke `ActionSchema` versie/hash geladen is. - Voeg een verificatiestap toe die faalt als `packages/bootstrap-actions` in de service niet overeenkomt met de Scrum4Me-bron. ### [P3] `ADD_DEPENDENCY.version` regex is te smal voor normale npm specs Referentie: `docs/plans/M8-bootstrap-wizard.md:770-778` De regex accepteert alleen cijfers en operators. Geldige npm-versies zoals `latest`, prerelease labels (`^1.2.3-beta.1`), `workspace:*`, `npm:` aliases of git/tarball specs worden afgewezen. Voor MVP kan dit acceptabel zijn als seed-data alleen simpele semver gebruikt, maar het moet expliciet zijn. Aanbevolen wijziging: - Documenteer MVP als "alleen exact/range semver". - Of gebruik een echte parser zoals `npm-package-arg`/`semver` en allowlist de toegestane spec-types. ## Wat goed verwerkt is - Transactionele status-sync staat nu in één `prisma.$transaction` met post-commit NOTIFY. - `FAILED_NEEDS_CLEANUP` wordt alleen gebruikt bij bekende GitHub side-effects. - `claimed_by_worker_id` is terecht apart gehouden van `claimed_by_token_id`. - De `@paralleldrive/cuid2` afhankelijkheid is verdwenen; Prisma `cuid()` blijft consistent met het bestaande schema. - Lowercase SSE-status via `jobStatusToApi` matcht het bestaande contract. - Stale recovery staat nu in Sprint 1d en is dus onderdeel van MVP. ## Go/no-go Go na verwerking van de P2-punten. De P3-punten kunnen mee in dezelfde planupdate, maar hoeven geen implementatie te blokkeren zolang ze expliciet als MVP-beperking of verificatietaak worden vastgelegd.