@substrat-run/adapter-cloudflare 0.118.0 → 0.120.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * driving `drainDue`/`runDueSchedules` per scope (#461)
15
15
  */
16
16
  export { CloudflareScopeHost } from './host.js';
17
- export type { CloudflareScopeHostOptions, ConnectorDelegation, EventDrainDelegation, SystemSwitchDelegation, } from './host.js';
17
+ export type { CloudflareScopeHostOptions, ConnectorDelegation, EventDrainDelegation, PeerSwitchDelegation, SystemSwitchDelegation, } from './host.js';
18
18
  export { defineScopeDO } from './scope-do.js';
19
19
  export { ControlPlaneDO } from './control-plane-do.js';
20
20
  export { OperationQueue } from './serialization.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC1F,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC1F,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAOhD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAQjF,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAQhD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAQjF,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type RouteTarget } from '@substrat-run/contracts';
1
+ import { type PeerCaller, type RouteTarget } from '@substrat-run/contracts';
2
2
  /**
3
3
  * Hostname → route target, for the router worker (K-26).
4
4
  *
@@ -24,6 +24,9 @@ export interface RouteRowLike {
24
24
  * `outbound` array lifted from the resolved version's manifest in the same read.
25
25
  * Null = a pre-#303 manifest (or no version), which dispatches unenforced. */
26
26
  outbound_json?: string | null;
27
+ /** The dispatched code's declared outgoing peer calls (#1706) as JSON text, on the
28
+ * same terms as `outbound_json`: null for a version pushed before the declaration. */
29
+ calls_json?: string | null;
27
30
  }
28
31
  export type RouteResolver = (hostname: string) => Promise<RouteTarget | undefined>;
29
32
  /** A hostname row → what the router dispatches on. Shared with `CloudflareScopeHost`
@@ -38,13 +41,69 @@ export declare const normalizeHostname: (hostname: string) => string;
38
41
  * carve-out `resolveIdentity` has (K-24). Only `active` bindings resolve, so a
39
42
  * hostname still validating DNS or one whose certificate failed is simply unknown.
40
43
  *
41
- * It does **not** re-check tenant suspension. `getScope` owns that, inside the
42
- * vertical, and a second enforcement point is a second thing that can disagree with
43
- * the first. The router's job is to find the door, not to decide who may open it.
44
+ * The directory requires an active hostname, correctly paired active scope and
45
+ * active owning tenant (#1713). CP-less verticals rely on this request-path gate;
46
+ * CP-backed `getScope` retains its own check. No lifecycle state crosses the wire.
47
+ * Direct/background calls and already-dispatched requests remain outside this gate.
44
48
  *
45
49
  * Uncached, per request, on purpose: K-26 defers cache invalidation to open
46
50
  * question 5 rather than answering it twice, because a cached route that keeps
47
51
  * serving a suspended tenant blunts suspension — which §7 calls a live weapon.
48
52
  */
49
53
  export declare function createRouteResolver(controlPlane: DurableObjectNamespace): RouteResolver;
54
+ /** `peerCallTarget`'s answer, as the router reads it. */
55
+ export interface PeerCallTargetRowLike {
56
+ caller: {
57
+ state: 'ok' | 'unknown' | 'not-primary' | 'inactive';
58
+ status: string | null;
59
+ };
60
+ outcome: 'resolved' | 'not-installed' | 'ambiguous';
61
+ count: number;
62
+ target: {
63
+ scope_id: string;
64
+ tenant_id: string;
65
+ vertical: string | null;
66
+ deployment_ref: string | null;
67
+ outbound_json: string | null;
68
+ calls_json: string | null;
69
+ } | null;
70
+ }
71
+ /**
72
+ * What the router does with a peer call: dispatch it to one instance, or refuse it with a
73
+ * reason a builder can act on. A discriminated union, so a caller cannot read a refusal as a
74
+ * destination — the shape `Decision` and `Coverage` use elsewhere, for that reason.
75
+ */
76
+ export type PeerCallDecision = {
77
+ outcome: 'dispatch';
78
+ /** The target instance and the script it runs — what `DISPATCH.get` takes. */
79
+ tenantId: string;
80
+ scopeId: string;
81
+ vertical: string;
82
+ deploymentRef: string;
83
+ /** The TARGET's own dispatch parameters, one hop deeper than the caller's. */
84
+ outboundHosts: string[] | null;
85
+ calls: string[] | null;
86
+ depth: number;
87
+ } | {
88
+ outcome: 'refused';
89
+ code: 'not_found' | 'conflict' | 'forbidden' | 'unavailable';
90
+ message: string;
91
+ };
92
+ /**
93
+ * Decide ONE peer call, over the directory (#1706). Pure but for the single read, so the
94
+ * order of the gates is readable in one place — and each is a refusal a test can name:
95
+ *
96
+ * 1. **The depth**, first: a chain already at the bound is refused before any read, so a loop
97
+ * costs one dispatch rather than a directory round trip per hop.
98
+ * 2. **The caller declared this target** (`substrat.calls`). A version pushed before the
99
+ * declaration existed carries `null` and is unenforced, exactly as a pre-#303 `outbound` is.
100
+ * 3. **The caller is who the platform says it is**: its scope record still names the vertical
101
+ * the dispatch ran as, in this tenant. Read here rather than taken from routing, so this
102
+ * path does not inherit #1713.
103
+ * 4. **The caller is a live primary instance** — not suspended, archived, a fork or a preview.
104
+ * 5. **The target resolves** to exactly one live primary instance in the SAME tenant, by the
105
+ * kernel's one rule. Never guessed when a tenant runs two.
106
+ * 6. **The target has a script to dispatch.**
107
+ */
108
+ export declare function createPeerCallResolver(controlPlane: DurableObjectNamespace): (caller: PeerCaller, target: string) => Promise<PeerCallDecision>;
50
109
  //# sourceMappingURL=route-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route-resolver.d.ts","sourceRoot":"","sources":["../src/route-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAExE;;;;;;;;;;;GAWG;AAEH,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;mFAE+E;IAC/E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAOD,MAAM,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;AAEnF;yDACyD;AACzD,wBAAgB,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CA2BpF;AAED,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,aAAc,MAAM,KAAG,MAAgC,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,sBAAsB,GAAG,aAAa,CAgBvF"}
1
+ {"version":3,"file":"route-resolver.d.ts","sourceRoot":"","sources":["../src/route-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,UAAU,EACf,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;GAWG;AAEH,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;mFAE+E;IAC/E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;2FACuF;IACvF,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAOD,MAAM,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;AAEnF;yDACyD;AACzD,wBAAgB,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAuCpF;AAED,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,aAAc,MAAM,KAAG,MAAgC,CAAC;AAEtF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,sBAAsB,GAAG,aAAa,CAgBvF;AAgBD,yDAAyD;AACzD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QAAE,KAAK,EAAE,IAAI,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACxF,OAAO,EAAE,UAAU,GAAG,eAAe,GAAG,WAAW,CAAC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GAAG,IAAI,CAAC;CACV;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB;IACE,OAAO,EAAE,UAAU,CAAC;IACpB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAY1G;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,sBAAsB,GACnC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAsEnE"}
@@ -1,8 +1,8 @@
1
- import { routeTarget } from '@substrat-run/contracts';
1
+ import { callDepthMessage, callsDeclares, PEER_CALL_DEPTH_MAX, previewCallerMessage, routeTarget, undeclaredCallMessage, } from '@substrat-run/contracts';
2
2
  /** A hostname row → what the router dispatches on. Shared with `CloudflareScopeHost`
3
3
  * so the two cannot drift on what "resolvable" means. */
4
4
  export function toRouteTarget(row) {
5
- // No status check here: `readRoute` filters `h.status = 'active'` in SQL, the same
5
+ // No status check here: `readRoute` gates hostname, scope and tenant lifecycle in SQL, the same
6
6
  // way adapter-sqlite's `resolveHostname` does. One place decides what resolves.
7
7
  if (!row)
8
8
  return undefined;
@@ -21,6 +21,19 @@ export function toRouteTarget(row) {
21
21
  // Malformed JSON never breaks routing — the request still dispatches, unenforced.
22
22
  }
23
23
  }
24
+ // #1706: the declared outgoing peer calls, lifted from the same manifest on the same
25
+ // terms — null for a version that predates the declaration, which is unenforced.
26
+ let calls = null;
27
+ if (row.calls_json) {
28
+ try {
29
+ const parsed = JSON.parse(row.calls_json);
30
+ if (Array.isArray(parsed))
31
+ calls = parsed.filter((v) => typeof v === 'string');
32
+ }
33
+ catch {
34
+ // Malformed JSON never breaks routing, exactly as above.
35
+ }
36
+ }
24
37
  return routeTarget.parse({
25
38
  tenantId: row.tenant_id,
26
39
  scopeId: row.scope_id,
@@ -29,6 +42,7 @@ export function toRouteTarget(row) {
29
42
  surface: row.surface,
30
43
  region: row.region,
31
44
  outboundHosts,
45
+ calls,
32
46
  });
33
47
  }
34
48
  /** DNS is case-insensitive, so the map is normalized and lookups must match. */
@@ -40,9 +54,10 @@ export const normalizeHostname = (hostname) => hostname.toLowerCase();
40
54
  * carve-out `resolveIdentity` has (K-24). Only `active` bindings resolve, so a
41
55
  * hostname still validating DNS or one whose certificate failed is simply unknown.
42
56
  *
43
- * It does **not** re-check tenant suspension. `getScope` owns that, inside the
44
- * vertical, and a second enforcement point is a second thing that can disagree with
45
- * the first. The router's job is to find the door, not to decide who may open it.
57
+ * The directory requires an active hostname, correctly paired active scope and
58
+ * active owning tenant (#1713). CP-less verticals rely on this request-path gate;
59
+ * CP-backed `getScope` retains its own check. No lifecycle state crosses the wire.
60
+ * Direct/background calls and already-dispatched requests remain outside this gate.
46
61
  *
47
62
  * Uncached, per request, on purpose: K-26 defers cache invalidation to open
48
63
  * question 5 rather than answering it twice, because a cached route that keeps
@@ -63,4 +78,100 @@ export function createRouteResolver(controlPlane) {
63
78
  return toRouteTarget(await cp.readRoute(normalizeHostname(hostname)));
64
79
  };
65
80
  }
81
+ const jsonList = (raw) => {
82
+ if (!raw)
83
+ return null;
84
+ try {
85
+ const parsed = JSON.parse(raw);
86
+ return Array.isArray(parsed) ? parsed.filter((v) => typeof v === 'string') : null;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ };
92
+ /**
93
+ * Decide ONE peer call, over the directory (#1706). Pure but for the single read, so the
94
+ * order of the gates is readable in one place — and each is a refusal a test can name:
95
+ *
96
+ * 1. **The depth**, first: a chain already at the bound is refused before any read, so a loop
97
+ * costs one dispatch rather than a directory round trip per hop.
98
+ * 2. **The caller declared this target** (`substrat.calls`). A version pushed before the
99
+ * declaration existed carries `null` and is unenforced, exactly as a pre-#303 `outbound` is.
100
+ * 3. **The caller is who the platform says it is**: its scope record still names the vertical
101
+ * the dispatch ran as, in this tenant. Read here rather than taken from routing, so this
102
+ * path does not inherit #1713.
103
+ * 4. **The caller is a live primary instance** — not suspended, archived, a fork or a preview.
104
+ * 5. **The target resolves** to exactly one live primary instance in the SAME tenant, by the
105
+ * kernel's one rule. Never guessed when a tenant runs two.
106
+ * 6. **The target has a script to dispatch.**
107
+ */
108
+ export function createPeerCallResolver(controlPlane) {
109
+ return async (caller, target) => {
110
+ if (caller.depth >= PEER_CALL_DEPTH_MAX) {
111
+ return { outcome: 'refused', code: 'forbidden', message: callDepthMessage(caller.depth) };
112
+ }
113
+ if (!callsDeclares(caller.calls, target)) {
114
+ return {
115
+ outcome: 'refused',
116
+ code: 'forbidden',
117
+ message: undeclaredCallMessage(caller.vertical, target),
118
+ };
119
+ }
120
+ // Per request, never held: a stub belongs to the request that created it (see above).
121
+ const cp = controlPlane.get(controlPlane.idFromName('control-plane'));
122
+ const row = await cp.peerCallTarget(caller.tenantId, caller.scopeId, caller.vertical, target);
123
+ if (row.caller.state === 'unknown') {
124
+ return {
125
+ outcome: 'refused',
126
+ code: 'forbidden',
127
+ message: `scope ${caller.scopeId} is not an instance of '${caller.vertical}' in this tenant`,
128
+ };
129
+ }
130
+ if (row.caller.state === 'not-primary') {
131
+ return { outcome: 'refused', code: 'forbidden', message: previewCallerMessage(caller.vertical) };
132
+ }
133
+ if (row.caller.state === 'inactive') {
134
+ return {
135
+ outcome: 'refused',
136
+ code: 'forbidden',
137
+ message: `'${caller.vertical}' may not call other verticals from a ${row.caller.status ?? 'non-active'} ` +
138
+ 'scope — only a live primary instance acts as its vertical',
139
+ };
140
+ }
141
+ // Ambiguity first: an ambiguous answer carries no target either, so testing for a missing
142
+ // target before the count would report "not installed" for a tenant that runs two.
143
+ if (row.outcome === 'ambiguous') {
144
+ return {
145
+ outcome: 'refused',
146
+ code: 'conflict',
147
+ message: `this tenant runs ${row.count} instances of '${target}' — a call cannot pick one; ` +
148
+ 'bind the instance first',
149
+ };
150
+ }
151
+ if (row.outcome === 'not-installed' || !row.target) {
152
+ return {
153
+ outcome: 'refused',
154
+ code: 'not_found',
155
+ message: `vertical '${target}' is not installed in this tenant`,
156
+ };
157
+ }
158
+ if (!row.target.deployment_ref) {
159
+ return {
160
+ outcome: 'refused',
161
+ code: 'unavailable',
162
+ message: `vertical '${target}' has no deployed version in this tenant`,
163
+ };
164
+ }
165
+ return {
166
+ outcome: 'dispatch',
167
+ tenantId: row.target.tenant_id,
168
+ scopeId: row.target.scope_id,
169
+ vertical: target,
170
+ deploymentRef: row.target.deployment_ref,
171
+ outboundHosts: jsonList(row.target.outbound_json),
172
+ calls: jsonList(row.target.calls_json),
173
+ depth: caller.depth + 1,
174
+ };
175
+ };
176
+ }
66
177
  //# sourceMappingURL=route-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"route-resolver.js","sourceRoot":"","sources":["../src/route-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAoB,MAAM,yBAAyB,CAAC;AAqCxE;yDACyD;AACzD,MAAM,UAAU,aAAa,CAAC,GAA6B;IACzD,mFAAmF;IACnF,gFAAgF;IAChF,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,mFAAmF;IACnF,iFAAiF;IACjF,+EAA+E;IAC/E,IAAI,aAAa,GAAoB,IAAI,CAAC;IAC1C,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAY,CAAC;YACxD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,kFAAkF;QACpF,CAAC;IACH,CAAC;IACD,OAAO,WAAW,CAAC,KAAK,CAAC;QACvB,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,OAAO,EAAE,GAAG,CAAC,QAAQ;QACrB,YAAY,EAAE,GAAG,CAAC,aAAa;QAC/B,aAAa,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI;QACzC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,aAAa;KACd,CAAC,CAAC;AACL,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAoC;IACtE,OAAO,KAAK,EAAE,QAAgB,EAAE,EAAE;QAChC,oEAAoE;QACpE,EAAE;QACF,+EAA+E;QAC/E,kFAAkF;QAClF,8EAA8E;QAC9E,gFAAgF;QAChF,gFAAgF;QAChF,EAAE;QACF,kFAAkF;QAClF,MAAM,EAAE,GAAG,YAAY,CAAC,GAAG,CACzB,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC,CACZ,CAAC;QAC/B,OAAO,aAAa,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"route-resolver.js","sourceRoot":"","sources":["../src/route-resolver.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { DurableObject } from 'cloudflare:workers';
2
+ import { type WireFailure } from '@substrat-run/contracts';
2
3
  import { type ModuleRegistration, type OperationHandler } from '@substrat-run/kernel';
3
4
  /**
4
5
  * `defineScopeDO` — one Durable Object per scope, the CF analogue of a single
@@ -31,6 +32,16 @@ export interface ScopeDoEnv {
31
32
  */
32
33
  SUBSTRAT_VERSION_ID?: string;
33
34
  }
35
+ /**
36
+ * What a capability attachment verb (#1686) answers: its value, or its failure as DATA —
37
+ * `invoke`'s envelope discipline, since a throw across the RPC keeps only its message.
38
+ */
39
+ export type CapabilityAttachmentReply<T> = {
40
+ value: T;
41
+ failure?: undefined;
42
+ } | {
43
+ failure: WireFailure;
44
+ };
34
45
  export declare function splitSqlStatements(sql: string): string[];
35
46
  export declare function defineScopeDO(modules: ModuleRegistration[], bareOps: Record<string, OperationHandler<never, unknown>>): new (ctx: DurableObjectState, env: ScopeDoEnv) => DurableObject;
36
47
  //# sourceMappingURL=scope-do.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAkDnD,OAAO,EA+CL,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAkDtB,MAAM,sBAAsB,CAAC;AA+D9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAkdD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwDxD;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,GACxD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CAuiIjE"}
1
+ {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAWL,KAAK,WAAW,EAqCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAmDL,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EA8DtB,MAAM,sBAAsB,CAAC;AA6E9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAwaD;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAgDxG,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwDxD;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,GACxD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CA48IjE"}