Skip to content

feat(operations): derive caller identity at the doors - #4603

Merged
lsm merged 3 commits into
devfrom
claude/operation-caller-contract
Sep 15, 2026
Merged

lsm merged 3 commits into
devfrom
claude/operation-caller-contract

Conversation

@lsm

@lsm lsm commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Phase 2 of the subsystem restructure: the caller contract that every capability port builds on.

What changes

  • OperationCaller gains spaceId, role, agentId, agentName (all readonly). OperationCallerRole is defined in the door; SpaceMcpSessionRole becomes an alias of it, so the scope conforms to the door's vocabulary rather than the door importing from space/.
  • One writer, lib/operations/caller.ts: resolveCallerIdentity (agent door — always carries the agent session, merges resolved scope).
  • space/runtime/space-caller-scope.ts: createSpaceCallerScopeResolver derives role, space, and agent identity from the session policy. Injected at boot and installed on SessionManager; the agent door delegates to it late-bound.
  • The RPC door's resolver stays () => ({}): plain web calls remain bare { source: 'rpc' } callers, so message.send origin and task.create provenance are unchanged. The wire sessionId is a client-declared routing label, so human-door identity requires an authenticated connection-bound principal and is out of scope here; the scope resolver serves the MCP door only. Pinned: a wire request carrying a claimed agent session in its envelope, input, and caller still invokes as { source: 'rpc' }.
  • The three inline spaceId derivations (task-metadata, owned-pending-completion, and the eight policy consumers via resolveMetadataSessionSpace) collapse onto resolveSessionSpaceId.

Invariant

Identity is derived at the door and never parsed from the payload. Pinned structurally at the invoke pipeline: an operation whose schema passes everything through receives the exact caller object the door supplied, untouched, regardless of identity keys in the input.

Not in this slice

Workflow position (taskId, workflowRunId, workflowNodeId) and agent name aliases are lookups from identity, not identity; node ports add them to the resolver if repeated lookups prove costly.

OperationCaller gains spaceId, role, agentId and agentName. Both doors resolve identity through one writer: the MCP door always carries its agent session and merges the scope resolved for it; the RPC door asserts a transport session only when it resolves to a known scope, so plain web calls stay bare rpc callers and message.send/task.create keep their existing provenance.

The scope resolver is injected at boot from the space policy and installed on SessionManager, replacing the empty resolver the RPC door had. The three inline spaceId derivations in task-metadata, owned-pending-completion and the policy consumers collapse onto resolveSessionSpaceId. Input can never reach the caller slot — pinned at the invoke pipeline.
@hyperneo-ai-test

hyperneo-ai-test Bot commented Sep 15, 2026

Copy link
Copy Markdown

Review status

Started 2026-09-15T18:04:55-04:00
Finished 2026-09-15T18:05:40-04:00
Head 7552258
Engine claude (glm-5.3-flash)
Verdict error

@hyperneo-ai-test hyperneo-ai-test Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved.

Engine claude (glm-5.3-flash) · Reviewed commit 236a385 · Trigger: open

@hyperneo-ai-test hyperneo-ai-test Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requires changes.

Engine claude (glm-5.3-flash) · Reviewed commit e428152 · Trigger: push

⚠️ Pre-existing — not introduced by this PR
Anchored outside the PR's changes — usually pre-existing; blocking severities here still gate, and defect provenance is the reviewer's judgment, not a verified claim.

  • packages/daemon/src/routes/setup-websocket.ts:109 — P2 — Pre-existing: wire sessionId is client-controlled, now identity-bearing

Comment thread packages/daemon/src/lib/rpc-handlers/operation-handlers.ts
…ion id

CallContext.sessionId is the per-message routing label the websocket client sends; setup-websocket only checks the session exists. Deriving caller scope from it let any client assert any session's spaceId, role and agent identity, including long_term_agent via the deterministic space:agent:<spaceId>:<agentId> id, which would have stamped message.send origin and task.create provenance with the claimed session.

The RPC door goes back to a bare { source: 'rpc' } caller. Human-door identity needs an authenticated connection-bound principal and is out of scope here. The scope resolver stays, serving the MCP door only, where the session id is the daemon's own.
@lsm
lsm merged commit 58aa102 into dev Sep 15, 2026
27 checks passed
@lsm
lsm deleted the claude/operation-caller-contract branch September 15, 2026 22:53
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