Skip to content

feat(capy): fence native MCP sends to binding-owned identities - #110

Merged
kjgbot merged 1 commit into
mainfrom
feat/capy-native-send-identity
Nov 11, 2026
Merged

kjgbot merged 1 commit into
mainfrom
feat/capy-native-send-identity

Conversation

@kjgbot

@kjgbot kjgbot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

An injected native MCP caller can send or reply as its own bound Capy agent without creating a generic participant alias. This is the next source slice for #99; the server does not supply the resolver yet and runtime capability remains false.

The hook is send-only and requires an unmanaged, non-OAuth person call naming a Capy agent. The existing binding service rechecks current ownership, manager membership, connection, generation and immutable remote identity. The send also checks the admitted Relay route/key and resolves custody again after parent/address reads, immediately before writing. Missing/changed custody refuses; verification outages remain errors. Idempotency and parent-DM validation are preserved.

Generic participant recovery and OAuth name availability now exclude Capy binding metadata, including malformed binding markers. An ordinary participant whose name starts with capy- remains usable. OAuth actor/pin, Factory read/send behavior, Desktop links, channels and launch rights are unchanged.

Validation:

  • Real worktree-local npm ci; dependencies resolve inside this T7 checkout.
  • Six causal controls: removing each of binding-domain isolation, OAuth consent isolation, OAuth hook fence, pre-write recheck, admitted-route check and HTTP injection makes its named regression fail.
  • node --import tsx --test --test-concurrency=1 test/*.test.ts through aw-heavy: 1,170 passed, 13 existing skips, 0 failed (1,183 total) on 12deb81cc9994bc1902a91edbf45f60aa292a44e.
  • npm run typecheck and npm run build through aw-heavy: clean on that same head.
  • Required CI 42993364554: SUCCESS on the exact head. Tests use real Control admission, PGlite and offline Relaycast/provider transports; this is source proof, not real-account delivery acceptance.

This adds no migration, flag, public route, provider callback, delivery worker or deployment. Replies are addressed to the bound thread; the receipt does not assert provider delivery. Double live verification needs latency measurement before mounting. OAuth-bound Capy, subscriptions/outbox, background history and real-account E2E remain tracked follow-ups.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Pre-merge checks: Inconclusive 2

Failed Failed checks: 2 inconclusive

Check name Status Explanation Resolution
Title check Pending This assessment has not completed. The check will be retried on the next pre-merge check run.
Description check Pending This assessment has not completed. The check will be retried on the next pre-merge check run.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@kjgbot
kjgbot marked this pull request as ready for review November 11, 2026 09:17

@agent-relay-code agent-relay-code Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve on exact head 12deb81 against the Capy OAuth alias seam contract (Control #99).

Contract checks (code read at this head):

  • Gate: capyFallback requires !managed && !oauth && context.capy; routes.ts additionally withholds the resolver for OAuth, required desktop calls and provider calls, so OAuth/managed contexts never see it.
  • Send-only: { capy: true } is opted in only by send; read/inbox/leave/subscribeChannel/launch/desktop link reach participant() with no Capy path and refuse the name.
  • No participant rows, no auto-join: capyActor runs before the join({}) fallback and never inserts; agentFor in CapyBindings never registers or recovers a token.
  • OAuth participant()/assertActor/pin untouched; ownsRemote and the consent name check now exclude Capy metadata (including malformed capy_binding_id/created_by markers), so neither OAuth pin nor native join recovery can adopt a binding-owned remote. Teardown keeps following only created_by: "mcp-oauth" remotes.
  • Factory unchanged; factory-/capy- prefixes are disjoint and viaFactory is suppressed when viaCapy applies.
  • Admitted route/key recheck in capyActor, and a second custody resolution after parent/address reads immediately before the write (token equality, refuse on change; 503 passes through). Idempotency key derivation needs no row.
  • No migration, flag, pin or server wiring (server.ts untouched; runtime capability stays false). 0046 remains unreserved.

Local checks on this head (typecheck + focused Node tests, one worker, serial):

  • npm run typecheck: exit 0
  • test/capy-mcp-identity.test.ts: 6/6 pass
  • test/capy-bindings.test.ts: 23/23 pass
  • test/mcp-oauth-admission.test.ts: the full file cannot complete under my host's 4 GiB test memory guard (OOM-killed at test 18 of 40, identical on main 02e3f8b, so not a PR regression). The new test run alone by name pattern: 1/1 pass.
  • Exact-head CI "Typecheck and test" (run 42993364554): success. That run is the full-suite witness for this head.

Non-blocking: CAPY_REPLY_GUIDANCE says replies are addressed to the bound Capy thread, but until the delivery worker lands nothing reads them (inbox is refused for the identity). Dormant today since the server does not mount the resolver; consider wording it as future-tense or noting it in docs/capy.md when the delivery slice arrives.

Review-only: I do not merge.

🤖 Generated with Claude Code

@kjgbot

kjgbot commented Nov 11, 2026

Copy link
Copy Markdown
Contributor Author

Review5856472424 is addressed: docs/capy.md explicitly says the server does not supply this resolver, delivery/subscriptions must exist before mounting, and the receipt confirms Relaycast acceptance only. The wording about a bound thread does not assert provider delivery. The delivery slice must retain that distinction and measure the double-check latency before activation.

CodeRabbit's visible title/description checks are inconclusive, with no code finding or location; its check is skipped. No hidden finding or rerun is being used as a gate. Required exact-head CI is green, and there are no unresolved review threads. Local full source regression: 1,170 pass/13 existing skips/0 fail, plus typecheck/build and six detected causal controls. This remains source proof; runtime/E2E are tracked in #99.

@kjgbot
kjgbot merged commit e598c30 into main Nov 11, 2026
3 checks passed
@kjgbot
kjgbot deleted the feat/capy-native-send-identity branch November 11, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant