Simplify itx dialable target data to trusted and untrusted addresses
The current production itx target model exposes too many materialization details
as peer target kinds: binding, loopback, source, and durable-object.
That makes "what can I dial?" harder to understand than the actual platform
topology.
Collapse the durable/sturdy dialable data model into two layers:
- Untrusted capability addresses — provider/user/script supplied.
- Trusted capability addresses — platform/built-in supplied.
Untrusted addresses must not mention raw env bindings, loopback export names, Durable Object namespace bindings, project ids, context refs, or mounted paths. Trusted addresses may use those escape hatches because they are written by platform code, not by a capability provider.
Proposed shape#
Sketch only; final names should follow the surrounding apps/os style.
type CapabilityAddress = UntrustedCapabilityAddress | TrustedCapabilityAddress;
type UntrustedCapabilityAddress = DynamicWorkerAddress | DynamicDurableObjectAddress;
type DynamicWorkerAddress = {
type: "dynamic-worker";
source: DynamicWorkerSource;
entrypoint?: string;
props?: Record<string, unknown>;
};
type DynamicDurableObjectAddress = {
type: "dynamic-durable-object";
source: DynamicWorkerSource;
className: string;
};
type TrustedCapabilityAddress =
| UntrustedCapabilityAddress
| TrustedEnvBindingAddress
| TrustedLoopbackAddress
| TrustedDurableObjectAddress;
type TrustedLoopbackAddress = {
type: "loopback";
entrypoint: string;
props?: Record<string, unknown>;
};
type TrustedEnvBindingAddress = {
type: "env-binding";
binding: string;
path?: string[];
};
type TrustedDurableObjectAddress = {
type: "durable-object";
namespace: string;
name: string;
path?: string[];
};
type DynamicWorkerSource =
| {
type: "inline";
mainModule: string;
modules: Record<string, string>;
}
| {
type: "repo";
repo: string;
commit: string | "latest";
path: string;
bundle?: { minify?: boolean; externals?: string[] };
};
type DynamicWorkerOptions = {
compatibilityDate?: string;
};
type ItxBinding = {
get(): Promise<unknown>;
};Meaning:
type === "dynamic-worker"is the safe public source-backed capability address. It is the public form of what production currently represents as asourceworker withexportType: "worker-entrypoint".type === "dynamic-durable-object"is the safe public stateful source-backed address. It loads inline code or repo source, extracts a named class ending inDurableObject, and materializes it as a facet of the currentItxDurableObject.type === "loopback"is trusted-only. It means a namedctx.exportsentrypoint in the current worker script.WorkerEntrypointis the runtime backend for this shape, not the conceptual capability model.type === "env-binding"is trusted-only. It means a host env binding whose concrete object is replayed directly. Use sparingly; prefer loopback or durable-object addresses for domain surfaces.type === "durable-object"is trusted-only. It means a Durable Object namespace plus object name plus an optional path prefix. Trusted domain objects can use this to mount exact methods on themselves, for example an Agent Durable Object mounting its ownsendMessageorstreammethod as an itx built-in.propscarries WorkerEntrypoint construction parameters and only belongs on WorkerEntrypoint-backed addresses (dynamic-workerandloopback). It does not belong ondynamic-durable-object.- Dynamic worker source is represented directly as
type: "dynamic-worker", not as a public loopback reference to an adapter. The runtime may share loader helper code, but that adapter name is not provider-supplied target data. - Dynamic durable source is represented directly as
type: "dynamic-durable-object", not asdynamic-workerplusexportType. The dialer branch should inline the Cloudflare AppRunner shape:this.ctx.facets.get(facetName, async () => ({ class })). - The facet name is host-owned, e.g.
cap:<mounted-capability-path>ordynamic-durable-object:<mounted-capability-path>, not provider-supplied target data. - Dynamic workers loaded by the dialer should receive an
env.ITXbinding. The source worker should obtain its scoped itx handle withawait env.ITX.get()rather than by being handed a special argument or by reaching back through HTTP. Express this as the one service-binding-style entrypoint we expect to expose to dynamic code. - Dynamic durable workers require host facet power. The itx processor should
not create facets directly; its host/dialer branch creates or resumes a facet
on the current
ItxDurableObject. This mirrors v1:ItxDurableObjectpassesdurableObjectFacetsHook(this.ctx)intomakeDial, and the dialer callsctx.facets.get(...)for stateful source capabilities. context,itxRef,capabilityPath, mounted path, origin, and similar attribution/scoping values must not appear in public target data. Ifenv.ITXneeds them, they are internal host wiring: the host mounting the capability already knows which itx it is serving and where the capability is mounted.durable-object.namespacenames a trusted Durable Object namespace known to the dialer.durable-object.namenames the object instance inside that namespace.durable-object.pathis a trusted path prefix replayed before the caller's remainder path. This is the deep sturdy-ref shape for trusted built-ins.
Rules#
- OpenAPI, MCP, Slack, Gmail, Secrets, etc. are not untrusted target kinds. They are trusted platform-provided capabilities, often implemented as loopback WorkerEntrypoints. WorkerEntrypoint is an implementation backend, not a capability concept.
- Plain host resources like
env.AI, R2, D1, KV, queues, and Hyperdrive should not be public target families. If they are exposed to itx, expose them through trusted platform capabilities. - Raw env binding names are trusted-only. This removes the need for a public allowlist around arbitrary provider-supplied env binding references.
- Durable Object namespace bindings are trusted-only. Dynamic durable user code gets state through facets instead of naming a namespace.
- Dynamic/source workers are the public untrusted address shape. They are safe
because the host chooses the loader, env,
ITXbinding, facet host, and state address. - Facet creation is a host/dialer dependency, not stream-fold state. The folded
capability row stores only the
dynamic-durable-objectaddress; on invoke, the dialer callsctx.facets.get(...)on the currentItxDurableObject. - The dynamic worker environment should expose
ITX, notITERATE, and the ergonomic API should beenv.ITX.get()for the current scoped handle. The source target data does not need to carry an explicit context ref; the host mounting the capability already knows which itx it is wiring. - Public
propsare for the worker entrypoint's own configuration only (dynamic-worker, loopback clients, API URLs, etc.). They are not where the host smuggles identity or attribution, and they are not available on dynamic durable object addresses. - Trusted domain objects may seed their own context with exact durable-object
path refs. This is how an Agent Durable Object can expose
itx.sendMessageoritx.streamwithout making the Agent Durable Object host the itx processor. - Live targets are outside this task. They are ephemeral session capabilities, not sturdy dialable data.
Migration Notes#
- Current
{ type: "rpc", worker: { type: "loopback" }, entrypoint, props }maps to trustedtype: "loopback"withentrypointandprops. - Current
{ type: "rpc", worker: { type: "binding", binding }, entrypoint?, props? }maps to trustedtype: "env-binding"only when direct replay is genuinely intended. Service-binding/named-entrypoint usage should be modeled as a WorkerEntrypoint-backed runtime detail, not as public target language. - Current
{ type: "rpc", worker: { type: "durable-object", binding, name } }maps to trustedtype: "durable-object". - Current
{ type: "rpc", worker: { type: "source", source }, ... }maps to untrustedtype: "dynamic-worker"ortype: "dynamic-durable-object". Repo-backed sources use the current production shape{ type: "repo", repo, commit, path, bundle? }. - Current v1 dynamic durable source behavior maps directly: v1 creates a
host-owned facet through the dialer. v2 should create an equivalent dynamic
durable object facet keyed by the canonical mounted capability path, e.g.
dynamic-durable-object:<hash-of-mounted-path>. There is no separate "capability name" concept in v2.
Out of Scope#
- Do not implement
/api/itx2in this task. - Do not port all existing production capabilities.
- Do not expose raw env bindings or loopback entrypoint names in untrusted provider data.
- Do not change the live-capability bridge.
- Do not introduce a generic dynamic Durable Object runner. The
ItxDurableObjectis the supervisor; facets are the platform primitive. - Do not add DSL/helper builders for trusted built-ins as part of the model. Registration examples should show the literal address objects.
Acceptance#
- The public/untrusted target data model has no raw env binding names, loopback export names, Durable Object namespace binding names, project ids, context refs, origins, or capability paths.
- Untrusted dynamic stateless code is represented by
type: "dynamic-worker". - Untrusted dynamic stateful code is represented by
type: "dynamic-durable-object"and materialized as a facet. - Trusted platform data can still represent loopback WorkerEntrypoints, direct env bindings, and deployed Durable Object namespace targets.
- Existing first-party capabilities currently represented as loopbacks are described as trusted loopback addresses, usually implemented by WorkerEntrypoint classes.
- Trusted Durable Object targets require
{ namespace, name }, may include apathprefix, and are not confused with direct env binding replay. - Raw resource bindings are reachable only through trusted platform capabilities.
- The current
sourcetarget is represented astype: "dynamic-worker"ortype: "dynamic-durable-object"using inline code or the existing production repo source data. - Dynamic durable workers are materialized through a host-supplied facet
dependency on the current
ItxDurableObject, not by the processor fold and not by provider-supplied namespace data. - Source-loaded workers receive a scoped
env.ITXbinding and can callawait env.ITX.get()to access their current itx context. - The public target data contains no
context,itxRef,origin, orcapabilityPathfields. Those are internal host facts, not provider input.