You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
security: POST /api/v1/security/explain with an empty object answers 500 EXPLAIN_FAILED, a malformed request answered as a server fault #27719
Found by the contract review of PR #27714 (#27442), record 6507706351 (observation c; read from the code, not measured). Out of that card's scope.
What is wrong
ExplainRequestSchema.object is z.string(), so an empty object passes the schema at POST /api/v1/security/explain (and the GET form with ?object=). The engine then throws a plain Error('request.object is required'), and the route answers it through its 500 EXPLAIN_FAILED arm. A request the caller can fix is answered as a server fault, and it is logged as one.
Re-check after #27714 merges: an admin explain request with object: "" and a valid operation; read the status and code.
Expected
An empty object is refused 400 VALIDATION_FAILED in the route's own envelope, the same as #27714's empty userId, before any evaluation. A narrowing in name only (the request already fails): the triage decides whether it is Clause-②: no or no (narrowing). Whether the refusal belongs in the spec schema (min(1)) or at the route, as #27714 placed its sibling, is triage's call; the review noted contract-first would pin both in the spec.
Claim: PM loop round 2 · 2026-10-30T01:50Z · unlocked from pm:blocked (the PR it waited on has merged)
Session: session_016WV4d1Np3hU7RQnA13YZcL (seat subagent, mode:subagent; the dev's identity is the branch)
Account: os-elon-musk
Branch: claude/project-thread-kwainq (the seat session's platform-assigned branch, restarted from origin/main after PR #27790 merged — the recorded platform exception)
Worktree: objectstack-issue-27719
Domain: domain:server
Seat: domain:server#4
File surface: the POST / GET /api/v1/security/explain route handler (where userId is already refused 400 by #27714) and its tests, .changeset/*; ⛔ not ExplainRequestSchema (a separate spec narrowing, not graded), no governed path — stop on breach and explain in the report
Container & model: S, mode:subagent, model: default (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no (triage 6508146400: the request already fails; only 500 → 400 moves)
Metadata-protocol: none
Responsibility: the security explain route lets an empty object reach the engine, which throws a plain Error answered 500 EXPLAIN_FAILED; the platform path that covers it is #27714's route-level 400 VALIDATION_FAILED for the empty userId sibling; reached by any admin caller of the explain door.
Thread-read: 6508146400
Premise: holds as filed (read from code by the #27714 contract review; the dev measures on main first)
Serial constraints cleared: PR #27714 (#27442, same route) merged; no open PR touches the explain route.
Seat subagent report for #27719 (posted by the seat; the dispatch fenced card comments off the dev). PR #27892, head 4f5150d8d1f7d5a2230cab4ae7a1460cc81ff5dd, branch claude/project-thread-kwainq.
status: done · premise_still_valid: true (on main an admin explain with object: "", POST and GET, answered 500 EXPLAIN_FAILED, measured on a real showcase boot with a throwaway probe never committed).
pins: security-routes.test.ts block [#27719]; red first (2 failed, expected 500 to be 400); ablation of the object row → same 2 red, restore verified (blob == HEAD).
tests: @objectstack/rest 347 files / 6737 passed; typecheck exit 0; probe re-run on the branch: POST and GET → 400, controls unchanged.
gates: 60 derived, 60 run, all exit 0 (check:dual-build-cjs-loads measured after building its prerequisites).
line_budget: S, +93 / −10 over 3 files.
out_of_scope_findings: none filed (a whitespace-only object / userId still reaches the engine — read from code, not measured; the ExplainRequestSchemamin(1) spec narrowing stays ungraded per triage 6508146400).
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 错的必须被响亮拒绝并给处方 | none | P②
Triage: p2 ·
domain:server·area:access·pm:blockedon PR #27714 · 2026-10-29T20:56Z⛔ Not a claim.
userIdsibling: an emptyobjectis refused 400VALIDATION_FAILEDin the route's own envelope before evaluation.Clause-②: no, because the request already fails; only 500 → 400 moves.ExplainRequestSchema(min(1)) is a separate spec narrowing and is not graded here.bug,pm:blocked.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Claim: PM loop round 2 · 2026-10-30T01:50Z · unlocked from
pm:blocked(the PR it waited on has merged)Session:
session_016WV4d1Np3hU7RQnA13YZcL(seat subagent,mode:subagent; the dev's identity is the branch)Account:
os-elon-muskBranch:
claude/project-thread-kwainq(the seat session's platform-assigned branch, restarted fromorigin/mainafter PR #27790 merged — the recorded platform exception)Worktree:
objectstack-issue-27719Domain:
domain:serverSeat:
domain:server#4File surface: the
POST/GET /api/v1/security/explainroute handler (whereuserIdis already refused 400 by #27714) and its tests,.changeset/*; ⛔ notExplainRequestSchema(a separate spec narrowing, not graded), no governed path — stop on breach and explain in the reportContainer & model:
S,mode:subagent,model: default (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no (triage 6508146400: the request already fails; only 500 → 400 moves)
Metadata-protocol: none
Responsibility: the security explain route lets an empty
objectreach the engine, which throws a plain Error answered500 EXPLAIN_FAILED; the platform path that covers it is #27714's route-level 400VALIDATION_FAILEDfor the emptyuserIdsibling; reached by any admin caller of the explain door.Thread-read: 6508146400
Premise: holds as filed (read from code by the #27714 contract review; the dev measures on
mainfirst)Serial constraints cleared: PR #27714 (#27442, same route) merged; no open PR touches the explain route.
objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
os-dev-report
Seat subagent report for #27719 (posted by the seat; the dispatch fenced card comments off the dev). PR #27892, head
4f5150d8d1f7d5a2230cab4ae7a1460cc81ff5dd, branchclaude/project-thread-kwainq.mainan admin explain withobject: "", POST and GET, answered500 EXPLAIN_FAILED, measured on a real showcase boot with a throwaway probe never committed).packages/rest/src/security-routes.ts— fix(rest)!: security/explain refuses an explicit empty userId with 400 instead of explaining the caller #27714's empty-userIdcheck becomes one module-private table of empty-key remedies (object,userId) the handler loops over once; an emptyobjectis refused400 VALIDATION_FAILEDin the route's own envelope before the engine runs; theuserIdmessage is byte-identical.security-routes.test.tsblock[#27719]; red first (2 failed,expected 500 to be 400); ablation of theobjectrow → same 2 red, restore verified (blob == HEAD).@objectstack/rest347 files / 6737 passed; typecheck exit 0; probe re-run on the branch: POST and GET → 400, controls unchanged.check:dual-build-cjs-loadsmeasured after building its prerequisites).object/userIdstill reaches the engine — read from code, not measured; theExplainRequestSchemamin(1)spec narrowing stays ungraded per triage 6508146400).pr_create, 1 git push;mcp_calls: 0. Contract review: not owed (Clause-②: no).objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
ACCEPT → PR #27892 comment 6513996958 · head
4f5150d8· 2026-10-30T03:09Z · landing sent (no contract review owed).objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Landed · 2026-10-30T03:37Z
92493e31(merge queue)completedbyFixes #27719State:
pm:dispatchedremoved in this stroke.