Skip to content

fix(gateway): the approval list refuses an empty filter, and approval creation checks its body's types (N32 follow-up) - #513

Merged
LamaSu merged 2 commits into
masterfrom
fix/n32-approval-followups
Oct 4, 2026
Merged

LamaSu merged 2 commits into
masterfrom
fix/n32-approval-followups

Conversation

@LamaSu

@LamaSu LamaSu commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Summary

These are follow-ups to #362 (N32), from its round-2 cross-family verdict (rm-n32-362-r2-7fccd046, SHIP). This PR is stacked on #362's branch. Retarget it to master after #362 merges.

Each finding was reproduced at 7fccd046 with a failing test before any fix; 9 of the new file's 10 tests failed there. The tenth is a positive control.

  • M1: an empty filter broadened the approval list. GET /api/operator/approvals?kernelId=&status=approved treated the empty kernelId as absent and returned every kernel's approved records. The OT-2 poller (scripts/ot2-agent.py:544) executes every record it gets. An empty or whitespace-only kernelId or status is now a 400 invalid_query, decided before any store read.
  • M2: the certification 501 wording. It said certifications are not recorded. In fact, registration stores the registrant's own certification claims, unverified. The 501 now says there is no verified operator-certification read, and that the registration claims are the registrant's own and not verified. The status (501) and code (not_available) are unchanged.
  • M3: approval creation's body was only cast. kernelId and agentId must now be non-empty strings. capabilityType, when given, must be a non-empty string. parameters, when given, must be a plain object. Anything else is a 400 invalid_body, and nothing is stored. autoApprove is unchanged; gateway's WP-C (WP-C: assurance ceiling + kernel owner checks (N31 e-stop, heartbeat, devices/N71, A2A owner) — stacked on #326 #445) owns it.
  • L1. Comments only: the source-ratchet tests in operator-no-fabrication.test.ts now say they are narrow named-fixture checks, and that the behavioral tests are the protection.

Tests

  • New: packages/gateway/src/__tests__/operator-n32-followups.test.ts (10 tests).
  • One wording assertion in operator-no-fabrication.test.ts follows M2's new text.
  • Mutation checks, on file copies: undoing M1 fails 3 tests, and undoing M3 fails 5.
  • The full gateway suite and tsc --noEmit were run at this head.

Agent: pcc-readmodels (c255d7dc).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sbd5dpwvmRsJqdff9zvNW6

LamaSu added 2 commits October 3, 2026 01:34
… creation checks its body's types (N32 follow-up)

Follow-ups to PR #362 (N32) from its r2 SHIP verdict
(rm-n32-362-r2-7fccd046.astra.verdict.md, "New findings, ranked").

M1 - GET /api/operator/approvals treated an explicitly empty or
whitespace-only kernelId/status as absent, silently broadening the
query past what the caller asked for (unsafe beside the OT-2 poller,
which executes every returned record). Reproduced at 7fccd04: two
kernels with approved records, `?kernelId=&status=approved` returned
200 with both records instead of 400; a whitespace-only kernelId and
an empty `status` alone showed the same class of bug. Fixed by
rejecting an empty/whitespace-only kernelId or status with 400
invalid_query, before any store read, matching the existing
repeated-parameter check in the same handler.

M2 - GET /api/operator/certifications said certifications are not
recorded and that no certification store exists, but machine
registration persistently carries the registrant's own certification
claims (db/schema/onboarding.ts). Reproduced by inserting a
registration with a nonempty operator.certifications and observing
the old wording ("not recorded" / "no certification store") contradict
it. Reworded the 501 to say there is no verified
operator-certification read, and that certifications given at
registration are the registrant's own claims, stored with the
registration and not verified. Status and error code unchanged.
Updated the one pre-existing assertion that pinned the old "not
recorded" wording for this route (the wording itself was the subject
of the fix).

M3 - POST /api/operator/approvals only cast its body, never validated
it. Reproduced: `capabilityType: {"unexpected": true}` was accepted
(200) and stored verbatim; a non-string agentId, a parameters array,
and an explicit `parameters: null` were each silently accepted or
substituted instead of rejected. Fixed by requiring capabilityType
(when present and not null) to be a non-empty string, kernelId and
agentId to be non-empty strings (same "kernelId and agentId required"
message), and parameters (when present) to be a plain object, never
an array or null. autoApprove handling is unchanged (owned by gateway
WP-C, #445).

L1 - The operator.ts source ratchet is a narrow check for named
fixtures, not a general fabrication guard. Reworded its comments (and
the equivalent comment on the five-named-field check in the fixture
describe block) to say so and point at the behavioral contract tests
as the real protection. No test logic changed.

New file packages/gateway/src/__tests__/operator-n32-followups.test.ts
reproduces all three findings at 7fccd04 (9 failing / 1 passing
control) and passes after the fix (10/10). Full gateway suite: 189
test files, 3013 passed + 6 skipped, no Errors line. tsc --noEmit:
clean. Mutation check (undo one fix in a scratch copy, confirm the
new tests fail, restore, cmp byte-identical) passed for M1 and M3.

subagent: implementer-echo
agent: pcc-readmodels (c255d7dc)
…(N32, #513 r1 M3)

Cross-family review r1 of #513 (rm-n32-513-r1-cf16f558): M1, M2 and L1 CLOSED; M3 OPEN. An
explicit `capabilityType: null` skipped the type check and still stored an approval (200).
Reproduced at cf16f55 by a new case in operator-n32-followups.test.ts, before the fix:
expected 200 to be 400.

Only an absent field now means "unspecified". null, like any other non-string or an empty
string, is a 400 invalid_body, and nothing is stored. Reverting this one condition is the
cf16f55 state the reproduction failed on.

agent: pcc-readmodels (c255d7dc)
@LamaSu
LamaSu changed the base branch from fix/operator-routes-no-fabrication to master October 3, 2026 22:13
@LamaSu LamaSu closed this Oct 3, 2026
@LamaSu LamaSu reopened this Oct 3, 2026
@LamaSu
LamaSu merged commit e2f7e38 into master Oct 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant