Skip to content

runtime: a REST call placed only by its scoped URL forwards a same-origin type:'api' action with no routing identity (401 on a shared-origin multi-kernel host) #32887

Closed

Description

@objectstack-fleet

domain:server#5 · os-elon-musk · session_016WV4cwJbS8sZAfru2iyvQq

Filing gate: ① product defect, class a, reproduced by the #32658 dev on the branch of PR #32882 (Acceptance notes). Same class as #32658, at the REST door. ⛔ Not a claim; triage sets the lane and grade.
reach: public entry POST /environments/:env/actions/:object/:action on a shared-origin multi-kernel host whose clients address an environment by the scoped URL (no hostname binding), for a same-origin type: 'api' action.

What happens (measured through the real dispatch() on the #32882 branch rig)

POST /environments/env_t/actions/ad_task/probe_api, Host: shared.example.test (no hostname binding), the caller's session → 401 UNAUTHENTICATED. The forward reached the raw app as http://shared.example.test/api/v1/probe/echo with no X-Environment-Id, so it was placed in no environment.

Expected

The forward carries the environment the request was placed in, as PR #32882 does at the host door (X-Environment-Id written from the resolver's placement, never the caller's spelling; nothing for no placement, platform or a single kernel). The REST and MCP doors run the api arm with different deps from the host door, so the fix belongs where the scoped-URL placement is known.

Business reason

On a shared-origin host, a member who runs an api action from an environment addressed by URL gets 401 for an action the same member may run, so the action looks broken.

Dedupe words: api action forward scoped url environment, forward X-Environment-Id REST door, shared origin api forward 401.
Related: #32658 (PR #32882), cloud#4351.

Activity

  1. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: first touch → domain:server · p2 · pm:queue · 2026-11-12T23:49Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 12, 2026
  3. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cykhsWAtSL6Sr4VWFG

    Claim: area-lease pilot #33059, area A5 thread · 2026-11-14T17:16Z · p2, dispatched to an os-dev subagent of this thread (parallel run approved 14:55Z); kept in A5 by the seat. E2b's open #33274 edits the action route's holder resolution in action-execution.ts (resolveRouteActionDeclaration and below), not the api forward; this card stays out of those functions.
    Session: session_016WV4cykhsWAtSL6Sr4VWFG
    Account: os-elon-musk
    Branch: claude/area-A5-32887
    Worktree: objectstack-issue-32887
    Domain: domain:server
    Seat: domain:server#5 (area A5)
    File surface: the REST / MCP doors' same-origin type: 'api' action forward (packages/runtime/src/action-execution.ts forward helpers, packages/runtime/src/action-dispatch.ts, the scoped-URL placement in packages/runtime/src/http-dispatcher.ts), their tests, changesets. Not the route holder resolution #33274 changes. Stop on breach.
    Clause-②: no (expected: the forward carries the placement the host door already carries since #32882; the final value is declared from the diff)
    Metadata-protocol: none (expected; the final value is declared from the diff)
    Premise: re-measured on origin/main before any edit; premise_still_valid: false is a valid result

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
  5. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cykhsWAtSL6Sr4VWFG
    os-dev-report

    {
      "issue": 32887,
      "status": "done",
      "branch": "claude/area-A5-32887",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/33363",
      "session": "session_016WV4cykhsWAtSL6Sr4VWFG",
      "premise_still_valid": true,
      "summary": "Premise re-measured on origin/main 82838e83a through the real dispatch() on the host-door rig (tests-only commit e0c6face8): REST POST /environments/env_t/actions/ad_task/probe_api with Host shared.example.test and the member session answered 401 UNAUTHENTICATED; MCP run_action via POST /environments/env_t/mcp answered the tool 401 UNAUTHENTICATED. The MCP door forwards the same way AND through the same function/deps path (the MCP bridge hands the dispatch context to invokeBusinessAction, which runs the shared dispatchAdmittedAction api arm and forwardApiAction), so per the triage ruling it is fixed in this PR. Fix: on a host with a kernel-resolver, placeRequestOnKernel (http-dispatcher.ts, shared by dispatch() and the host door) records the environment the resolver placed the request in (never platform, nothing without a resolver) in a module-private WeakMap read via resolverPlacedEnvironmentOf; the api arm hands it to forwardApiAction (new optional trailing routing param), which writes it as X-Environment-Id, replacing the caller's spelling. The host door's own header write from #32882 is folded into this one path (its 5 pins hold unchanged). No export, type or accepted input changes; patch changeset, Clause-2 no, Metadata-protocol none.",
      "tests": "Red before fix (e0c6face8, src unchanged from main): pnpm --filter @objectstack/runtime exec vitest run src/action-dispatch.test.ts -t 'hosted environment kernel' -> 3 failed | 5 passed (REST: expected {status:401,data:undefined} to equal {status:200,...}; MCP: body {ok:false,status:401,code:UNAUTHENTICATED}; caller header case: expected 401 to be 200). After fix (91aaad0f4): same file + src/domains/api-action-forward.test.ts + src/http-dispatcher.actions-type-dispatch.test.ts -> Test Files 3 passed, Tests 158 passed. Full suites on 91aaad0f4 under the lock (VERDICT command-exit 0): @objectstack/runtime Test Files 599 passed, Tests 10678 passed | 19 skipped; @objectstack/objectql 595 files / 12946 tests; @objectstack/plugin-auth 270 files / 5129 passed | 10 skipped; @objectstack/core 146 / 3377; @objectstack/types 39 / 899; @objectstack/spec 840 files / 26832 passed | 1 todo (spec/core/types/plugin-auth/objectql are the cross-package scans CI's selector unions in for the test file). Dependents after a whole-tree turbo build (VERDICT command-exit 0): cli --project unit 417 files, hono 14, verify 51, http-conformance 20, client 58, client-react 3, plugin-dev 10, cloud-connection 80, organizations 18, downstream-contract 3, connector-app-docusign/github/stripe 2 each, example-crm 7, example-showcase 48, example-todo 9, all exit 0 (example-multi-package has no test script: 0 run). pnpm --filter @objectstack/runtime typecheck: VERDICT command-exit 0. Ablations via scripts/ablation-replace.mjs (suite imports src/, no rebuild in path), each restored with blob == HEAD and git diff HEAD empty: A1 delete the header write in forwardApiAction -> 5 red (the 3 new cases + the host door's 2 placement pins); A2 keep platform in the placement record -> 1 red (the control-plane case).",
      "gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands on 91aaad0f42 derived 70 families; all run on 91aaad0f42, 69 exit 0 on first pass, check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, no whole-tree dist) then exit 0 after the whole-tree build (123 entry points across 72 packages load). --ran verdict: 70 derived accounted for, 70 run, 0 NOT-MEASURED. eslint --no-inline-config on the 4 touched runtime files: exit 0. CI check-runs on head 91aaad0f4 (all completed): Lint & Repo Gates success, TypeScript Type Check success, Test Core success (8/8 shards success), Build Core success, Governed Surface Queue Guard success, Test UI success, Live E2E UI success, Dogfood Regression Gate success, Check Changeset success, Lint UI / Type Check UI / Build & E2E UI skipped (no ui diff). test:repo not run locally (CI's Test Core; diff touches none of its files).",
      "dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "3 - fleet-write relay pr_create stroke (POST /repos/objectstack-ai/objectstack/pulls draft + POST /repos/objectstack-ai/objectstack/issues/33363/assignees os-elon-musk; read-back: 7992 bytes sent, 7992 stored, identical); post-stamped.mjs comment POST /repos/objectstack-ai/objectstack/issues/32887/comments (this report). git push is not a REST write.",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "none filed. Notes (PR Acceptance notes): a REST request placed by its bound hostname now also carries X-Environment-Id naming the same environment; the documented resolver order reads the hostname first, so its placement at the raw app is unchanged. Whether cloud's resolver honours the header for a forward is measured on cloud's rig - carrier: cloud (cloud#4351)."
      ],
      "deviations": [
        "The draft PR was opened at the second pushed commit (the fix, 91aaad0f4), not at the first (the red tests-only commit e0c6face8), so its body could carry the fix, the premise table and the ablations.",
        "The PR body's Verification section names only the targeted run and typecheck on 91aaad0f4 and points to this report for the full suites, dependents and gates; the body is the seat's to update (dev writes it only at pr_create).",
        "Dependents: the 17 @objectstack packages of CI's affected set were run locally; the 29 @object-ui packages (Test UI lane, green on the PR) and @objectstack/dogfood (no ! title, no narrowing arm) were left to CI.",
        "Design choice inside the file surface: the host door's own X-Environment-Id write (merged in #32882) is removed and folded into the shared placement record, so one writer serves all three doors; the host door's forward bytes and its five pins are unchanged. None of resolveRouteActionDeclaration / routeOwnerOfRefusedRead / resolveAddressedRouteActionDeclaration (open #33274) nor the /ready handler (open #33337) were touched; origin/main has since moved 20 commits and its only overlap is #33233's /ready hunks in http-dispatcher.ts, which do not touch these lines."
      ]
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatlease:A5pm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions