itx — the iterate context
Status: historical design record (pre-migration). This document describes the PRE-itx-v4 itx kernel, which was deleted in the itx-v4 replacement (PR #1585) — most files it names (
itx.ts,handle.ts,dial.ts,fetch.ts,platform-context.ts, …) no longer exist. It is kept because it explains the design lineage the current implementation inherits (describe(), instructions/types, capabilities as stream events). The current implementation lives atapps/os/src/(README.md+types.ts), and the browser hooks moved to theiteratepackage (packages/iterate/src/itx/—iterate/client+iterate/sdk/itx/react; guide:docs/frontend-development.md). What actually remains in THIS folder isbrowser-repl.ts(REPL compiler),path-proxy.ts,examples.ts(the example catalogue), ande2e/(the cross-runtime example matrix).
You are handed an itx. In the REPL, inside an agent, in a project worker,
from withItx() on your laptop — it is always the same object: a handle on a
context, a node that holds named capabilities. Three scenes cover almost
everything you will ever do with one. The mechanics — streams, fold, dial,
chain, addresses — are an appendix, because you don't need them to act.
Scene 1: use a capability#
// What am I holding? What can I call?
await itx.__describe();
// → { context: "prj_…:/", access: ["prj_…"],
// capabilities: [{ name: "slack", kind: "rpc", instructions: "Use
// itx.slack.<Slack Web API method path>(args), e.g.
// itx.slack.chat.postMessage({ channel, thread_ts, text })…" }, …],
// project: { … } }
await itx.slack.chat.postMessage({ channel: "C123", text: "hi" });itx.slack works because someone provided "slack" on this context (or on a
parent — lookup climbs the chain), not because itx knows about Slack. Unknown
property names fall through to the context's capability table; dots accumulate
locally and the terminal call dispatches once. describe() is the runtime
truth: every capability carries the instructions (and optionally types)
its provider attached, so the answer to "what can this context do?" is always
one call away — for you, and for any agent handed the same itx.
Scene 2: offer a capability from your laptop#
import { withItx } from "~/itx/itx-client.ts";
using itx = withItx({ baseUrl, token, context: "my-project" });
await itx.provideCapability({
name: "runSwiftOnMyMac",
instructions: "Compile-and-run Swift on Jonas's Mac. Pass the source string.",
capability: async (src) => runSwift(src),
});That's the whole thing. While your process stays connected, every agent,
REPL, and script in the project can call itx.runSwiftOnMyMac(src) — the
call travels back over YOUR connection and runs in YOUR process. A bare
function is the simplest provider; live capabilities are session-bound (gone
when the socket drops, back when you reconnect and provide again). The
instructions you attach are what everyone else's describe() will say —
write them for the stranger who finds your capability there.
Scene 3: override one method#
const session = await itx.extend({ name: "review-run" }); // or { path: "/runs/42" }
await session.provideCapability({
path: ["workspace", "git", "commit"],
instructions: "publishing waits for human approval; everything else is the real workspace.",
capability: approvalGate, // async (input) => { await approve(input); return itx.workspace.git.commit(input); }
});extend() mints a cheap child context — itself a stream coordinate: a path
you choose, or a generated /itx/<id> catch-all (extending an existing path
is get-or-create). The shadow lives at a PATH:
session.workspace.git.commit(...) hits the gate, while
session.workspace.readFile(...) misses the child's table and falls through
the chain to the inherited workspace — prototype semantics, per method.
session.super is the handle on the unshadowed parent — the middleware
"call next()": a fetch shadow delegates to the real pipe via
itx.super.fetch(request). Revoke the shadow and the inherited entry
resurfaces. Hand session to the thing you don't fully trust; keep itx.
The review question for any of this is always the same: what does
describe() say? A context is exactly the sum of what its chain describes.
Everything below is the appendix: how the above actually works, in build
order. Design history: apps/os/docs/itx-design.md (the arc) and DECISIONS.md
(what changed on contact with reality). The agent-facing surface is
types.ts — handwritten, import-free, the design of record the
implementation conforms to.
The Laws#
These invariants ARE the architecture. Every file here serves one of them.
- The stream holds the facts. The node holds live refs. Code holds composition. A context IS a stream coordinate; its durable state is a fold of that stream; live stubs are runtime-only and rebuilt by reconnection; wiring capabilities together is always plain code, never data interpreted by the platform.
- Props carry identity, never composition or authority-by-content.
ItxProps = { context, access?, capabilityPath? }— the ref IS the coordinate; address and owning project are projections of it. - Auth happens at connect, nowhere else. Credentials become a handle
once, at the edge (
fetch.ts). No code holding an itx ever checks scopes; which context your handle points at is the authority. - Narrowing is construction.
itx.projects.get()anditx.extend()return new handles on narrower contexts — never flags on wider ones. - All policy-governed egress flows through one pipe. Loaded isolates get
it as global
fetch(globalOutbound = ProjectEgress); everyone else callsitx.fetch(). Secret placeholders are substituted in the stateless terminal pipe and never exist anywhere else. - One calling convention. The core dispatches every capability as
target.call({ path, args }). The members/path-call choice lives at the EDGES, never in stored data. - Cap'n Web terminates in the stateless worker, never in a DO. The DOs speak plain Workers RPC — Kenton's stated hibernation architecture (capnweb#36 / workerd#6087), so hibernatable RPC arrives for free.
① Dots become data: PathCall and PathProxy (path-proxy.ts)#
Everything starts with one wire shape:
type PathCall = { path: string[]; args: unknown[] }; // itx.slack.chat.postMessage(x)
// ⇢ { path: ["slack","chat","postMessage"], args: [x] }The consumer-side half is a callable JS Proxy
(createInvokeCapabilityPathProxy, domains/itx/utils.ts): property access
accumulates a path locally (zero round trips, then reads undefined so
await never misfires mid-chain) and the terminal call fires once with
the accumulated PathCall. RESERVED_PATH_SEGMENTS is the one list of
names that may never traverse a dynamic surface (prototype-pollution vectors,
stub controls like dup/then/constructor).
Note for the curious: there are TWO composing pipelining systems in play.
This one flattens dotted NAMES into data before anything touches the network;
the RPC transports (capnweb, Workers RPC) separately pipeline CALLS onto
returned stubs. They compose — itx.streams.get("/x").append(e) is one
PathCall producing a stub the transport then pipelines append onto.
② Data becomes dots again: replayPathCall#
replayPathCall(target, { path, args }) is the receiver-preserving
replay: walk the segments on a concrete object (re-filtering reserved names —
this is the authoritative gate, since invoke is public and paths can be
hand-built) and call the terminal method ON ITS PARENT. An empty path calls
target itself as a function.
There is deliberately NO callsite wrapper to pair with it: a plain object
(or bare function) IS a live capability — the core's dispatch notices a
target that doesn't implement call and replays the path onto its members
itself (③). The replay still runs where the object lives (your functions
cross the session as stubs), so providing { run(src) { … } } means run
executes in YOUR process.
That is Law 6 in full: the core only ever says target.call({ path, args });
whoever holds the concrete object decides what a path means there — and "I'm
just an object of methods" is the default, not a wrapper.
③ The core: Itx — one map, longest prefix, a chain (itx.ts)#
A context is, behaviorally, a prototype-chain object whose properties are
capabilities. Itx is the class that implements one link of that chain.
Its state is ONE path-keyed capability table; its protocol is four verbs
(provideCapability, revokeCapability, describe,
invoke — together the ItxStub shape, "the context protocol"):
- Entries live at PATHS.
provideCapability({ name })is the 1-segment sugar;provideCapability({ path: ["slack","chat","postMessage"], … })shadows ONE subtree of an inherited capability (Scene 3).invokeresolves the longest provided prefix of the call path (resolveLongestProvidedPrefix, pure and module-level) and dispatches the REMAINDER as thePathCall. - A miss delegates the WHOLE path to
parentItx— a stub of the parent context's core. Shadowing, revoke-resurfaces-the-inherited-entry, and deploy-updated defaults are all consequences of this chain, not rules. - Delegation carries
ItxOrigin({ id, address }of the context the call STARTED at), set by delegating nodes, never by handles. It is the chain's trusted identity channel: attribution, and — crucially — dial-back (⑤). - What can be provided (
Capability): a serializableCapabilityAddress(durable), or anything live — a plain object of methods (dispatch replays the dotted path onto its members; nested objects give depth for free), a bare function (calling the capability calls the function), or a stub implementingcallitself (own your whole method tree). Discrimination is structural (isCapabilityAddress): an address is a PLAIN object carryingtype: "rpc"; everything else is live. Live stubs sit in an in-memory instance table, dup-retained, torn down with the provider's session. - Every entry is self-describing.
instructions(prose for whoever callsdescribe()) andtypes(a TypeScript declaration for machines and editors) ride with the provide, are recorded with it, and come back fromdescribe()on every handle down the chain. - Provide is structural validation only (
assertValidCapabilityPath, address well-formedness). Reachability is the dial's authority (④) and surfaces at first call.
provideCapability returns a provision handle { revoke(), [Symbol.dispose] }; dispose auto-revokes ONLY live provides (a durable
provide must outlive the session that created it — session teardown disposes
every returned handle, so a revoking disposer would silently undo it).
④ Targets as data: CapabilityAddress and the dial (dial.ts)#
The system has ONE serializable data structure — the address — and everything is a use of it. "How do I obtain an RPC surface?" has exactly these answers:
type CapabilityAddress = { type: "rpc"; worker: WorkerRef; entrypoint?; props? };
type WorkerRef =
| { type: "binding"; binding } // anything on env: env.AI, a service binding
| { type: "loopback" } // this worker's own exports (first-party code)
| { type: "durable-object"; binding; name } // dialed as itx:<projectId>:<name>
| { type: "source"; source }; // a dynamic worker from stored sourcemakeDial(host) is THE one effect injected into the core: address →
something speaking call({ path, args }). It owns REACH — the
DialableTargets allowlists (config can only widen) — and injects the
attribution props { capabilityPath, context, projectId } that dialable
loopback entrypoints scope by (a provider can never point one at someone
else's project). Per worker-ref kind: bindings and loader entrypoints get
wrapped in-process with the ② replay; loopbacks self-replay via their own
call; source refs materialize an isolate via the Worker Loader — wired
by wireIsolateEnv (isolate.ts) with env.ITERATE (an itx scoped to the
ORIGIN context) and globalOutbound = ProjectEgress (Law 5) — and
exportType: "durable-object" sources become facets of the hosting DO
with their own private SQLite.
Because the isolate is wired to the ORIGIN, an inherited source capability's
bare fetch() climbs the INVOKING context's chain — origin dial-back: shadow
fetch on your extension and an inherited capability's outbound traffic
lands in your shadow, while siblings and the parent are untouched.
Context nodes are addressed the same way: the REF is the CapabilityHostDurableObject's
name, so contextAddress(ref) (coordinates.ts) is a pure projection;
dialContext is the kernel restore, deliberately not allowlist-gated
because only kernel code writes context refs.
⑤ The context's stream: events are the only writes (contract.ts, coordinates.ts)#
Itx extends StreamProcessor (packages/streams). A context IS a stream
coordinate — { projectId, path }, written as the REF <projectId>:<path>
— and that stream is the ONLY authority:
- Identity is the coordinate. The project context is the project's root
stream (
prj_x:/); deployment-wide contexts use__null__:/...; an agent's context is the agent's own stream (prj_x:/agents/…); anonymous extends default to/itx/<generated>— a plain convention, not a reserved segment (any stream path can be a context). There are NO context ids and no directory: ref, stream, and node address are one string. - Creation is two idempotent appends by the CREATOR
(
createContext,coordinates.ts): ansubscription-configuredevent pointing the stream at the node's hosteditxprocessor, thencontext-created { name, parent: { ref, address } }— the birth certificate. The generic host derives nothing; parentage comes from the event. The fold takes the first certificate and ignores any later one, so creation is get-or-create (exactly-once as a property of the fold, not of delivery — the standing doctrine,docs/domain-objects-and-stream-processors.md). provideCapabilityAPPENDSevents.iterate.com/capability-host/capability-provided(path-keyed payload: path, kind, full address, meta — which carriesinstructions/types— and owner) and then SELF-INGESTS through the one consumption door — read from the checkpoint, fold forward. Read-your-writes with no waiting machinery; duplicate delivery is inert by offset bookkeeping.capability-revokedandcapability-disconnected(session teardown) work the same way. A LIVE provide appends the EVENT — the record outlives the session — while the stub stays an instance field; replay marks the entry disconnected.reduceItxEvent(pure, module-level) is the fold; theItxContract(contract.ts) is the event vocabulary and state shape. The processor checkpoint (in the host DO's storage) is a disposable cache of the fold — delete it and replay rebuilds it. itx events interleave freely with whatever else lives on the stream (an agent's conversation, a session's record): the fold ignores foreign types.- Scripts ride the same stream. The synchronous door (
run.ts,POST /api/itx/run) recordsscript-run-requested/script-run-settledaround an inline run. Appending a requested event withenqueued: trueIS requesting work: the context's own processor runs it (Itx.processEventBatch→ the host's runner) and appends the completed event — at-least-once reruns stay detectable via the requested/completed pair, and already-completed pairs are inert.
⑥ The host: CapabilityHostDurableObject, and nothing else#
One host. CapabilityHostDurableObject (itx-durable-object.ts) hosts EVERY
context — project, agent, extension. It holds NO configuration:
its DO name IS the ref, so identity, stream, and self-address are
projections of the name; parentage folds from the birth certificate;
descriptor() derives from state. It registers its Itx as the itx
processor on a stream-processor host, so the context's stream delivers batches
to it through the subscription its creator configured; itx() returns the
core (a method, not a property — workerd does not pipeline calls through
property accesses, so node.itx().invoke(…) stays one round trip).
Creators: projects.get(slug).create appends the project context's creation events
onto the root stream (parent: the platform defaults); the agent DO appends
its own onto the agent stream (parent: the project context); extend()
appends onto the chosen child path. The Project and Agent DOs keep their
DOMAIN work — processors, creation orchestration, tool provides — but host
no capability core.
Workspaces are deliberately NOT the kernel's concern: WorkspaceCapability
takes an explicit provider-chosen workspaceId. The defaults provide the
shared project workspace; the AGENT (as creator) provides its context its
own workspace capability bound to the context's ref. Plain extensions
share the project workspace through the chain.
⑦ The handle: the one thing user code touches (handle.ts)#
ItxHandle is a cheap, ephemeral VIEW over a context node — identical in
the browser, Node, the REPL, the project worker, itx scripts, and
capabilities themselves. Anatomy: the typed trust kernel — the four verbs
plus extend (mint a child context: ④'s address + ⑤'s birth
certificate + a handle), super (a path-proxied handle on the parent
context — the "call next()" of middleware: a fetch shadow delegates to the
unshadowed pipe via itx.super.fetch(request)), streams, project,
projects, fetch, describe, capability(name) — and a
fallthrough Proxy: any unknown name becomes a path proxy (①) whose terminal
call is one invoke on the node's core (Scene 1).
One papercut, priced and accepted: super is a reserved word, so
itx.super works but const { super } = itx does not — destructure
everything else, dot the parent.
Handles are minted in exactly three ways (Law 3/4):
- connect (
fetch.ts):/api/itx[/:ref]— credentials → access → handle, Cap'n Web terminating in the stateless worker (Law 7). The restorer (resolveItx,entrypoint.ts) turns serializableItxPropsinto the live handle;ItxEntrypointis the same restorer bound asenv.ITERATEinside every platform-loaded isolate. - narrowing:
itx.projects.get(…)(the access check) anditx.extend(). - platform wiring:
wireIsolateEnvhands isolates a handle scoped to their home context, withcapabilityPathas pure attribution.
Client plumbing: client.ts (withItx for Node, Scene 2), itx-react.tsx
(the whole browser React surface), browser-repl.ts (REPL compiler), errors.ts
(ItxError codes that survive capnweb's name-dropping reconstruction, plus
existence-masking — missing and forbidden are byte-identical NOT_FOUND).
⑧ The defaults: a parent written in code (platform-context.ts)#
Every chain roots in code: everything WRITABLE is durable; the root of every chain is code.
prj_x:/itx/a1b2 → prj_x:/ → platform:project (PlatformContext, read-only code)PlatformContext is a loopback WorkerEntrypoint answering the same
context protocol as every node — describe/invoke from
PLATFORM_PROJECT_CAPABILITIES (ai, fetch, streams, repos, workspace,
worker — each with its own instructions, so even the code-rooted defaults
self-describe), provide/revoke refused — addressed
{ type: "rpc", worker: { type: "loopback" }, entrypoint: "PlatformContext" }
and dialed in-process, so default dispatch pays no DO hop. The worker
capability is the project's own code: the worker built from the project's
repo (slug project). Shipping a new default is a deploy, not a migration:
the chain sees it immediately; recorded rows shadow it; revoking a shadow
resurfaces it. There is no defaults mechanism — only the chain.
Egress, both doors — fetch is itself a shadowable default:
bare fetch() in ANY loaded isolate ─► ProjectEgress.fetch (origin's node) ─┐
├─► "fetch" via the chain
itx.fetch(...) from ANY handle ────────────────────────────────────────────┘ │
shadow provided on the origin's chain? ─► the shadow (placeholders UNsubstituted)
default ─► EgressPipe.call (stateless terminal: secret substitution + the real fetch)HTTP routing to capabilities: https://{cap}--{project}.{base}/… →
ItxCapabilityIngress (http.ts): 404 unless meta.http.expose; exposed
caps are public; then one core dispatch with [...capabilityPath, "fetch"].
Files#
| File | Role | Owns |
|---|---|---|
itx.ts |
THE CORE | Itx (the four verbs, stream write/consume seam, live table, longest-prefix dispatch, chain), capability data model, validation |
contract.ts |
the stream contract | ItxContract, ITX_EVENT_TYPES, ItxState (zod schemas — deliberately loose so old streams never wedge the fold) |
path-proxy.ts |
Law 6, client-safe | PathProxy, replayPathCall, RESERVED_PATH_SEGMENTS |
coordinates.ts |
the coordinate system | refs (<namespace>:<path>), context addresses, dialContext, createContext (subscription + birth certificate) |
dial.ts |
reach | makeDial (allowlists, loader/facet wiring, prop injection), durableObjectFacetsHook, resolveDialableTargets |
platform-context.ts |
the chain root | PlatformContext (read-only code context), PLATFORM_PROJECT_CAPABILITIES, getPlatformContext |
itx-durable-object.ts |
THE host | CapabilityHostDurableObject: name = ref, the itx processor + subscription, descriptor from state, itx() |
handle.ts |
the handle | ItxHandle + built-ins, CapabilityProvision, the bare-function probe/wrap, ItxProjects |
entrypoint.ts |
restorer + egress | resolveItx, ItxEntrypoint (env.ITERATE), ProjectEgress (globalOutbound), EgressPipe, BindingCapability |
isolate.ts |
isolate wiring | wireIsolateEnv — the one trust posture for every platform-loaded isolate |
run.ts |
the script runner | runItxScript: loader isolate + the two-event record on the context's stream |
fetch.ts |
connect + run | /api/itx[/:ref], /api/itx/run, project-host /__itx |
access.ts |
connect-time access | accessForPrincipal, resolveAccessibleContextRef |
http.ts |
routable capabilities | hostname rule, ItxCapabilityIngress |
refs.ts |
wire refs | ItxProps, ProjectAccess, GLOBAL_CONTEXT_ID, isChildContextId (import-light) |
types.ts |
design of record | the handwritten, import-free agent-facing surface (feeds the REPL editor) |
capabilities/ |
first-party targets | StreamsCapability, McpClient |
client.ts |
tier-3 clients | withItx for Node |
itx-react.tsx |
the browser surface | the whole React surface in one file — useIterateSession/connectIterateSession (the catalog) and useItx(ref)/connectItx(ref) (a project), useItxQuery/useIterateSessionQuery (read), useStreamConnection/useLiveState (subscribe), <ProjectScope> (ambient slug + pre-warm); ONE socket per tab with invisible reconnect, Suspense (router-provided), never SSRs |
browser-repl.ts |
dev tooling | the REPL snippet compiler (not part of the kernel) |
admin-auth-cookie.ts |
test bridge | browser-WebSocket admin auth (cookies, since WS can't set headers) |
Everything else in apps/os (oRPC, dashboard routes, domain entrypoints) sits on top of this layer or beside it — never underneath it.
Writing capabilities#
Live (session-bound — your laptop, a browser tab, another service; Scene
2). The capability IS the value you pass; provideCapability discriminates
structurally. A bare function is the simplest provider (calling the
capability calls it); a plain object of methods is the next step up — dotted
paths replay onto its members, in YOUR process, no wrapper anywhere; and an
object that implements call({ path, args }) itself owns its whole method
tree (the SDK shape: the public SDK docs become the tool docs):
import { withItx } from "~/itx/itx-client.ts";
using itx = withItx({ baseUrl, token, context: "my-project" });
const provision = await itx.provideCapability({
name: "runSwiftOnMyMac",
instructions: "Compile-and-run Swift on Jonas's Mac.",
capability: async (src) => runSwift(src),
});
// callable as itx.runSwiftOnMyMac(...) until this connection drops
// (or provision.revoke() — `using provision = …` revokes live provides)Durable, stateless. Source capabilities are member-shaped: the dial wraps
the loader entrypoint and replays the dotted path on its real members — just
export methods, no method list anywhere. types is the machine-facing
counterpart of instructions:
await itx.provideCapability({
name: "slack",
instructions: "Post to the team Slack: itx.slack.postToChannel({ channel, text }).",
types:
"declare function postToChannel(input: { channel: string; text: string }): Promise<unknown>;",
capability: {
type: "rpc",
worker: {
type: "source",
source: {
cacheKey,
mainModule: "cap.js",
modules: {
"cap.js": `
import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
async postToChannel({ channel, text }) {
// bare fetch() here IS project egress: the Slack token lives in
// project secrets and is substituted server-side (Law 5)
return await (await fetch("https://slack.com/api/chat.postMessage", {
body: JSON.stringify({ channel, text }),
headers: { authorization: 'Bearer getSecret("/secrets/slack-token")',
"content-type": "application/json" },
method: "POST",
})).json();
}
}
`,
},
},
},
},
});
await itx.slack.postToChannel({ channel: "C123", text: "hi" });Durable + stateful — exportType: "durable-object": a named export
extending DurableObject (D12), instantiated as a facet of the hosting
context node with its own private SQLite, zero provisioning:
await itx.provideCapability({
name: "todo",
capability: {
type: "rpc",
worker: {
type: "source",
source: {
cacheKey,
entrypoint: "Todo",
exportType: "durable-object",
mainModule: "cap.js",
modules: {
"cap.js": `
import { DurableObject } from "cloudflare:workers";
export class Todo extends DurableObject {
async add({ text }) { /* this.ctx.storage is YOURS alone */ }
}
`,
},
},
},
},
});Typed capabilities: there is no static name table — itx.<name> falls
through a runtime Proxy, so TypeScript only knows the built-ins. Cast a
stub through Stubify (from types.ts) to borrow an SDK's types:
import type { Stubify } from "~/itx/types.ts";
const slack = itx.capability("slack") as Stubify<import("@slack/web-api").WebClient>;
await slack.chat.postMessage({ channel, text });Execution modes (all proven in e2e/)#
| Mode | How the handle arrives |
|---|---|
| Node / laptop | withItx() → capnweb WebSocket → /api/itx |
| browser | same endpoint; REPL routes put itx in scope |
| itx script | POST /api/itx/run → loader isolate, env.ITERATE.context |
| project worker | Project DO loads it with a project-scoped env.ITERATE |
| source / facet cap | the dial loads it with an ORIGIN-scoped env.ITERATE |
The e2e suite (e2e/vitest/, runnable against any deployment) covers the
five-step live→durable capability flow, the stream as the record, egress
through both doors with real secret substitution, the two locked acceptance
tests (middleware via a bare-function fetch shadow + itx.super;
indirection via origin dial-back), extend/shadow/chain, the host-provided
workspace semantics, facet state, and HTTP routing (exposed caps are public;
unexposed caps 404).
What deliberately does not exist#
- No mounts, no scopes, no composition-as-data. Wiring is code; only overrides are data (recorded rows).
- No capability table outside the context's stream. The old SQLite registry and the fire-and-forget "audit" appends are gone; an audit log is not a concept — events are the writes, state is their fold.
- No context ids, no directory. Identity is the coordinate; the
itx_contextsD1 catalog died with the ids. - No reserved stream segments.
/itx/is a naming convention for anonymous extends, nothing more. - No name index for chain resolution — misses delegate upward per call (D2; add a cache only when latency data demands it).
- No durable delivery on live stubs. Offline means offline; durability is an address or the context's stream.
- No heap persistence for REPL/session state — replay from streams.
- No verb-level permission data. Narrower authority = a narrower context or capability, by construction.
- No global context node yet. Global handles are connect-minted views;
the locked direction makes
globala named instance of the generic host when something needs to write on it.