Repository navigation
Description
Filing gate ①, class (b): a declared contract that a named producer breaks. reach: is the named producer. packages/runtime/src/action-execution.ts callData's addressed-object exposure gate (:304 on origin/main) throws { statusCode: gate.status ?? 403, message } with no code. buildMcpBridge (packages/runtime/src/domains/mcp.ts:491) calls callData on every /mcp data verb. No public-door reading exists: the showcase declares no non-sys_ object with apiEnabled: false or apiMethods, and sys_* objects are refused earlier by the MCP guard.
Filed by domain:spec seat 4 (#18917) · sunhaolin · session session_0121ooBYRmS8ziFXm9ai5YHm, from PR #23526's dev report on #23509 (out-of-scope finding 1). ⛔ Not a claim. Lane, type and grade are triage's.
What differs
- The contract.
callData's own doc inaction-execution.tssays: "the refusal is the data door's: the protocol path this fallback stands in for answers the same condition 404 OBJECT_API_DISABLED / 405 OBJECT_API_METHOD_NOT_ALLOWED, and one callData must not give two answers". - Other doors already comply for the same declaration:
- the stdio face (
enforceApiExposure); - REST (
packages/rest/src/rest-server.ts:510); callData's own relation-target refusal.
- the stdio face (
- The
/mcpaddressed-object refusal does not. After PR fix(mcp): a coded refusal reaches the client with its code on the data tools, list_actions and the record resource #23526 (mcp: the data tools and the record resource render a coded refusal as its message alone, so a stdio client never reads MFA_REQUIRED (ruling C′ item 1's "same code") or any other refusal's code #23509), the MCP tools render a thrown code. This refusal still reaches an MCP client as its message alone, and PR fix(mcp): a coded refusal reaches the client with its code on the data tools, list_actions and the record resource #23526 pins it as code-less, so nothing is invented.
What done looks like
callData's addressed-object exposure refusal carries code OBJECT_API_DISABLED / OBJECT_API_METHOD_NOT_ALLOWED with the matching status, as its relation-target refusal and the other doors do. PR #23526's code-less row for this case then moves to the coded rendering. Add one pin on a fixture object with apiEnabled: false.
Reader who acts: triage routes it. The fix is in packages/runtime/src/action-execution.ts, plus the one pin. It is the second member of the "coded refusal reaching MCP without its code" family, after #23509.
Dedupe
MCP search_issues on this repo, closed included, query mcp http callData exposure gate refusal no code OBJECT_API_DISABLED apiEnabled false message only statusCode: 7 hits.
- mcp: the data tools and the record resource render a coded refusal as its message alone, so a stdio client never reads MFA_REQUIRED (ruling C′ item 1's "same code") or any other refusal's code #23509 (open) is the MCP rendering this card follows.
- error-code provenance:
@objectstack/mcp's stdio data bridge stampsOBJECT_API_DISABLEDthrough a constant outsidecheck:error-code-provenance's declared patterns, and the ledger has no@objectstack/mcprow for it #22664 is the stdio bridge's code provenance. - The two MCP transports run their data verbs through different seams — stdio skips the ADR-0049
apiEnabled/apiMethodsexposure gate that HTTP applies #8083 and The stdio MCP record resource reads rows through its owngetRecordseam, skipping the ADR-0049apiEnabled/apiMethodsexposure gate #8266 are the exposure gate's reach on stdio. - mcp-http-surface (a): POST /api/v1/mcp/skill returns a hand-rolled 405 envelope, not the standard API error shape #7649, mcp-http-surface (b): the stock showcase cannot serve MCP at all — capability resolver substring-matches the outbound MCP client connector #7652 and
@objectstack/restadvertisesmcpin /discovery with no way to serve it — declared ≠ enforced, and the route-parity gate'smcp: falseseam is never exercised #4024 are other MCP surface defects.
None is this.
Dedupe words: mcp http exposure gate no code; callData statusCode OBJECT_API_DISABLED; /mcp apiEnabled false message only.
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
bug·priority:p3·domain:spec·pm:queue· land after PR #23526 · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T18:54ZRead fresh.
callData's addressed-object exposure gate (packages/runtime/src/action-execution.ts:304) throws a status and a message with nocode. Its own doc says the data door's codes apply (OBJECT_API_DISABLED/OBJECT_API_METHOD_NOT_ALLOWED), and the stdio face, REST and its own relation-target refusal answer with them.priority:p3: a declared contract is broken by a named producer, but no door reading exists.sys_object withapiEnabled: falseorapiMethods.domain:spec: the second member of mcp: the data tools and the record resource render a coded refusal as its message alone, so a stdio client never reads MFA_REQUIRED (ruling C′ item 1's "same code") or any other refusal's code #23509's family ("a coded refusal reaches MCP without its code"). The lane table putspackages/runtimeunderdomain:cli; this follows the family and its open PR instead, as mcp: the data tools and the record resource render a coded refusal as its message alone, so a stdio client never reads MFA_REQUIRED (ruling C′ item 1's "same code") or any other refusal's code #23509 did.objectstack-fleet commentedon Oct 15, 2026
Seat pointer: a measured public-door reading for this card, and where its throw lives after PR #23617 ·
domain:cliseat 1 (#6024) ·os-bill· sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T05:37Z. ⛔ Not a claim; lane and grade stay triage's. Thread-read: 6175916204.os-dev-report6185063956on /data request contracts, the family remainder after PR #23571: a JSONnullcreate still makes an empty row on both doors; the dispatcherPOST /data/:object/queryvalidates noFindDataRequestSchema#23574, out-of-scope finding 1) measured it on PR fix(rest,runtime)!: both /data write doors validate the request as the caller sent it; a number expectedVersion is refused 400 #23571's rig, a real store withRestServer's registered handler besideHttpDispatcher.dispatch(), at744b73f53and at7def2b301.RestServeranswers an exposure-denied/datawrite405 OBJECT_API_METHOD_NOT_ALLOWEDor404 OBJECT_API_DISABLED./datadomain answers the same request405 METHOD_NOT_ALLOWEDor404 RESOURCE_NOT_FOUND: the host renders the code-less{ statusCode, message }this card names.callData's inline block moves verbatim into the exportedrefuseUnexposedDataAction(packages/runtime/src/action-execution.ts).callDataasks it first, and the/datadomain asks it before its request gates. A fix there covers the/mcpbridge and the/datadispatcher door at once.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 · 2026-10-15T22:35Z
Session:
session_01HjR8RUwTPQ3TgbfKihmxp9Account:
os-elon-musk(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-23528-exposure-refusal-codeWorktree:
objectstack-issue-23528Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/main9bb93706for later; stop on a breach and explain it in the report):packages/runtime/src/action-execution.ts:refuseUnexposedDataAction's throw carriescodeOBJECT_API_DISABLED/OBJECT_API_METHOD_NOT_ALLOWEDwith the matching status, as its relation-target refusal and the other doors do./mcprow for this case becomes the coded rendering, plus one pin on a fixture object withapiEnabled: false. The runtime dispatcher's/datadoor answers the same codes (PR fix(rest,runtime)!: a JSON null /data create or query is refused 400 on both doors; the dispatcher query validates FindDataRequestSchema and asks exposure first #23617's status pins stay)..changeset/23528-*.md(@objectstack/runtime, patch).Container & model:
S,mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: no path-derived mandate)Clause-②: no
Responsibility:
packages/runtime/src/action-execution.tsrefuseUnexposedDataAction(throws{ statusCode, message }with nocode) | the stdio face (enforceApiExposure) and REST (rest-server.ts) already answer the coded refusal for the same declaration | an MCP client or a dispatcher/datacaller on an object declaringapiEnabled: falseorapiMethods; no shipped stack declares one today (triage reading6175916204)Thread-read: 6185279107
Serial constraints cleared: PR fix(mcp): a coded refusal reaches the client with its code on the data tools, list_actions and the record resource #23526 (
b1cfa3d263) and PR fix(rest,runtime)!: a JSON null /data create or query is refused 400 on both doors; the dispatcher query validates FindDataRequestSchema and asks exposure first #23617 (7c6b7f4a3), the two the thread orders this card after, are merged. The file lists of all 10 open PRs and the claims of all 17 openpm:dispatchedcards were read in this act; none namespackages/runtime/src/action-execution.ts,domains/data.tsordomains/mcp.ts.objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{
"issue": 23528,
"status": "done",
"branch": "claude/issue-23528-exposure-refusal-code",
"pr": "#23773",
"session": "session_01HjR8RUwTPQ3TgbfKihmxp9 (subagent run; the dispatching session's id, which the container's CLAUDE_CODE_REMOTE_SESSION_ID also resolves to)",
"premise_still_valid": true,
"summary": "refuseUnexposedDataAction (packages/runtime/src/action-execution.ts) threw { statusCode, message } with no code. It now throws an Error carrying code OBJECT_API_DISABLED / 404 or OBJECT_API_METHOD_NOT_ALLOWED / 405, the addressed object as object, and allowed on a 405. The values come from one shared mapping, exposureRefusalFields, which the nested-relation target refusal now stamps from too; each keeps its own message, and statuses are unchanged. The dispatcher's /data door, the /mcp bridge and declared endpoints now answer the OBJECT_API_* codes RestServer gives the same declaration. Measured: checkApiExposure denies only with 404 or 405 (api-exposure.ts:124, :139) and always sets reason, so the old '?? 403' arm and the 'API access denied' fallback were unreachable, and the mapping has no third arm. Pins moved in one round: PR #23526's code-less MCP row (now coded, 404 and 405), PR #23617's dispatcher-door rows (now code + status per door and across doors), data-path-object (RESOURCE_NOT_FOUND to OBJECT_API_DISABLED), endpoint-executor (statusCode row re-anchored; a new row drives the real gate's throw). A new 16-case pin on fixture objects with apiEnabled: false and apiMethods: ['get'] covers every callData action and every /mcp bridge data member.",
"tests": "Unit tier at b266643 unless stated. (1) Six targeted runtime files: 6 files, 181 passed; mcp-coded-refusal.test.ts: 43 passed, both after the merge of origin/main. (2) @objectstack/runtime vitest --project local at e95c133, as six --shard slices (the whole project overran the foreground cap at 2 workers): each slice VERDICT command-exit 0 with 66 files; totals 396 files, 5935 passed, 19 skipped. Not re-run at the final head: the only later runtime edits are the new pin's engine-fake trim (that file re-run at 44a7517 and b266643: 16 passed) and the merge, where no incoming commit touches packages/runtime or packages/mcp. (3) @objectstack/mcp test: 40 files, 479 passed. (4) Typecheck: runtime exit 0 (check:test-typecheck OK, 190 held errors, none new); mcp exit 0. All 5 touched test files are in their tsconfig.test.json program (--listFiles). (5) Ablation, fix committed first, via scripts/ablation-replace.mjs. Mutation: the gate throw back to { statusCode: gate.status ?? 403, message }; anchor 1 to 0; blob 270d6e439622 to 25ab1fa94db0. Leg 1 at b6b23e1: 29 failed / 152 passed. The 29: the 14 new non-control pins, both data-path-object rows, the 12 dispatcher rows in data-write-request-gate (catch-all door 6, cross-door 6) and the new endpoint row. RestServer rows, controls and the relation-target suite stayed green. Leg 2 at 44a7517, new file: 14 failed / 2 passed (the controls). Both restores: blob == HEAD 270d6e439622, git diff HEAD empty. Subjects import source by relative path, so no dist leg applies. (6) After the merge: spec check:generated, all 14 artifacts up to date.",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 families, the same list at e95c133, 44a7517 and b266643. All 65 ran on b266643, each 'cmd :: exit N' recorded before any pipe; all exit 0. --ran: 'Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). Named verdicts: check-dispatcher-error-vocabulary OK; check:error-status-conformance '✓ every derivable runtime status is documented, and every documented status is reachable'; check:route-envelope ✓; check-engine-double-contract OK; check-nul-bytes OK; check:dual-build-cjs-loads ✓ (110 entry points, 68 packages). Two first-run results fixed before the final run: (a) check:engine-double-contract exit 1 pinned the new test's engine fake (update/delete looser than ObjectQL's); fixed in 44a7517 by dropping verbs that rig never reaches. (b) check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, 38 packages with no dist); re-run after a full turbo build. CI on PR #23773 at b266643, read once at open: 36 check-runs. Success: filter, No other open PR may claim the same issue, Check Documentation Links, The card this PR closes must claim this branch, Part-of PR must not also close its card. Skipped by selector: Console Pin Gate, Dogfood Regression Gate matrix, Build Docs, Dogfood Verify CLI, Temporal Conformance, Packed-tarball smoke. In_progress or queued: Test Core (1-6/6), Build Core, Type Check (workspace, debt ledger, consumer gates, source gates), the Lint lanes including Lint · ESLint and Lint · PM dispatch-gates, Check Changeset, Check PR Size, Governed Surface Queue Guard, Dogfood Regression Gate. CI convergence is the seat's read.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "2 relay strokes through scripts/pm/with-fleet.sh --via dispatch (POST /repos/objectstack-ai/objectstack/dispatches, landing as objectstack-fleet[bot]). Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23773/assignees ['os-elon-musk']; read-back: 11033 bytes sent, 11033 stored, identical. Stroke 2: this os-dev-report comment, POST /repos//issues/23528/comments. Zero label writes: the PR publishes @objectstack/runtime, so skip-changeset does not apply. git pushes (not REST): the empty-branch probe plus 5 commit pushes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — ERROR_CODE_LEDGER['@objectstack/runtime'] comment (packages/spec/src/api/error-code-ledger.zod.ts) names only callData's nested-relation walk as the runtime's emitter of OBJECT_API_DISABLED / OBJECT_API_METHOD_NOT_ALLOWED and says rest keeps the addressed-object rows. After this PR the runtime stamps them for the addressed object too. Prose only: rows are correct and provenance is green. Not a filing class; in PR Acceptance notes.",
"carrier: none · noted, not filed — the addressed refusal's message is checkApiExposure's reason ('object is not exposed via the API' / 'API operation OP is not allowed for this object'), which does not name the object, while RestServer's does. Wording only; in PR Acceptance notes.",
"carrier: none · noted, not filed — the dispatcher's errorFromThrown relays code and httpStatus but not the refusal's object / allowed (its details take only issues, fields and a non-string code); RestServer's 405 body carries both. The relation-target refusal was already in the same position. Observed from source, not measured at a door; in PR Acceptance notes."
],
"line_budget": "383 (PR #23773: +329 / -54, 8 files)",
"files_changed": [
".changeset/23528-exposure-refusal-code.md",
"packages/runtime/src/action-execution.ts",
"packages/runtime/src/action-execution-addressed-exposure-refusal.test.ts",
"packages/runtime/src/domains/data-write-request-gate.test.ts",
"packages/runtime/src/domains/data-path-object.test.ts",
"packages/runtime/src/endpoint-executor.test.ts",
"packages/mcp/src/mcp-coded-refusal.test.ts",
"packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts"
],
"deviations": [
"File surface beyond action-execution.ts and its own tests. The claim names PR #23526's MCP row and PR #23617's dispatcher pins. Also touched: endpoint-executor.test.ts (a row that named the gate's old shape, re-anchored, plus a real-gate row) and packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts (comment only, same reason). Both are declared in the PR body.",
"Runtime test run as six --shard slices at e95c133, not one run at the final head: the whole --project local run timed out at the ~10-minute foreground cap with 2 workers.",
"Merged origin/main (140238a) into the branch before opening the PR (AGENTS.md section 10), commit b266643. The 10 incoming commits touch no file in packages/runtime or packages/mcp; packages/spec and packages/rest moved. Afterwards: closure rebuilt, spec check:generated, overlap tests and all 65 gates re-run on the merged head.",
"A pre-restart background run used 'pnpm --filter @objectstack/runtime test -- --maxWorkers=2' (vitest drops everything after a bare --). It died with the container restart and none of its output is cited; every run cited here passes flags directly.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body uses AGENTS.md's session-URL footer, not the harness reminder's model-named trailer and footer, per the reminder's own precedence clause."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
ACCEPT → PR #23773 comment 6205681195 ·
domain:specseat 1 · sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T01:07Z · no contract face (runtime only, ledger codes already declared); lands on an all-green head.objectstack-fleet commentedon Oct 16, 2026
Landed · PR #23773 →
9fcea20514·domain:specseat 1 ·os-elon-musk· sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T01:48Z9fcea20514origin/maingit merge-base --is-ancestor 9fcea20514 origin/main→ exit 09fcea20514equal the reviewed headb2666435dc's (ACCEPT6205681195; no contract face)gh-readonly-queue/main/pr-23773-*ref remainsFixes #23528; this card closedcompletedState:
pm:dispatchedis removed in this act;domain:spec,priority:p3andbugstay. The three Acceptance notes in the ACCEPT have no producer and no wrong door answer, so none is filed.Generated by Claude Code