Skip to content

e2e: which process a call runs on, counted rather than inferred - #268

Merged
dinstein merged 1 commit into
mainfrom
derive-e2e
Aug 8, 2026
Merged

e2e: which process a call runs on, counted rather than inferred#268
dinstein merged 1 commit into
mainfrom
derive-e2e

Conversation

@dinstein

@dinstein dinstein commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Derived instances decide which process an allowed call runs on. They are a
connection-plane feature and nothing else: a derived instance shares its base
server's id, so exposed names, scope keys and audit records are identical, and
deriving never adds, hides or renames a tool.

That is exactly why it had no end-to-end coverage — there is nothing to see.
Every observation a client can make is designed to be the same whether or not
deriving happened, so a test written from the outside has no signal unless it
counts processes.

The arithmetic is what makes each case falsifiable: the base is dialled at
startup, a derived instance on first use. One spawn after a call means the
call ran on the base; two means it ran somewhere of its own.

The client can now report roots, which is the input derive: root keys on —
and the rootless/rooted pair differs by nothing else, so it is the pair rather
than either case that carries the result.

  • 1. Shared, session-derived, the rootless fallback, the rooted instance, and a denied call that dials nothing

Derived instances had no end-to-end coverage for the reason they are hard to
test: everything a client can observe is designed to be identical whether or
not deriving happened. Exposed names, scope keys and audit records all come
from the base server's id, so a test from the outside has no signal unless it
counts processes. These cases count them, and the arithmetic is stated where
it is asserted — the base is dialled at startup and a derived instance on
first use, so one spawn after a call means the base served it and two mean it
did not.

The client can now report roots. That is the input derive: root keys on, and
the two cases around it are a pair by design: same entry, same command, same
call, and the only difference is whether the client answered roots/list with
anything. The rootless one covers the documented fallback — an empty key means
the base instance, which is safe because the base spec is what the operator
configured — and it needs no new capability, because the default e2e client is
already that client.

The session case additionally asserts what must NOT change: the exposed name,
and that a second call reuses the instance rather than dialling again, since a
pool that did not hold it would cost a process per call.

The last case is the two features meeting. execTool dials inside the call
closure, after both gates, because "a call the scope gate is about to deny
must not cause either" — a denied call that still dialled would leave a
refusal with a side effect, and a process an attacker can spend by making
calls they are not allowed to make.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dinstein
dinstein merged commit 5de3d71 into main Aug 8, 2026
3 checks passed
@dinstein
dinstein deleted the derive-e2e branch August 8, 2026 13:44
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