feat(hook): AskUserQuestion→Hub-brug — vraag naar iOS, antwoord native terug #179

Merged
janpeter merged 17 commits from claude/askuserquestion-ios-approval-c76040 into main 2026-08-19 17:42:33 +02:00
Owner

Brengt Claude Code's ingebouwde AskUserQuestion naar de iOS-hub: een PreToolUse-hook duwt een enkelvoudige, niet-multiSelect vraag door de bestaande CUSTOM-intake (M34) en geeft het antwoord van de telefoon native terug via permissionDecision: "allow" + updatedInput.answers. JP's vrije toelichting uit het detailscherm komt mee als additionalContext. Elk ander pad — timeout, expiry, ongeldige handtekening, gefilterde vorm, payload-gate — is fail-open: geen stdout, exit 0, de terminal-UI verschijnt gewoon.

Wijzigingsoppervlak

Nieuw scripts/hooks/hub-askuserquestion-hook.mjs (zero-dep), twee testbestanden, docs/runbooks/hub-askuserquestion-hook.md
Gewijzigd aan bestaande code één trefwoord: export op numberedBody (lib/hub/notification-deliverer-server.ts:23) zodat de drift-test de echte bouwer kan aanroepen

scripts/hooks/hub-permission-hook.mjs blijft byte-voor-byte onaangeraakt (lopende refactor).

Waarom de payload-gate een spiegel is

fitPayload stript knoppen stil boven 3800 bytes: de melding komt dan aan zonder de opties die de gebruiker moet kiezen. Een raw-byte- of UTF-16-budget sneuvelde twee reviewronden op JSON-escaping en op eenheden (Zod telt UTF-16-eenheden, truncateCodePoints telt code points). De hook meet daarom exact wat APNs meet — Buffer.byteLength(JSON.stringify(candidate)) <= 3800 — op een nagebouwde payload die beide deliverer-takken volgt (≤4 labels → HUB_CHOICE_n met options; 5–8 → APPROVAL_INFO). Een node-environment drift-test vergelijkt die spiegel byte-voor-byte met de echte apns-server-bouwer over een 7-gevallenmatrix; loopt de server weg, dan faalt de test.

Reviewsporen

  • Spec docs/superpowers/specs/2026-08-19-askuserquestion-hub-brug-design.mddubbel-GO in ronde 4 (mac:claude + mac:codex).
  • Plan docs/superpowers/plans/2026-08-19-askuserquestion-hub-brug.mddubbel-GO in ronde 2. 5 TDD-taken, per taak npm run verify.
  • Ronde 1 dwong de architectuur om: de aanname "hooks kunnen AskUserQuestion niet beantwoorden" was fout — de hooks-reference documenteert answers-injectie via updatedInput. De hele deny-met-reden-opzet is weggegooid.
  • Securityreview apart (IDEA-150-conventie), ná de bouw. mac:codex weigerde op zijn security-hardstop (NO-GO, geen inhoudelijk oordeel). mac:claude: GO — 0 BLOCKER / 0 MAJOR / 4 MINOR, alle vier nagetrokken tegen de boom en verwerkt in f7c8a7c: de note kan het citaat niet meer sluiten (»>>), projectOptions begrenst de description mét zichtbare afkapmarkering, en het runbook documenteert nu de volledige datastroom (Apple krijgt de titel inclusief hostnaam; de hub-DB daarbovenop cwd, session_id, de volledige vraag en de descriptions) plus dat knop-antwoorden label-only is.

Verificatie

  • npm run verify groen — 2092 tests.
  • npm run docs groen (INDEX geregenereerd, alle links geldig).
  • Draaiend bewijs, als proces: het script via stdin/stdout tegen een loopback-neptaub met echte HMAC-ondertekening → allow + answers + additionalContext, preview gestript uit de intake-context; alle fail-open-paden exit 0 zonder stdout. Beide security-fixes opnieuw als proces bewezen na f7c8a7c.

Wat hierna nog moet (niet in deze PR)

  1. Per-host registratie volgens het runbook. De rooktest is een harde activeringsvoorwaarde — de allow+updatedInput-vorm is versie-afhankelijk gedrag van Claude Code; faalt hij, dan blijft HUB_ASK_WAIT_SECONDS=0 staan.
  2. E2E-gate mét JP (toestel/Watch-attestatie).

Drie securitypunten bewust NIET in deze PR (ter beslissing)

  • userNoteReason in hub-permission-hook.mjs heeft dezelfde citaat-eigenschap — dat bestand moest onaangeraakt blijven.
  • Een harde cap op context in het intake-Zod-schema (app/api/hub/approvals/intake/route.ts:14) zou óók toekomstige intake-bronnen dekken, maar raakt bestaande server-code.
  • isSecureHubUrl staat plain http op loopback toe — bestaande vlootafspraak, wordt pas scherp op een host met meerdere gebruikers.
Brengt Claude Code's ingebouwde `AskUserQuestion` naar de iOS-hub: een PreToolUse-hook duwt een enkelvoudige, niet-multiSelect vraag door de bestaande `CUSTOM`-intake (M34) en geeft het antwoord van de telefoon **native** terug via `permissionDecision: "allow"` + `updatedInput.answers`. JP's vrije toelichting uit het detailscherm komt mee als `additionalContext`. Elk ander pad — timeout, expiry, ongeldige handtekening, gefilterde vorm, payload-gate — is fail-open: geen stdout, exit 0, de terminal-UI verschijnt gewoon. ## Wijzigingsoppervlak | | | |---|---| | Nieuw | `scripts/hooks/hub-askuserquestion-hook.mjs` (zero-dep), twee testbestanden, `docs/runbooks/hub-askuserquestion-hook.md` | | Gewijzigd aan bestaande code | **één trefwoord**: `export` op `numberedBody` (`lib/hub/notification-deliverer-server.ts:23`) zodat de drift-test de echte bouwer kan aanroepen | `scripts/hooks/hub-permission-hook.mjs` blijft byte-voor-byte onaangeraakt (lopende refactor). ## Waarom de payload-gate een spiegel is `fitPayload` **stript knoppen stil** boven 3800 bytes: de melding komt dan aan zonder de opties die de gebruiker moet kiezen. Een raw-byte- of UTF-16-budget sneuvelde twee reviewronden op JSON-escaping en op eenheden (Zod telt UTF-16-eenheden, `truncateCodePoints` telt code points). De hook meet daarom exact wat APNs meet — `Buffer.byteLength(JSON.stringify(candidate)) <= 3800` — op een nagebouwde payload die beide deliverer-takken volgt (≤4 labels → `HUB_CHOICE_n` met `options`; 5–8 → `APPROVAL_INFO`). Een node-environment drift-test vergelijkt die spiegel byte-voor-byte met de **echte** `apns-server`-bouwer over een 7-gevallenmatrix; loopt de server weg, dan faalt de test. ## Reviewsporen - Spec `docs/superpowers/specs/2026-08-19-askuserquestion-hub-brug-design.md` — **dubbel-GO in ronde 4** (mac:claude + mac:codex). - Plan `docs/superpowers/plans/2026-08-19-askuserquestion-hub-brug.md` — **dubbel-GO in ronde 2**. 5 TDD-taken, per taak `npm run verify`. - Ronde 1 dwong de architectuur om: de aanname "hooks kunnen `AskUserQuestion` niet beantwoorden" was fout — de hooks-reference documenteert `answers`-injectie via `updatedInput`. De hele deny-met-reden-opzet is weggegooid. - **Securityreview apart (IDEA-150-conventie), ná de bouw.** `mac:codex` weigerde op zijn security-hardstop (NO-GO, geen inhoudelijk oordeel). `mac:claude`: **GO — 0 BLOCKER / 0 MAJOR / 4 MINOR**, alle vier nagetrokken tegen de boom en verwerkt in `f7c8a7c`: de note kan het citaat niet meer sluiten (`»` → `>>`), `projectOptions` begrenst de `description` mét zichtbare afkapmarkering, en het runbook documenteert nu de volledige datastroom (Apple krijgt de titel **inclusief hostnaam**; de hub-DB daarbovenop `cwd`, `session_id`, de volledige vraag en de descriptions) plus dat knop-antwoorden label-only is. ## Verificatie - `npm run verify` groen — 2092 tests. - `npm run docs` groen (INDEX geregenereerd, alle links geldig). - **Draaiend bewijs, als proces**: het script via stdin/stdout tegen een loopback-neptaub met echte HMAC-ondertekening → `allow` + `answers` + `additionalContext`, `preview` gestript uit de intake-context; alle fail-open-paden exit 0 zonder stdout. Beide security-fixes opnieuw als proces bewezen na `f7c8a7c`. ## Wat hierna nog moet (niet in deze PR) 1. Per-host registratie volgens het runbook. **De rooktest is een harde activeringsvoorwaarde** — de `allow`+`updatedInput`-vorm is versie-afhankelijk gedrag van Claude Code; faalt hij, dan blijft `HUB_ASK_WAIT_SECONDS=0` staan. 2. E2E-gate mét JP (toestel/Watch-attestatie). ## Drie securitypunten bewust NIET in deze PR (ter beslissing) - `userNoteReason` in `hub-permission-hook.mjs` heeft dezelfde citaat-eigenschap — dat bestand moest onaangeraakt blijven. - Een harde cap op `context` in het intake-Zod-schema (`app/api/hub/approvals/intake/route.ts:14`) zou óók toekomstige intake-bronnen dekken, maar raakt bestaande server-code. - `isSecureHubUrl` staat plain `http` op loopback toe — bestaande vlootafspraak, wordt pas scherp op een host met meerdere gebruikers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(hook): vier MINORs uit de securityreview — note kan het citaat niet meer sluiten, description begrensd, datastroom volledig gedocumenteerd
Some checks failed
CI / Detect deploy-relevant changes (pull_request) Has been cancelled
CI / Deploy Preview (PR) (pull_request) Has been cancelled
CI / Deploy Production (main) (pull_request) Has been cancelled
CI / Deploy Manual (workflow_dispatch) (pull_request) Has been cancelled
CI / Lint, Typecheck, Test & Build (pull_request) Has been cancelled
f7c8a7c8dc
Merge branch 'main' into claude/askuserquestion-ios-approval-c76040
All checks were successful
CI / Lint, Typecheck, Test & Build (pull_request) Successful in 5m3s
CI / Deploy Manual (workflow_dispatch) (pull_request) Has been skipped
CI / Detect deploy-relevant changes (pull_request) Has been skipped
CI / Deploy Preview (PR) (pull_request) Has been skipped
CI / Deploy Production (main) (pull_request) Has been skipped
a4c9c1cd46
Enige conflict was docs/INDEX.md — gegenereerd bestand, opgelost door
`npm run docs` opnieuw te draaien (223 docs, alle links geldig).
Shared-submodule volgt main (c63f0bd, issue-tracker-bump).

npm run verify groen op het samengevoegde geheel: 272 bestanden / 2197 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
s4m-codex-reviewer left a comment

Verdict: APPROVED

Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.

Findings

Geen blokkerende of niet-blokkerende findings.

Beoordeling

De nieuwe scripts/hooks/hub-askuserquestion-hook.mjs volgt de bestaande hub-hook patronen: ontbrekende/onveilige configuratie faalt stil naar terminal-fallback, het hub-besluit wordt pas gebruikt na HMAC-verificatie, en de APNs-payload wordt vooraf byte-begroot met drift-tests tegen de serverbouwer. De kleine export van numberedBody is beperkt en testgericht. Tests dekken vormfiltering, truncatie/budgettering, answer-output, signed/unsigned hub-responses, context-projectie en payload-spiegeling. De nieuwe runbook documenteert registratie, env, rooktest, dataflow en scopegrenzen.

# Verdict: APPROVED Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden. ## Findings Geen blokkerende of niet-blokkerende findings. ## Beoordeling De nieuwe `scripts/hooks/hub-askuserquestion-hook.mjs` volgt de bestaande hub-hook patronen: ontbrekende/onveilige configuratie faalt stil naar terminal-fallback, het hub-besluit wordt pas gebruikt na HMAC-verificatie, en de APNs-payload wordt vooraf byte-begroot met drift-tests tegen de serverbouwer. De kleine export van `numberedBody` is beperkt en testgericht. Tests dekken vormfiltering, truncatie/budgettering, answer-output, signed/unsigned hub-responses, context-projectie en payload-spiegeling. De nieuwe runbook documenteert registratie, env, rooktest, dataflow en scopegrenzen.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
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!179
No description provided.