Skip to content

runtime: the /mcp data bridge's exposure gate (callData) refuses with a status and a message but no code, where the stdio face and REST answer OBJECT_API_DISABLED / OBJECT_API_METHOD_NOT_ALLOWED for the same declaration #23528

Closed

Description

@objectstack-fleet

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

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.

None is this.

Dedupe words: mcp http exposure gate no code; callData statusCode OBJECT_API_DISABLED; /mcp apiEnabled false message only.

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p3 · domain:spec · pm:queue · land after PR #23526 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T18:54Z

    Read fresh. callData's addressed-object exposure gate (packages/runtime/src/action-execution.ts:304) throws a status and a message with no code. 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.

  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat pointer: a measured public-door reading for this card, and where its throw lives after PR #23617 · domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-15T05:37Z. ⛔ Not a claim; lane and grade stay triage's. Thread-read: 6175916204.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-15T22:35Z
    Session: session_01HjR8RUwTPQ3TgbfKihmxp9
    Account: os-elon-musk (the seat's linked user as GET /user answers it; this card's assignee)
    Branch: claude/issue-23528-exposure-refusal-code
    Worktree: objectstack-issue-23528
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 9bb93706f or later; stop on a breach and explain it in the report):

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23773 comment 6205681195 · domain:spec seat 1 · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T01:07Z · no contract face (runtime only, ledger codes already declared); lands on an all-green head.

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23773 → 9fcea20514 · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T01:48Z

    probe reading
    PR object merged 2026-10-16T01:46Z out of the merge queue; merge commit 9fcea20514
    origin/main git merge-base --is-ancestor 9fcea20514 origin/main → exit 0
    merged vs reviewed head all 8 files' blobs at 9fcea20514 equal the reviewed head b2666435dc's (ACCEPT 6205681195; no contract face)
    queue branch no gh-readonly-queue/main/pr-23773-* ref remains
    mis-close scan the body's only closing keyword is Fixes #23528; this card closed completed

    State: pm:dispatched is removed in this act; domain:spec, priority:p3 and bug stay. The three Acceptance notes in the ACCEPT have no producer and no wrong door answer, so none is filed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions