Context
@neo-kimi-phoebe's break-it review of PR #15389 (the fleet wire's first write verb) flagged one non-blocking assumption on the record: "whitelisted value shapes are unvalidated at the seam — the primitive's tolerance for a non-array relatedTickets is assumed, not stated." Author verification (this ticket's premise V-B-A) confirms the assumption is FALSE in the dangerous direction: the primitive does not tolerate it, it corrupts or throws.
The Problem
MailboxService.addMessage destructures relatedTickets = [] (ai/services/memory-core/MailboxService.mjs:1249) and later spreads it: relatedTickets : [...relatedTickets] (:435). For a non-array value crossing the seam:
- a string silently character-splits —
relatedTickets: "15379" stores ['1','5','3','7','9']: five garbage ticket references ride the WAL into the graph with no error anywhere;
- a number (or any non-iterable) throws inside the primitive mid-send.
The MCP transport cannot produce this (its schema enforces String[]), but the fleet wire can: FleetControlBridge.composeOperatorMessage forwards relatedTickets whenever it is !== undefined with no shape check, and the fleet transport has no schema layer. Reachable by any authenticated cockpit caller with a malformed body — low severity (authenticated, operator-facing surface), but silent data corruption is the worst failure shape for a coordination-fabric write.
The Architectural Reality
ai/services/fleet/FleetControlBridge.mjs — composeOperatorMessage builds its payload by field whitelist (to, subject, body, priority, wakeSuppressed, relatedTickets), conditionally attaching each. The whitelist governs WHICH fields cross; nothing governs their SHAPE. The verb already owns an honest structured refusal ({status: 'not-wired', reason}), so a rejection shape exists to extend.
ai/services/memory-core/MailboxService.mjs:1249/:435 — the primitive's @param {String[]} contract is enforced by the MCP schema on the MCP path only. priority / wakeSuppressed are NOT in scope: the primitive's own class-policy guards reject invalid values there (verified live in the #15376 witnesses).
- Defense belongs at the unvalidated ingress boundary — the verb — not the primitive: the primitive's typed contract is legitimately owned by its schema-validated callers, and the verb is the single schema-less caller. This is the same discipline as the verb's existing field whitelist, one level deeper.
- Structure gate: no new file; the touched surfaces are the verb + its existing witness file
test/playwright/unit/ai/services/fleet/wireOperatorComposeWriter.spec.mjs (sibling precedent = the PR #15389 smuggling-negative battery).
The Fix
In composeOperatorMessage, before attaching: relatedTickets must be undefined or an Array; anything else returns the verb's honest structured rejection — {status: 'rejected', reason: 'relatedTickets must be an array'} — never a silent drop (a caller that sent garbage should learn it sent garbage) and never a pass-through.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
FleetControlBridge.composeOperatorMessage (relatedTickets param) |
MailboxService.addMessage @param {String[]} contract (:1226) |
non-array + non-undefined → {status:'rejected', reason} before the writer is invoked |
unchanged: undefined omits the key (sender-class defaults stay the primitive's decision) |
verb JSDoc gains the shape rule |
unit witnesses below |
Acceptance Criteria
Out of Scope
- Primitive-level normalization in
MailboxService.addMessage (its typed contract is schema-enforced on the MCP path; adding tolerance there would mask caller bugs).
- Shape guards for
priority / wakeSuppressed (already rejected by the primitive's class-policy guards).
- Any other wire verb (read-observe verbs carry no write payloads).
Related
Decision Record impact: none (extends the PR #15389 whitelist discipline; no ADR surface).
Live latest-open sweep: checked latest 20 open issues at 2026-07-18T02:44Z; no equivalent found. A2A in-flight claim sweep (status:all, limit 30, herd window): no competing claim on this scope.
Origin Session ID: 89818500-8a12-4162-b41f-8947703b1b06
Retrieval Hint: "composeOperatorMessage relatedTickets shape guard character-split"
Context
@neo-kimi-phoebe's break-it review of PR #15389 (the fleet wire's first write verb) flagged one non-blocking assumption on the record: "whitelisted value shapes are unvalidated at the seam — the primitive's tolerance for a non-array
relatedTicketsis assumed, not stated." Author verification (this ticket's premise V-B-A) confirms the assumption is FALSE in the dangerous direction: the primitive does not tolerate it, it corrupts or throws.The Problem
MailboxService.addMessagedestructuresrelatedTickets = [](ai/services/memory-core/MailboxService.mjs:1249) and later spreads it:relatedTickets : [...relatedTickets](:435). For a non-array value crossing the seam:relatedTickets: "15379"stores['1','5','3','7','9']: five garbage ticket references ride the WAL into the graph with no error anywhere;The MCP transport cannot produce this (its schema enforces
String[]), but the fleet wire can:FleetControlBridge.composeOperatorMessageforwardsrelatedTicketswhenever it is!== undefinedwith no shape check, and the fleet transport has no schema layer. Reachable by any authenticated cockpit caller with a malformed body — low severity (authenticated, operator-facing surface), but silent data corruption is the worst failure shape for a coordination-fabric write.The Architectural Reality
ai/services/fleet/FleetControlBridge.mjs—composeOperatorMessagebuilds its payload by field whitelist (to, subject, body, priority, wakeSuppressed, relatedTickets), conditionally attaching each. The whitelist governs WHICH fields cross; nothing governs their SHAPE. The verb already owns an honest structured refusal ({status: 'not-wired', reason}), so a rejection shape exists to extend.ai/services/memory-core/MailboxService.mjs:1249/:435— the primitive's@param {String[]}contract is enforced by the MCP schema on the MCP path only.priority/wakeSuppressedare NOT in scope: the primitive's own class-policy guards reject invalid values there (verified live in the #15376 witnesses).test/playwright/unit/ai/services/fleet/wireOperatorComposeWriter.spec.mjs(sibling precedent = the PR #15389 smuggling-negative battery).The Fix
In
composeOperatorMessage, before attaching:relatedTicketsmust beundefinedor anArray; anything else returns the verb's honest structured rejection —{status: 'rejected', reason: 'relatedTickets must be an array'}— never a silent drop (a caller that sent garbage should learn it sent garbage) and never a pass-through.Contract Ledger
FleetControlBridge.composeOperatorMessage(relatedTicketsparam)MailboxService.addMessage@param {String[]}contract (:1226){status:'rejected', reason}before the writer is invokedundefinedomits the key (sender-class defaults stay the primitive's decision)Acceptance Criteria
relatedTickets(string, number, object) returns{status:'rejected'}from the verb and the injected writer is NEVER invoked (witness: captured-writer stays null for all three shapes).undefinedstill omits the key entirely (the existing omitted-keys-stay-absent witness keeps passing — regression pin).[...spread]at the primitive).Out of Scope
MailboxService.addMessage(its typed contract is schema-enforced on the MCP path; adding tolerance there would mask caller bugs).priority/wakeSuppressed(already rejected by the primitive's class-policy guards).Related
Decision Record impact: none (extends the PR #15389 whitelist discipline; no ADR surface).
Live latest-open sweep: checked latest 20 open issues at 2026-07-18T02:44Z; no equivalent found. A2A in-flight claim sweep (status:all, limit 30, herd window): no competing claim on this scope.
Origin Session ID: 89818500-8a12-4162-b41f-8947703b1b06
Retrieval Hint: "composeOperatorMessage relatedTickets shape guard character-split"