[ISS-2] expected_status onbruikbaar zonder volledig ppe-blok: ongedocumenteerde XOR-guard blokkeert CAS voor gewone callers (9 tools) #128

Open
opened 2026-08-29 15:53:44 +02:00 by janpeter · 0 comments
Owner

Beheerd door Scrum4Me — wijzigingen hier worden overschreven. Bron: https://thuis.jp-visser.nl/issues/cmtefsp920004cv17ke2ezxif

Status: investigating · Severity: s3_major · Gemeld door: claude (mac) · Occurrences: 1 (laatst: 2026-08-29T13:49:17.654Z) · Aangemaakt: 2026-08-29T13:49:17.654Z

Registratie

Symptoom

update_task_status met expected_status faalt met PPE_INPUT_INCOMPLETE. Zonder expected_status slaagt exact dezelfde aanroep.

Gevonden op 2026-08-29 tijdens de IDEA-169-dogfood-run, bij het afsluiten van acht M37-taken (product Scrum4Me). Werkbare omweg: expected_status weglaten — maar dan is er geen optimistic-concurrency-bescherming meer.

Reproductie

update_task_status { task_id: <id>, status: "done", expected_status: "todo" }   → PPE_INPUT_INCOMPLETE
update_task_status { task_id: <id>, status: "done" }                            → OK

Oorzaak

src/tools/update-task-status.ts:35 (op origin/main, en in de gedeployde commit f222f692e7a264cd97132248054d308dc76817e7):

if ((ppe === undefined) !== (expected_status === undefined)) throw new Error('PPE_INPUT_INCOMPLETE')

Dit is een XOR-koppeling: ppe en expected_status moeten allebei aanwezig of allebei afwezig zijn. Regel 36 leunt daarop met een non-null assertion (PPE_STATUS_TRANSITIONS[expected_status!]), dus de guard is daar bewust: hij beschermt die assertion.

Gevolg: het CAS-veld is de facto PPE-only. Een gewone caller kan expected_status niet gebruiken, want het ppe-blok vereist zes velden die alleen een PPE-orchestrator kan leveren (run_id, orchestrator_id, orchestrator_generation, operation_key, payload_hash, plan_authority_operation_key) — en die zou een gewone caller ook niet mogen verzinnen.

Waarom dit verwart

  1. Het inputSchema presenteert beide als losse optionals. Niets in de schemavorm zegt dat ze aan elkaar vastzitten.
  2. De tool-description noemt ze geen van beide. De live description gaat alleen over sprint_run_id en demo-accounts; over expected_status, ppe of hun koppeling staat er niets.
  3. De foutcode wijst de verkeerde kant op. PPE_INPUT_INCOMPLETE suggereert dat er iets aan een PPE-aanroep ontbreekt, terwijl de caller helemaal geen PPE-aanroep deed — hij wilde alleen een statusguard.
  4. Het gate-3-receipt (idea-169-gate-3-reentry-evidence-correction-stop.json) legt vast: all_ppe_identity_and_cas_fields_optional: true en server_rejects_legacy_callers_missing_these_fields: false. Dat klopt voor callers die geen van beide meesturen, maar niet voor een caller die alléén het CAS-veld gebruikt — dat pad is niet afgedekt.

Reikwijdte — een klasse, geen incident

Dezelfde XOR staat in negen tools (git grep -l PPE_INPUT_INCOMPLETE origin/main -- src/tools):

create-pbi.ts · create-sprint.ts · create-story.ts · create-task.ts · log-commit.ts · log-implementation.ts · log-test-result.ts · update-task-plan.ts · update-task-status.ts

De ceremony-tools koppelen ppe aan ceremony_object_key, de log-tools aan hun eigen complete-vlag, update-task-plan aan casComplete. Repareer dit als klasse, niet per tool.

Impact

Geen dataschade en er is een omweg, maar elke caller die een statuswissel wil beschermen tegen een gelijktijdige schrijver kan dat niet — en juist in een multi-agent-opzet is dat het geval waarvoor CAS bestaat. De omweg (veld weglaten) verwijdert stil precies de bescherming die de caller vroeg.

Voorgestelde richting (keuze is aan de eigenaar)

Eén van tweeën, consequent over alle negen:

  • A — CAS zelfstandig toestaan. Laat expected_status zonder ppe toe als gewone optimistic-concurrency-check; laat ppe de CAS-waarde blijven vereisen (dus alleen de andere richting van de XOR behouden). Vervang de non-null assertion op regel 36 door een echte check. Nieuwe foutcode voor een mismatch, bijv. TASK_STATUS_CONFLICT.
  • B — expliciet PPE-only maken. Laat de guard staan, maar zet de koppeling in de tool-description én in de veldbeschrijving, en geef een foutcode die de caller de weg wijst (bijv. CAS_REQUIRES_PPE).

In beide gevallen: neem de gekozen regel op in het gate-3-receiptverhaal, zodat "alle PPE-/CAS-velden zijn optioneel" niet langer strikter leest dan het is.

Onderzoek


2026-08-29T14:26:02.968Z — mac:codex

Bevestigd op origin/main 93e4f5f (2026-08-29). Commit aa4c5be (feat: make Scrum4Me mutations replay-safe) introduceerde alle negen PPE_INPUT_INCOMPLETE-guards.

Root cause: PPE-envelopevalidatie en companionvelden zijn met XOR gekoppeld, terwijl het inputSchema ze onafhankelijk optioneel presenteert. Bij update_task_status wordt bovendien het CAS-uitvoerpad op ppe gekozen; expected_status zonder PPE wordt dus vóór de mutatie afgewezen. Bij update_task_plan gebeurt hetzelfde met expected_current_hash + replacement_hash.

Klasse-effecten:

  • ceremony-tools: ppe ↔ ceremony_object_key;
  • log-tools: ppe ↔ volledig paar task_id + execution_key (één los veld zonder PPE wordt nu zelfs stil geaccepteerd);
  • task-plan: ppe ↔ volledig hashpaar (één losse hash zonder PPE wordt stil genegeerd);
  • task-status: ppe ↔ expected_status.

De bestaande PPE-integratietests dekken replay/fencing/CAS binnen PPE, maar geen inputcontractmatrix voor legacy/gewone callers. Productdoc-search op PPE/CAS/replay leverde geen bindende productspecificatie op; broncode en introductiecommit zijn daarom de primaire contractbron.

Oplossing

Nog geen oplossing.

> Beheerd door Scrum4Me — wijzigingen hier worden overschreven. Bron: https://thuis.jp-visser.nl/issues/cmtefsp920004cv17ke2ezxif Status: investigating · Severity: s3_major · Gemeld door: claude (mac) · Occurrences: 1 (laatst: 2026-08-29T13:49:17.654Z) · Aangemaakt: 2026-08-29T13:49:17.654Z ## Registratie ## Symptoom `update_task_status` met `expected_status` faalt met `PPE_INPUT_INCOMPLETE`. Zonder `expected_status` slaagt exact dezelfde aanroep. Gevonden op 2026-08-29 tijdens de IDEA-169-dogfood-run, bij het afsluiten van acht M37-taken (product Scrum4Me). Werkbare omweg: `expected_status` weglaten — maar dan is er geen optimistic-concurrency-bescherming meer. ## Reproductie ``` update_task_status { task_id: <id>, status: "done", expected_status: "todo" } → PPE_INPUT_INCOMPLETE update_task_status { task_id: <id>, status: "done" } → OK ``` ## Oorzaak `src/tools/update-task-status.ts:35` (op `origin/main`, en in de gedeployde commit `f222f692e7a264cd97132248054d308dc76817e7`): ```ts if ((ppe === undefined) !== (expected_status === undefined)) throw new Error('PPE_INPUT_INCOMPLETE') ``` Dit is een **XOR-koppeling**: `ppe` en `expected_status` moeten allebei aanwezig of allebei afwezig zijn. Regel 36 leunt daarop met een non-null assertion (`PPE_STATUS_TRANSITIONS[expected_status!]`), dus de guard is daar bewust: hij beschermt die assertion. Gevolg: het CAS-veld is de facto **PPE-only**. Een gewone caller kan `expected_status` niet gebruiken, want het `ppe`-blok vereist zes velden die alleen een PPE-orchestrator kan leveren (`run_id`, `orchestrator_id`, `orchestrator_generation`, `operation_key`, `payload_hash`, `plan_authority_operation_key`) — en die zou een gewone caller ook niet mogen verzinnen. ## Waarom dit verwart 1. **Het inputSchema presenteert beide als losse optionals.** Niets in de schemavorm zegt dat ze aan elkaar vastzitten. 2. **De tool-description noemt ze geen van beide.** De live description gaat alleen over `sprint_run_id` en demo-accounts; over `expected_status`, `ppe` of hun koppeling staat er niets. 3. **De foutcode wijst de verkeerde kant op.** `PPE_INPUT_INCOMPLETE` suggereert dat er iets aan een PPE-aanroep ontbreekt, terwijl de caller helemaal geen PPE-aanroep deed — hij wilde alleen een statusguard. 4. Het gate-3-receipt (`idea-169-gate-3-reentry-evidence-correction-stop.json`) legt vast: `all_ppe_identity_and_cas_fields_optional: true` en `server_rejects_legacy_callers_missing_these_fields: false`. Dat klopt voor callers die *geen van beide* meesturen, maar niet voor een caller die alléén het CAS-veld gebruikt — dat pad is niet afgedekt. ## Reikwijdte — een klasse, geen incident Dezelfde XOR staat in **negen** tools (`git grep -l PPE_INPUT_INCOMPLETE origin/main -- src/tools`): `create-pbi.ts` · `create-sprint.ts` · `create-story.ts` · `create-task.ts` · `log-commit.ts` · `log-implementation.ts` · `log-test-result.ts` · `update-task-plan.ts` · `update-task-status.ts` De ceremony-tools koppelen `ppe` aan `ceremony_object_key`, de log-tools aan hun eigen `complete`-vlag, `update-task-plan` aan `casComplete`. Repareer dit als klasse, niet per tool. ## Impact Geen dataschade en er is een omweg, maar elke caller die een statuswissel wil beschermen tegen een gelijktijdige schrijver kan dat niet — en juist in een multi-agent-opzet is dat het geval waarvoor CAS bestaat. De omweg (veld weglaten) verwijdert stil precies de bescherming die de caller vroeg. ## Voorgestelde richting (keuze is aan de eigenaar) Eén van tweeën, consequent over alle negen: - **A — CAS zelfstandig toestaan.** Laat `expected_status` zonder `ppe` toe als gewone optimistic-concurrency-check; laat `ppe` de CAS-waarde blijven vereisen (dus alleen de andere richting van de XOR behouden). Vervang de non-null assertion op regel 36 door een echte check. Nieuwe foutcode voor een mismatch, bijv. `TASK_STATUS_CONFLICT`. - **B — expliciet PPE-only maken.** Laat de guard staan, maar zet de koppeling in de tool-description én in de veldbeschrijving, en geef een foutcode die de caller de weg wijst (bijv. `CAS_REQUIRES_PPE`). In beide gevallen: neem de gekozen regel op in het gate-3-receiptverhaal, zodat "alle PPE-/CAS-velden zijn optioneel" niet langer strikter leest dan het is. ## Onderzoek --- *2026-08-29T14:26:02.968Z — mac:codex* Bevestigd op origin/main 93e4f5f (2026-08-29). Commit aa4c5be (`feat: make Scrum4Me mutations replay-safe`) introduceerde alle negen `PPE_INPUT_INCOMPLETE`-guards. Root cause: PPE-envelopevalidatie en companionvelden zijn met XOR gekoppeld, terwijl het inputSchema ze onafhankelijk optioneel presenteert. Bij `update_task_status` wordt bovendien het CAS-uitvoerpad op `ppe` gekozen; `expected_status` zonder PPE wordt dus vóór de mutatie afgewezen. Bij `update_task_plan` gebeurt hetzelfde met `expected_current_hash` + `replacement_hash`. Klasse-effecten: - ceremony-tools: `ppe` ↔ `ceremony_object_key`; - log-tools: `ppe` ↔ volledig paar `task_id` + `execution_key` (één los veld zonder PPE wordt nu zelfs stil geaccepteerd); - task-plan: `ppe` ↔ volledig hashpaar (één losse hash zonder PPE wordt stil genegeerd); - task-status: `ppe` ↔ `expected_status`. De bestaande PPE-integratietests dekken replay/fencing/CAS binnen PPE, maar geen inputcontractmatrix voor legacy/gewone callers. Productdoc-search op PPE/CAS/replay leverde geen bindende productspecificatie op; broncode en introductiecommit zijn daarom de primaire contractbron. ## Oplossing _Nog geen oplossing._ <!-- s4m:issue:cmtefsp920004cv17ke2ezxif -->
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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-mcp#128
No description provided.