The no-ungoverned-operation walk reads a RETURN TYPE's braces as the method body — and never follows a `this.` hop
Found while running MOTIR-2355: the gate added to aiChatService.submitDiscoveryTurn was invisible to the guard, so /api/ai/chat stayed in the PENDING bucket after it was demonstrably gated.
Two independent blind spots in tests/permissions/noUngovernedOperation.test.ts, both of the MOTIR-2292 family (a brace that is not the body's):
-
A RETURN TYPE's braces are captured as the method body.
methodBodywalks the parameter list by paren depth (the MOTIR-2292 repair) and then takessrc.indexOf('{', cursor)— but the very next thing after)is the return annotation, and): Promise<{ jobId: string }> {puts a{there. Reproduced directly:BODY CAPTURED: "{ jobId: string }"Every service method whose return type contains a brace therefore reports UNGOVERNED however plainly it asserts. MOTIR-2292 fixed WHERE the walk starts looking and left this half untouched, exactly as MOTIR-2304 fixed WHAT it recognises and left the same half untouched.
-
A
this.someMethod(call is not followed.serviceCallsmatchessomeService.method(only, so a method that delegates to a sibling on the SAME object —getOrCreateForProject→this.getOrCreateForScope, which asserts — reads as ungated. The walk already hops ACROSS services and (since MOTIR-2304) into module-local functions; the same-object hop is the one direction it still cannot see.
This blocks the close-out card: PENDING cannot reach 0 while gated operations are counted as pending, so it is fixed inside this story rather than logged for later.
Acceptance criteria
methodBodyskips a return-type annotation before taking the body: a{that sits inside<…>or immediately follows a:belongs to the TYPE, not the body.- A SYNTHETIC control pins the failure shape — the same pattern the MOTIR-2292 and MOTIR-2304 controls use — so the regression cannot return once real services stop exhibiting it. Both halves: the fix finds the gate in
): Promise<{ … }> {, and still reports NO gate when the body genuinely has none. bodyIsGatedfollows athis.method(call to the sibling method of the same service, with its own positive AND negative synthetic control.- The pinned
PENDINGandCLAIMED_BUT_UNVERIFIEDcounts are re-derived by RUNNING the guard, and the comment on each records that the fall is a measurement correction, not gates being added — the distinction MOTIR-2292 and MOTIR-2304 both had to write down. pnpm test tests/permissions/is green.
Context refs
tests/permissions/noUngovernedOperation.test.ts—methodBody,serviceCalls,bodyIsGated, and the two existing synthetic controls to mirror.lib/services/aiChatService.ts—submitDiscoveryTurn, the reproduction.lib/services/planChangeSessionsService.ts—getOrCreateForProject, thethis.hop.docs/decisions/permission-inventory.md— the GATE TODAY, MEASURED section, which records each instrument correction.