Update: the original scope of this issue landed on main — a declared, body-independent
consequence now stands down the unread-body caveat instead of downgrading the verdict.
A narrow residual survives, and this issue is retitled to it.
The residual
declaresBodyIndependentChannel (packages/server/src/events/declared.ts:87-107) returns true
for text, signal, state, and an element matched by value or text. Everything else falls
to default: false, including:
PredicateKind.ROUTE
- a non-absent
element located by role, name or testid
So a caller who declares "this action lands on /lobby" in advance, and whom Reticle then watches
land on /lobby, still gets verified: "unknown" with reason outcome_unread if the write's
response body was not captured — even though the consequence they named held on a channel that has
nothing to do with the body.
Repro
- Leave
captureNetworkBodies off (the default).
reticle_act_and_wait on a control that issues a write and changes route, with
until: { kind: "route", ... } — or until: { kind: "element", query: { role: "heading", name: "…" } }.
- The route/element change is observed and the embedded evidence says pass; the top-level verdict
is unknown / outcome_unread.
Fix
Add PredicateKind.ROUTE, and non-absent element matches by role / name / testid, to
declaresBodyIndependentChannel. A route change the caller named before the action and Reticle
watched happen is body-independent evidence by construction — the same argument the existing text
and state cases already rest on.
Keep the same conservatism the function already documents: top level and allOf only, never an
anyOf branch, and never an absent predicate.
Update: the original scope of this issue landed on
main— a declared, body-independentconsequence now stands down the unread-body caveat instead of downgrading the verdict.
A narrow residual survives, and this issue is retitled to it.
The residual
declaresBodyIndependentChannel(packages/server/src/events/declared.ts:87-107) returnstruefor
text,signal,state, and anelementmatched byvalueortext. Everything else fallsto
default: false, including:PredicateKind.ROUTEelementlocated byrole,nameortestidSo a caller who declares "this action lands on
/lobby" in advance, and whom Reticle then watchesland on
/lobby, still getsverified: "unknown"with reasonoutcome_unreadif the write'sresponse body was not captured — even though the consequence they named held on a channel that has
nothing to do with the body.
Repro
captureNetworkBodiesoff (the default).reticle_act_and_waiton a control that issues a write and changes route, withuntil: { kind: "route", ... }— oruntil: { kind: "element", query: { role: "heading", name: "…" } }.is
unknown/outcome_unread.Fix
Add
PredicateKind.ROUTE, and non-absentelementmatches byrole/name/testid, todeclaresBodyIndependentChannel. A route change the caller named before the action and Reticlewatched happen is body-independent evidence by construction — the same argument the existing
textand
statecases already rest on.Keep the same conservatism the function already documents: top level and
allOfonly, never ananyOfbranch, and never anabsentpredicate.