Uh oh!
There was an error while loading. Please reload this page.
docs(adr): ADR-0100 — serve-side MCP default wiring; register the MCP tool-execution surface in the authz matrix (#3167) - #3182
Conversation
… tool-execution surface in the authz matrix (#3167) #3167 is a decision issue whose #1 (identity admission) is the hard gate: "nothing else proceeds before it." Two findings shaped the response: 1. The issue's premise — "MCPServerPlugin is wired nowhere by default" — is stale against main. MCP is already a default-on core capability (isMcpServerEnabled() default true + serve.ts capability push + the matching route gate), and the HTTP tool surface is already fail-closed on identity (handleMcp 401 for a principal-less caller; buildMcpBridge threads the caller ExecutionContext into callData on every verb; OAuth callers run at the ADR-0090 D10 ceiling∩user intersection). 2. What was genuinely missing is exactly the gate: the MCP tool-execution surface had NO row in the ADR-0096 D4 authz conformance matrix, so "whose ExecutionContext runs a tools/call" lived only in ADR-0096 prose, not as a checked, red-CI-on-regression property. This lands: - ADR-0100 recording the four decisions grounded in the verified state: D1 identity admission (caller-principal posture, registered as the gate), D2 dev/prod boundary is identity + off-switch not a default flip, D3 HTTP-only default with stdio kept explicit-opt-in, D4 bounded default exposure (apiEnabled + ai.exposed). - Two authz-conformance rows: an enforced `mcp-tool-exec-identity` (fail-closed gate + bridge threading) and an experimental `mcp-stdio-resource-read` classifying the ADR-0096 E1 context-less stdio resource read (opt-in, never networked) with its admission requirement — registered honestly, not patched with a discouraged { isSystem: true } literal. - Three source probes pinning both halves of the HTTP admission and the stdio fall-open, so dropping the gate/threading or networking the stdio read trips CI (STALE/UNCLASSIFIED) instead of being an adversarial-review finding. Test-only + docs (packages/qa/dogfood is private) — no runtime behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155A3k1CMJQb44V4iwTLUcH
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…3167) Upgrades the `mcp-tool-exec-identity` conformance row from cited-tests-only to a real end-to-end boundary proof (ADR-0096 D4 requires one for an agent/ external-reachable surface). `showcase-mcp-identity.dogfood.test.ts` boots the showcase with MCPServerPlugin registered (via the harness `extraPlugins` — the verify harness boots the plugin set directly, not through the CLI capability push) and asserts on the real in-process HTTP stack: - anonymous POST /mcp (tools/list) → 401 (fail-closed: not 404/501, so the surface IS served and the service IS registered — the denial is identity); - an authenticated member clears the gate (not 401); - GET /mcp/skill → 200 (surface served default-on while tool-exec is gated). Wires it in as the row's `proof:` and adds the row to the test's HIGH_RISK set (so the surface must carry a proof). Adds @objectstack/mcp to the private @objectstack/dogfood dev deps (lockfile updated). Validated locally: `pnpm --filter @objectstack/dogfood test` authz-conformance + showcase-mcp-identity → 10/10 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155A3k1CMJQb44V4iwTLUcH
os-zhuang
commented
Jul 18, 2026
Superseded by #3202, which already registered the MCP execution-surface identity admission in the ADR-0096 matrix ( Generated by Claude Code |
…-ai#4576) * feat(agents): pm-dispatch escalation bar — obvious fixes ship, the maintainer gets a veto window, not a permission gate Maintainer feedback after objectstack-ai#3182 sat waiting for confirmation: obvious problems should just be fixed. Escalation now requires genuine product/ contract divergence with no repo-norm answer, or destructive/irreversible action. Named non-escalation classes act immediately: restore-invariant fixes (dual-version dependency graphs, inert tripwires, unwired gates), sequencing, verification strategy, and dev needs_decision reports that fall into those classes (the PM answers the dev directly). Also records the GitHub body-sanitizer generics trap in the os-dev definition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 * feat(agents): resource discipline for parallel dev agents in one container Maintainer-reported OOM under parallel dispatch. Memory peaks are the build/test phases, so heavy phases serialize on a container-wide flock (/tmp/os-heavy-verify.lock) while editing stays parallel; plus heap caps (NODE_OPTIONS max-old-space-size), scoped --filter builds/tests, capped vitest/turbo workers, and worktree cleanup after the PR is up. PM-side: batch:2 or mode:cloud for build-heavy tasks; OOM-killed agents redispatch alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 * feat(agents): pm-dispatch scaling — repo sharding for multi-PM, multi-account claims, batch:5 operating point - Multiple PM sessions shard by whole repo, never share one queue (batch independence is only checked within one PM's view; the merge queue is one lane regardless). Scaling order: bigger batch + mode:cloud first, repo shard second, same-queue multi-PM prohibited. - Multiple GitHub accounts (colleagues' Claude Code) simplify claiming: assignee alone disambiguates across accounts; the claim-comment ritual is for sessions within one account. Partition by repo/label with a recorded assignment table. - batch:5 recorded as the maintainer's operating point, riding on the container resource discipline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 --------- Co-authored-by: Claude <noreply@anthropic.com>
* feat(agents): pm-dispatch backlog sweep duty + stale-premise triage checks + PID-only process discipline - Step 0: classifying the unlabeled backlog is a standing PM duty — auto-queue concrete/no-risk work, flag design cards and programs for the maintainer, request repair for sanitizer-truncated bodies. - Fetch step now requires reading issue comments and running a stale-premise check against recent main history before dispatch (three same-day cases: objectstack-ai#4525, objectstack-ai#4379, objectstack-ai#4075 step 1). - os-dev resource discipline: never kill by process name; PID-scoped operations only (from the objectstack-ai#3182 agent's pkill disclosure). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 * feat(agents): pm-dispatch cross-shard transfer protocol — work crosses shards via the target queue, PMs never do A sharded PM whose task needs a change in another shard's repo files it into the target repo's queue (pm:queue + source line) instead of dispatching across the line; dependencies via Blocked-by, follow-up chores belong to the consuming shard, and anything touching packages/spec transfers to the main-backlog PM. The queue label is the inter-PM channel — no direct PM-to-PM coordination needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 * feat(agents): pm-dispatch shard ownership registry — caretaker mode is explicit, never assumed A registry issue in the main backlog records which session owns which shard; taking over or handing off a shard is a comment there. Unowned shards may be caretaken by the main-backlog PM, but a registered shard ends caretaking immediately — in-flight claimed tasks finish under their claimant, everything else belongs to the new owner. Claim comments state the mode so registry and claims never disagree silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Decision-issue response to #3167 (default wiring for the serve-side MCP server — identity admission first). #3167 makes decision #1 (identity admission) a hard gate: "nothing else in this issue proceeds before it." This PR lands the decision record and that gate, with an end-to-end proof.
Two findings that shaped the response
The issue's premise is stale against
main.decision: default wiring for the serve-side MCP server (@objectstack/mcp) in dev — identity admission first #3167 saysMCPServerPluginis "wired nowhere by default." Verified againstmain, MCP is already a default-on core capability:isMcpServerEnabled()defaults ON (packages/types/src/env.ts),packages/cli/src/commands/serve.tspushes themcpcapability intoos serve/os devunless opted out, and the dispatcher route gate reads the same helper. The HTTP tool surface is also already fail-closed on identity —handleMcpreturns 401 for a principal-less caller (no dev-admin fallback, no fall-open), andbuildMcpBridgethreads the callerExecutionContextintocallData(..., ec)on every verb (OAuth callers run at the ADR-0090 D10 ceiling∩user intersection).What was genuinely missing is exactly the gate. The MCP tool-execution surface had no row in the ADR-0096 D4 authz conformance matrix — so "whose
ExecutionContextruns atools/call" lived only in ADR-0096 prose, not as a checked, red-CI-on-regression property.What this lands
docs/adr/0100-serve-side-mcp-default-wiring-identity-admission.md— the four decisions grounded in the verified state:user) posture, fail-closed, registered as the gate.OS_MCP_SERVER_ENABLED=falsegates registration and route), not a code-level default flip (an opt-in-for-prod amendment is documented for ratification).os devowns the process; the stdio resource path carries the ADR-0096 E1 context-less read).apiEnabledobjects,sys_*fail-closed, actions only whenai.exposed; metadata-declared exposure is the future story.authz-conformance.matrix.ts— two rows: enforcedmcp-tool-exec-identity(fail-closed 401 gate +buildMcpBridgethreading + OAuth scope gate; now carrying a real dogfoodproofand marked high-risk) and experimentalmcp-stdio-resource-readclassifying the ADR-0096 E1 context-less stdio resource read (opt-in, never networked) with its admission requirement — registered honestly, not patched with a discouraged{ isSystem: true }literal (ADR-0096 D2 retires those; thesystemContext(reason)constructor doesn't exist yet).authz-conformance.test.ts— three source probes pinning both halves of the HTTP admission and the stdio fall-open, so dropping the gate/threading or networking the stdio read trips CI (STALE / UNCLASSIFIED) instead of surfacing in an adversarial review later.showcase-mcp-identity.dogfood.test.ts— the end-to-end proof: boots the showcase withMCPServerPluginregistered and asserts on the real HTTP stack that anonymousPOST /mcp→401 (not 404/501 — the surface is served and the service is registered; the denial is identity), an authenticated member clears the gate, andGET /mcp/skill→200 (served default-on while tool-exec is gated).Scope / safety
Docs + a private test package (
@objectstack/dogfood) only — no runtime behavior change, no changeset. The identity path, OAuth admission (#2698), andai.exposed+ capability action gate are pre-existing and unchanged; this PR registers and proves them. (@objectstack/mcpadded to the dogfood dev deps; lockfile updated.)Validation
Validated locally:
pnpm --filter @objectstack/dogfood testforauthz-conformance+showcase-mcp-identity→ 10/10 pass (7 conformance ratchet + 3 dogfood-boot). The three new probes each match their source anchor exactly once and are a clean bijection with the two rows'covers; theproof:-exists and high-risk assertions pass.Open for ratification
NODE_ENV-aware opt-in-for-prod amendment.Closes#3167 pending ratification of D2.
🤖 Generated with Claude Code