POST /api/pods with type: 'agent-room' succeeds and produces a one-member DM-kind pod, violating ADR-001 §3.10 (DM pods are strictly 1:1 — exactly two members) at creation time.
Demonstration
Run against controllers/podController.ts using the existing podController.test.js harness (mocked Pod, so this captures controller behaviour):
createPod({ body: { name: 'Sneaky Room', type: 'agent-room' }, userId: 'creator' })
→ res.status : never called (no 400)
→ new Pod(…) : { type: 'agent-room', members: ['creator'] }
→ save() : called
→ res.json() : called
Why it gets through
VALID_POD_TYPES (controllers/podController.ts:26) includes agent-room. The sibling allowlist at routes/agentsRuntime.ts:2554 omits both agent-room and agent-dm; this one omits only agent-dm, with no stated reason for the difference.
- The gate at
:383 therefore passes, and :387–395 constructs the pod with members: [req.userId] — one member.
Pod.ts's only pre-save hook (:148) pushes createdBy into members and enforces nothing about DM cardinality, so there is no model-level backstop on this path.
The guard exists — it is just never applied at creation
DM_POD_TYPES_GUARD is consulted in four places:
| site |
path |
controllers/podController.ts:477 |
join |
routes/podInvites.ts:175 |
invite create |
routes/podInvites.ts:242 |
invite redeem |
routes/agentsRuntime.ts:2444 |
discovery type exclusion |
Every entrance into a DM pod is guarded except the one that makes the pod.
Suggested fix
Apply DM_POD_TYPES_GUARD in createPod — refuse DM kinds with a 400 naming the correct rail (ensureAgentInPod / commonly_open_dm), matching how agentsRuntime's allowlist already behaves. Either that, or drop agent-room from podController's VALID_POD_TYPES so the two allowlists agree.
Note VALID_POD_TYPES in podController is also used at :279 by getPodsByType, a read filter — so narrowing the constant for a creation reason silently narrows a read endpoint. If the fix is "drop agent-room from the list," that side effect needs handling; adding the guard to createPod avoids it.
Provenance
Surfaced while reviewing #802, whose ADR-016 §Kind paragraph states the reasoning correctly — the allowlists omit DM kinds because "creating one through a generic create-pod endpoint would produce a 1-member pod violating the §3.10 two-member guard at birth" — and then notes podController permits agent-room as an unresolved asymmetry. It is not an asymmetry; it is that failure, reachable today.
Not verified
- Live behaviour — unit-level only; no POST to production, and the PG mirror write was not traced.
- Whether any
agent-room rows created this way exist in production. Unmeasured, not unobserved.
POST /api/podswithtype: 'agent-room'succeeds and produces a one-member DM-kind pod, violating ADR-001 §3.10 (DM pods are strictly 1:1 — exactly two members) at creation time.Demonstration
Run against
controllers/podController.tsusing the existingpodController.test.jsharness (mockedPod, so this captures controller behaviour):Why it gets through
VALID_POD_TYPES(controllers/podController.ts:26) includesagent-room. The sibling allowlist atroutes/agentsRuntime.ts:2554omits bothagent-roomandagent-dm; this one omits onlyagent-dm, with no stated reason for the difference.:383therefore passes, and:387–395constructs the pod withmembers: [req.userId]— one member.Pod.ts's only pre-save hook (:148) pushescreatedByintomembersand enforces nothing about DM cardinality, so there is no model-level backstop on this path.The guard exists — it is just never applied at creation
DM_POD_TYPES_GUARDis consulted in four places:controllers/podController.ts:477routes/podInvites.ts:175routes/podInvites.ts:242routes/agentsRuntime.ts:2444Every entrance into a DM pod is guarded except the one that makes the pod.
Suggested fix
Apply
DM_POD_TYPES_GUARDincreatePod— refuse DM kinds with a 400 naming the correct rail (ensureAgentInPod/commonly_open_dm), matching howagentsRuntime's allowlist already behaves. Either that, or dropagent-roomfrompodController'sVALID_POD_TYPESso the two allowlists agree.Note
VALID_POD_TYPESinpodControlleris also used at:279bygetPodsByType, a read filter — so narrowing the constant for a creation reason silently narrows a read endpoint. If the fix is "dropagent-roomfrom the list," that side effect needs handling; adding the guard tocreatePodavoids it.Provenance
Surfaced while reviewing #802, whose ADR-016 §Kind paragraph states the reasoning correctly — the allowlists omit DM kinds because "creating one through a generic create-pod endpoint would produce a 1-member pod violating the §3.10 two-member guard at birth" — and then notes
podControllerpermitsagent-roomas an unresolved asymmetry. It is not an asymmetry; it is that failure, reachable today.Not verified
agent-roomrows created this way exist in production. Unmeasured, not unobserved.