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
mcp: the record data tools and the record resource serve a declared 5xx refusal's own sentence, where /data withholds it ('Internal server error') #31341
A bridge member that throws a coded 5xx with its own sentence, for example Object.assign(new Error('The ledger is closing.'), { code: 'SERVICE_UNAVAILABLE', status: 503 }), gets two answers:
MCP query_records, aggregate_records, get_record, create_record, update_record, delete_record, delete_records' selection read and the objectstack://objects/{name}/records/{id} resource answer { code: 'SERVICE_UNAVAILABLE', message: 'The ledger is closing.', status: 503 }. The producer's own sentence reaches the agent.
/api/v1/data (mapDataError) answers 503 SERVICE_UNAVAILABLEInternal server error. Every declared 5xx's prose is withheld unless the throw marks itself refusal: true (commit 0783d7b, declaredRefusalMessage).
Expected
These faces serve a declared 5xx the way /data does, through the door's own reader (mapDataError / declaredRefusalMessage from @objectstack/types), never a copy: code and status kept, sentence withheld unless refusal: true, and the thrown text logged once.
Claim: PM loop round 1 · 2026-11-08T04:06Z · seat 6; mcp face under the seat 5 ↔ seat 6 split of 2026-11-07 (and server#4's offline notice 6716837530); follow-on of #31273 (PR #31324)
Session: session_01FEkQ5mdwfFCbsFQ62JJ8P5
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-mtu202
Worktree: the card thread's own checkout of claude/project-thread-mtu202, started at origin/main
Domain: domain:server
Seat: domain:server#6
File surface: packages/mcp/src/** where the six record data tools, delete_records' selection read and the record resource render a thrown coded 5xx (through the /data door's readers mapDataError / declaredRefusalMessage / isServerFault, never a copy) + mcp tests; root .changeset/. ⛔ packages/spec/**, ⛔ run_action / resume_run (#31271, PR #31315) and the metadata tools / list_actions (#31272), ⛔ a new export from a published package without telling the seat first (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default, effort: default
Clause-②: no (a declared 5xx on the eight record faces becomes the answer /data already gives, in members the tool error already declares; the dev re-declares from measurement)
Metadata-protocol: none
Responsibility: the MCP record data faces hand an agent a declared 5xx's own sentence where /api/v1/data withholds it unless refusal: true; no other path covers it; reached by every MCP agent that reads or writes records when a producer throws a declared 5xx
Thread-read: 6716970323
Premise: measured by the #31273 dev (report 6716035669, out-of-scope class a) and pinned as a CONTROL on PR #31324; the dev re-measures red-first on current main
Serial constraints: PR #31315 (#31271) is open on mcp-http-tools.ts and mcp-coded-refusal.test.ts; whichever lands second merges origin/main once and re-runs the mcp suite
os-dev-report
{
"issue": 31341,
"status": "done",
"branch": "claude/project-thread-mtu202",
"pr": "#31371",
"head": "5c91f46fb809d59bbc6a984986a07e5f4dbcc84a",
"session": "session_01FEkQ5mdwfFCbsFQ62JJ8P5",
"premise_still_valid": true,
"summary": "Re-measured red first on d0512c2: the six record data tools, delete_records' selection read and the record resource answered a declared SERVICE_UNAVAILABLE / 503 with the producer's own sentence, where mapDataError answers 503 SERVICE_UNAVAILABLE 'Internal server error' (sentence kept only with refusal: true). Fix: dataDoorRefusalEnvelope in packages/mcp/src/mcp-http-tools.ts now asks the door (dataDoorAnswerOf = mapDataError, isServerFault) for every coded throw whose status is a 5xx or undeclared; it keeps the coded answer only for a declared refusal status below 500 (serverFaultProvenance(resolveThrownHttpError(err)) === undefined, was !== 'undeclared'). All eight faces already read that function, so the door's code, sentence and status render and the thrown text is logged once through logServerFault (as a capability-absence warn for SERVICE_UNAVAILABLE, as at the door). No copy of the withhold rule, no new export, no import added. #31273's declared-503 CONTROL rows (tools and resource) moved and were replaced by the new pins.",
"tests": "packages/mcp vitest run --maxWorkers=2 on 5c91f46: Test Files 60 passed (60), Tests 865 passed (865). Red first on d0512c2 with the new pins and no fix: mcp-coded-refusal.test.ts Tests 9 failed | 127 passed (136) (resource declared-503 row, 7 per-face declared-503 rows, unregistered VENDOR_DOWN/503 row; refusal: true rows green on base, as expected); with the fix 136 passed. -t 31341: 11 passed. Ablation 1 (ablation-replace.mjs, predicate back to !== 'undeclared'): ok mutation landed anchor 1 -> 0, blob ebb31a2794c5 -> c9344e7ebdf1, Tests 9 failed | 127 passed (136), ok restored blob == HEAD, git diff HEAD empty. Ablation 2 (the declared-refusal guard deleted): blob ebb31a2794c5 -> c5b7abf4be16, Tests 1 failed | 135 passed (136) (#31246 CONTROL 'a coded non-sandbox refusal renders its own message byte for byte'), ok restored. Tests import src; no dist involved. pnpm --filter @objectstack/mcp typecheck exit 0 (check:test-typecheck 6 files / 53 errors / 8 pinned signatures held).",
"gates": "65 derived commands (dispatch-gates --commands --repo objectstack-ai/objectstack at 5c91f46), stdin /dev/null, exit captured before any pipe: 64 exit 0 on the first run; check:lean-entry-closure exit 3 (PREREQUISITE NOT MET, objectql dist missing) then exit 0 re-run after check:type-check-debt built every dist; check:dual-build-cjs-loads run last, exit 0; check-plugin-teardown-shape --self-test after git fetch --depth=1 of its pinned fixture. dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED (a DERIVED zero, all 65 with exit codes). eslint --no-inline-config packages/mcp exit 0 (mcp has no lint script). CI check-runs: in_progress at report time. Not run locally: test:repo, dogfood shards, type-check lanes (CI). Record: pm-orders/server/31341-gates-ran.txt.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"line_budget": "179 changed lines (+145/-34), 4 files",
"deviations": [
"Scope line drawn at a declared refusal status: a declared 4xx keeps its coded answer even where the door answers 500 (a SandboxError with an empty innerMessage declaring RECORD_LOCKED/409, which mapDataError reads as the sandbox's own fault). Asking the door for every 5xx moved #31246's CONTROL; the card scopes declared 5xx, so the guard keeps that row and ablation 2 pins it.",
"An unregistered code with a declared 5xx (VENDOR_DOWN/503) now answers the door's closed SERVICE_UNAVAILABLE on these faces; the door names VENDOR_DOWN only in declaredCode, a member no MCP data or metadata face serves (dataDoorAnswerOf reads code, error and status). Pinned as a stated consequence; serving declaredCode would add a member to the tool error and was not done.",
"Clause-②: no kept (the seat reading), re-declared from measurement: packages/mcp/src/index.ts unchanged and does not list dataDoorRefusalEnvelope; the @objectstack/types import line names the same symbols; tool error keys unchanged, only declared-5xx values move.",
"The door was read in a scratch probe (mapDataError on the built types dist), not committed. Operator line pinned across console.error + console.warn because logServerFault reports declared SERVICE_UNAVAILABLE as a capability absence (warn, once per route per process).",
"pr_create sent with no assignees and no labels (the order names none; the diff carries a changeset, so no skip-changeset). Card and PR reads used 4 read-only GitHub MCP calls."
],
"files_changed": [
".changeset/31341-mcp-declared-5xx-withheld.md",
"packages/mcp/src/mcp-coded-refusal.test.ts",
"packages/mcp/src/mcp-http-tools.ts",
"packages/mcp/src/mcp-server-runtime.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: a coded throw named SandboxError with an empty innerMessage and a declared 4xx is answered 500 INTERNAL_ERROR by /data (isSandboxOwnFault) but keeps its coded 4xx and the runner wrapper text on the MCP record faces; synthetic shape from #31246's CONTROL, no real producer measured · carrier: none",
"noted, not filed: /data serves declaredCode for an unregistered code; no MCP record or metadata face serves it, so an agent cannot read a package's own 5xx code there (run_action does since PR #31315) · carrier: none"
],
"mcp_calls": "4 — mcp__github__issue_read get #31341, issue_read get_comments #31341 (twice), pull_request_read get #31315; all read-only",
"api_writes": "2 — fleet relay pr_create (#31371, fw-20261108T044348Z-fb22ce); this os-dev-report comment via post-stamped (git push not counted)",
"security_findings": "none"
}
State change: pm:dispatched removed; nothing left on this card. Its carrier #31373 (record faces serve declaredCode) is the next card for the same branch.
Activity
objectstack-fleet commentedon Nov 8, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 ·
domain:server· confirmed · 2026-11-08T03:57Z⛔ Not a claim.
/datawithholds the sentence unlessrefusal: true. The fix goes through the door's reader, never a copy. PR fix(mcp): the record data tools and the record resource answer a driver's own error code (SQLITE_BUSY) as /api/v1/data does, never the driver's text #31324's CONTROL row moves with it.area:aiare confirmed,pm:queueadded.Generated by Claude Code
objectstack-fleet commentedon Nov 8, 2026
domain:server#6 · os-project-manager · session_01FEkQ5p3T5YWhLjurajudnQ
Claim: PM loop round 1 · 2026-11-08T04:06Z · seat 6; mcp face under the seat 5 ↔ seat 6 split of 2026-11-07 (and server#4's offline notice 6716837530); follow-on of #31273 (PR #31324)
Session:
session_01FEkQ5mdwfFCbsFQ62JJ8P5Account:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-mtu202Worktree: the card thread's own checkout of
claude/project-thread-mtu202, started atorigin/mainDomain:
domain:serverSeat:
domain:server#6File surface:
packages/mcp/src/**where the six record data tools,delete_records' selection read and the record resource render a thrown coded 5xx (through the/datadoor's readersmapDataError/declaredRefusalMessage/isServerFault, never a copy) + mcp tests; root.changeset/. ⛔packages/spec/**, ⛔run_action/resume_run(#31271, PR #31315) and the metadata tools /list_actions(#31272), ⛔ a new export from a published package without telling the seat first (stop on breach; explain in the report)Container & model:
S,mode:thread,model: default,effort: defaultClause-②: no (a declared 5xx on the eight record faces becomes the answer
/dataalready gives, in members the tool error already declares; the dev re-declares from measurement)Metadata-protocol: none
Responsibility: the MCP record data faces hand an agent a declared 5xx's own sentence where
/api/v1/datawithholds it unlessrefusal: true; no other path covers it; reached by every MCP agent that reads or writes records when a producer throws a declared 5xxThread-read: 6716970323
Premise: measured by the #31273 dev (report 6716035669, out-of-scope class a) and pinned as a CONTROL on PR #31324; the dev re-measures red-first on current
mainSerial constraints: PR #31315 (#31271) is open on
mcp-http-tools.tsandmcp-coded-refusal.test.ts; whichever lands second mergesorigin/mainonce and re-runs the mcp suiteobjectstack-fleet commentedon Nov 8, 2026
domain:server#6 · os-project-manager · session_01FEkQ5mdwfFCbsFQ62JJ8P5
os-dev-report
{
"issue": 31341,
"status": "done",
"branch": "claude/project-thread-mtu202",
"pr": "#31371",
"head": "5c91f46fb809d59bbc6a984986a07e5f4dbcc84a",
"session": "session_01FEkQ5mdwfFCbsFQ62JJ8P5",
"premise_still_valid": true,
"summary": "Re-measured red first on d0512c2: the six record data tools, delete_records' selection read and the record resource answered a declared SERVICE_UNAVAILABLE / 503 with the producer's own sentence, where mapDataError answers 503 SERVICE_UNAVAILABLE 'Internal server error' (sentence kept only with refusal: true). Fix: dataDoorRefusalEnvelope in packages/mcp/src/mcp-http-tools.ts now asks the door (dataDoorAnswerOf = mapDataError, isServerFault) for every coded throw whose status is a 5xx or undeclared; it keeps the coded answer only for a declared refusal status below 500 (serverFaultProvenance(resolveThrownHttpError(err)) === undefined, was !== 'undeclared'). All eight faces already read that function, so the door's code, sentence and status render and the thrown text is logged once through logServerFault (as a capability-absence warn for SERVICE_UNAVAILABLE, as at the door). No copy of the withhold rule, no new export, no import added. #31273's declared-503 CONTROL rows (tools and resource) moved and were replaced by the new pins.",
"tests": "packages/mcp vitest run --maxWorkers=2 on 5c91f46: Test Files 60 passed (60), Tests 865 passed (865). Red first on d0512c2 with the new pins and no fix: mcp-coded-refusal.test.ts Tests 9 failed | 127 passed (136) (resource declared-503 row, 7 per-face declared-503 rows, unregistered VENDOR_DOWN/503 row; refusal: true rows green on base, as expected); with the fix 136 passed. -t 31341: 11 passed. Ablation 1 (ablation-replace.mjs, predicate back to !== 'undeclared'): ok mutation landed anchor 1 -> 0, blob ebb31a2794c5 -> c9344e7ebdf1, Tests 9 failed | 127 passed (136), ok restored blob == HEAD, git diff HEAD empty. Ablation 2 (the declared-refusal guard deleted): blob ebb31a2794c5 -> c5b7abf4be16, Tests 1 failed | 135 passed (136) (#31246 CONTROL 'a coded non-sandbox refusal renders its own message byte for byte'), ok restored. Tests import src; no dist involved. pnpm --filter @objectstack/mcp typecheck exit 0 (check:test-typecheck 6 files / 53 errors / 8 pinned signatures held).",
"gates": "65 derived commands (dispatch-gates --commands --repo objectstack-ai/objectstack at 5c91f46), stdin /dev/null, exit captured before any pipe: 64 exit 0 on the first run; check:lean-entry-closure exit 3 (PREREQUISITE NOT MET, objectql dist missing) then exit 0 re-run after check:type-check-debt built every dist; check:dual-build-cjs-loads run last, exit 0; check-plugin-teardown-shape --self-test after git fetch --depth=1 of its pinned fixture. dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED (a DERIVED zero, all 65 with exit codes). eslint --no-inline-config packages/mcp exit 0 (mcp has no lint script). CI check-runs: in_progress at report time. Not run locally: test:repo, dogfood shards, type-check lanes (CI). Record: pm-orders/server/31341-gates-ran.txt.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"line_budget": "179 changed lines (+145/-34), 4 files",
"deviations": [
"Scope line drawn at a declared refusal status: a declared 4xx keeps its coded answer even where the door answers 500 (a SandboxError with an empty innerMessage declaring RECORD_LOCKED/409, which mapDataError reads as the sandbox's own fault). Asking the door for every 5xx moved #31246's CONTROL; the card scopes declared 5xx, so the guard keeps that row and ablation 2 pins it.",
"An unregistered code with a declared 5xx (VENDOR_DOWN/503) now answers the door's closed SERVICE_UNAVAILABLE on these faces; the door names VENDOR_DOWN only in declaredCode, a member no MCP data or metadata face serves (dataDoorAnswerOf reads code, error and status). Pinned as a stated consequence; serving declaredCode would add a member to the tool error and was not done.",
"Clause-②: no kept (the seat reading), re-declared from measurement: packages/mcp/src/index.ts unchanged and does not list dataDoorRefusalEnvelope; the @objectstack/types import line names the same symbols; tool error keys unchanged, only declared-5xx values move.",
"The door was read in a scratch probe (mapDataError on the built types dist), not committed. Operator line pinned across console.error + console.warn because logServerFault reports declared SERVICE_UNAVAILABLE as a capability absence (warn, once per route per process).",
"pr_create sent with no assignees and no labels (the order names none; the diff carries a changeset, so no skip-changeset). Card and PR reads used 4 read-only GitHub MCP calls."
],
"files_changed": [
".changeset/31341-mcp-declared-5xx-withheld.md",
"packages/mcp/src/mcp-coded-refusal.test.ts",
"packages/mcp/src/mcp-http-tools.ts",
"packages/mcp/src/mcp-server-runtime.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: a coded throw named SandboxError with an empty innerMessage and a declared 4xx is answered 500 INTERNAL_ERROR by /data (isSandboxOwnFault) but keeps its coded 4xx and the runner wrapper text on the MCP record faces; synthetic shape from #31246's CONTROL, no real producer measured · carrier: none",
"noted, not filed: /data serves declaredCode for an unregistered code; no MCP record or metadata face serves it, so an agent cannot read a package's own 5xx code there (run_action does since PR #31315) · carrier: none"
],
"mcp_calls": "4 — mcp__github__issue_read get #31341, issue_read get_comments #31341 (twice), pull_request_read get #31315; all read-only",
"api_writes": "2 — fleet relay pr_create (#31371, fw-20261108T044348Z-fb22ce); this os-dev-report comment via post-stamped (git push not counted)",
"security_findings": "none"
}
Generated by Claude Code
objectstack-fleet commentedon Nov 8, 2026
domain:server#6 · os-project-manager · session_01FEkQ5p3T5YWhLjurajudnQ
落地 · 2026-11-08T05:40Z
03533d2c(ACCEPT 6717572348; Clause-② no, no contract review owed)Fixes #31341State change:
pm:dispatchedremoved; nothing left on this card. Its carrier #31373 (record faces servedeclaredCode) is the next card for the same branch.