@nimbus-sh/fabric 0.7.0 → 0.8.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.
Files changed (52) hide show
  1. package/README.md +19 -1
  2. package/dist/bindings.d.ts +11 -0
  3. package/dist/bindings.d.ts.map +1 -1
  4. package/dist/bindings.js +19 -8
  5. package/dist/connections.d.ts +1 -1
  6. package/dist/connections.js +1 -1
  7. package/dist/do-calls.d.ts +71 -9
  8. package/dist/do-calls.d.ts.map +1 -1
  9. package/dist/do-calls.js +148 -22
  10. package/dist/facet-pool.d.ts +3 -0
  11. package/dist/facet-pool.d.ts.map +1 -1
  12. package/dist/facet-pool.js +3 -0
  13. package/dist/fanout.d.ts.map +1 -1
  14. package/dist/fanout.js +6 -3
  15. package/dist/fenced-work.d.ts +3 -3
  16. package/dist/host-dispatch.d.ts +12 -4
  17. package/dist/host-dispatch.d.ts.map +1 -1
  18. package/dist/host-dispatch.js +11 -4
  19. package/dist/image-store.js +2 -2
  20. package/dist/inner-do-registry.d.ts +9 -0
  21. package/dist/inner-do-registry.d.ts.map +1 -1
  22. package/dist/inner-do-registry.js +35 -0
  23. package/dist/isolate-pool.d.ts +8 -1
  24. package/dist/isolate-pool.d.ts.map +1 -1
  25. package/dist/isolate-pool.js +9 -7
  26. package/dist/process-fabric.d.ts +16 -5
  27. package/dist/process-fabric.d.ts.map +1 -1
  28. package/dist/process-fabric.js +18 -6
  29. package/dist/process-host.d.ts +5 -0
  30. package/dist/process-host.d.ts.map +1 -1
  31. package/dist/process-host.js +13 -11
  32. package/dist/supervisor-props.d.ts +47 -0
  33. package/dist/supervisor-props.d.ts.map +1 -0
  34. package/dist/supervisor-props.js +37 -0
  35. package/dist/workerd-facet-host.d.ts +4 -17
  36. package/dist/workerd-facet-host.d.ts.map +1 -1
  37. package/dist/workerd-facet-host.js +98 -32
  38. package/package.json +6 -6
  39. package/src/bindings.ts +25 -10
  40. package/src/connections.ts +1 -1
  41. package/src/do-calls.ts +182 -25
  42. package/src/facet-pool.ts +5 -0
  43. package/src/fanout.ts +6 -3
  44. package/src/fenced-work.ts +3 -3
  45. package/src/host-dispatch.ts +22 -7
  46. package/src/image-store.ts +2 -2
  47. package/src/inner-do-registry.ts +31 -0
  48. package/src/isolate-pool.ts +16 -8
  49. package/src/process-fabric.ts +33 -10
  50. package/src/process-host.ts +18 -11
  51. package/src/supervisor-props.ts +56 -0
  52. package/src/workerd-facet-host.ts +99 -37
package/README.md CHANGED
@@ -51,7 +51,25 @@ composeFabric({
51
51
  adoptCtxExports(ctx.exports);
52
52
  ```
53
53
 
54
- Both calls take the first value they are given.
54
+ `composeFabric` runs once per isolate. Calling it again with the same values
55
+ is a no-op; calling it again with different values throws, naming both. A
56
+ Worker that imports Nimbus's own entry (`@nimbus-sh/sdk/worker`) inherits
57
+ that entry's composition, so a host that names its own namespace composes in
58
+ a Worker that does not import it.
59
+
60
+ The route back to the host (namespace, dispatch method, supervisor
61
+ entrypoint) is minted into every binding the fabric hands a program, in the
62
+ host's isolate, and the entrypoints that answer those bindings read it from
63
+ their props. The platform serves a Worker's entrypoints from whichever
64
+ isolate it likes; an isolate that composed nothing still answers to the
65
+ right host.
66
+
67
+ Every instance of the host namespace forwards `supervisorOp(envelope)` to a
68
+ hosted runtime, the siblings Nimbus opens by name included: a wide `npm
69
+ install` fans out to `nbf:*` peers of the same namespace, and
70
+ `NIMBUS_PROCESS_HOST=peer` places processes on siblings. A sibling that
71
+ forwards to a bare workspace answers "is a host op, and this handler is a
72
+ bare workspace's".
55
73
 
56
74
  ## Timers
57
75
 
@@ -24,6 +24,7 @@
24
24
  */
25
25
  import { WorkerEntrypoint } from 'cloudflare:workers';
26
26
  import { z } from 'zod/v4';
27
+ import type { HostRoute } from './composition.js';
27
28
  import type { WorkerCode } from './vendor/types.js';
28
29
  /**
29
30
  * A dynamic worker's entrypoint, as hop 3 relays to it. `fetch` is the
@@ -67,6 +68,8 @@ interface NimbusAssetsProps {
67
68
  assetsDir?: string;
68
69
  /** Supervisor DO id whose VFS holds the assets. */
69
70
  doId?: string;
71
+ /** The way back to that DO, minted with the binding. */
72
+ route?: HostRoute;
70
73
  }
71
74
  /**
72
75
  * Assets binding shim. The inner Worker calls `env.ASSETS.fetch(request)`
@@ -105,6 +108,12 @@ declare const NimbusLoadedEntrypointPropsSchema: z.ZodObject<{
105
108
  doId: z.ZodString;
106
109
  pid: z.ZodNumber;
107
110
  writerId: z.ZodString;
111
+ route: z.ZodOptional<z.ZodObject<{
112
+ supervisorEntrypoint: z.ZodString;
113
+ hostNamespace: z.ZodString;
114
+ hostDispatchMethod: z.ZodString;
115
+ }, z.core.$strip>>;
116
+ hostIncarnation: z.ZodOptional<z.ZodString>;
108
117
  }, z.core.$strip>>;
109
118
  stage: z.ZodOptional<z.ZodUnknown>;
110
119
  }, z.core.$loose>;
@@ -213,6 +222,8 @@ export declare class NimbusLoadedEntrypoint extends WorkerEntrypoint<NimbusLoade
213
222
  interface NimbusDoNamespaceProps {
214
223
  bindingName?: string;
215
224
  supervisorDoId?: string;
225
+ /** The way back to the supervisor DO, minted with the binding. */
226
+ route?: HostRoute;
216
227
  }
217
228
  /**
218
229
  * `env.MY_DO` shim — a DurableObjectNamespace-like WorkerEntrypoint.
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
package/dist/bindings.js CHANGED
@@ -25,8 +25,7 @@
25
25
  import { WorkerEntrypoint } from 'cloudflare:workers';
26
26
  import { z } from 'zod/v4';
27
27
  import { disposeRpcResource, useRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
28
- import { supervisorEntrypoint, supervisorEntrypointName } from './composition.js';
29
- import { stagedBootAssembler } from './composition.js';
28
+ import { supervisorEntrypoint, supervisorEntrypointName, stagedBootAssembler } from './composition.js';
30
29
  import { hostNamespaceBinding, hostOpDispatch } from './host-dispatch.js';
31
30
  import { assertModuleMapWithinCodeLimit } from './budgets.js';
32
31
  /**
@@ -98,9 +97,9 @@ export class NimbusAssetsRPC extends WorkerEntrypoint {
98
97
  let stub = null;
99
98
  let dispatch;
100
99
  try {
101
- const ns = hostNamespaceBinding(this.env, 'NimbusAssetsRPC');
100
+ const ns = hostNamespaceBinding(this.env, 'NimbusAssetsRPC', props.route);
102
101
  stub = ns.get(ns.idFromString(doId));
103
- dispatch = hostOpDispatch(stub, 'NimbusAssetsRPC');
102
+ dispatch = hostOpDispatch(stub, 'NimbusAssetsRPC', props.route);
104
103
  }
105
104
  catch (e) {
106
105
  disposeRpcResource(stub);
@@ -242,6 +241,11 @@ function mimeTypeForPath(path) {
242
241
  const _NIMBUS_LOADED_CODES = new Map();
243
242
  const _LOADED_CODES_MAX = 32;
244
243
  let _loadedCodesEvictions = 0;
244
+ const HostRouteSchema = z.object({
245
+ supervisorEntrypoint: z.string().min(1),
246
+ hostNamespace: z.string().min(1),
247
+ hostDispatchMethod: z.string().min(1),
248
+ });
245
249
  const NimbusLoadedEntrypointPropsSchema = z.object({
246
250
  key: z.string().min(1),
247
251
  name: z.string().nullable().optional(),
@@ -250,6 +254,9 @@ const NimbusLoadedEntrypointPropsSchema = z.object({
250
254
  doId: z.string().min(1),
251
255
  pid: z.number().int().nonnegative(),
252
256
  writerId: z.string().uuid(),
257
+ route: HostRouteSchema.optional(),
258
+ /** The host instance's delivery incarnation (ResidentSupervisorProps). */
259
+ hostIncarnation: z.string().uuid().optional(),
253
260
  }).optional(),
254
261
  /**
255
262
  * Staged-artifact spec, for a ONE-SHOT run. The module map — ~23 MB for
@@ -445,9 +452,12 @@ export class NimbusLoadedEntrypoint extends WorkerEntrypoint {
445
452
  async _supervisorBinding(props) {
446
453
  if (!props.supervisor)
447
454
  return undefined;
448
- const factory = supervisorEntrypoint(ctxExportsOf(this.ctx));
455
+ // The route names the entrypoint too: this stateless hop may run in an
456
+ // isolate whose composition is not the host's.
457
+ const name = props.supervisor.route?.supervisorEntrypoint ?? supervisorEntrypointName();
458
+ const factory = supervisorEntrypoint(ctxExportsOf(this.ctx), name ?? undefined);
449
459
  if (!factory) {
450
- throw new Error(`Nimbus: ctx.exports.${supervisorEntrypointName() ?? '<supervisor entrypoint>'} unavailable`);
460
+ throw new Error(`Nimbus: ctx.exports.${name ?? '<supervisor entrypoint>'} unavailable`);
451
461
  }
452
462
  return await factory({ props: props.supervisor });
453
463
  }
@@ -635,6 +645,7 @@ export class NimbusDurableObjectNamespace extends WorkerEntrypoint {
635
645
  props: {
636
646
  bindingName: props.bindingName,
637
647
  supervisorDoId: props.supervisorDoId,
648
+ route: props.route,
638
649
  id: String(id),
639
650
  },
640
651
  });
@@ -664,9 +675,9 @@ export class NimbusDOStub extends WorkerEntrypoint {
664
675
  let stub = null;
665
676
  let dispatch;
666
677
  try {
667
- const ns = hostNamespaceBinding(this.env ?? {}, 'NimbusDOStub');
678
+ const ns = hostNamespaceBinding(this.env ?? {}, 'NimbusDOStub', props.route);
668
679
  stub = ns.get(ns.idFromString(supervisorDoId));
669
- dispatch = hostOpDispatch(stub, 'NimbusDOStub');
680
+ dispatch = hostOpDispatch(stub, 'NimbusDOStub', props.route);
670
681
  }
671
682
  catch (e) {
672
683
  disposeRpcResource(stub);
@@ -3,7 +3,7 @@
3
3
  * state over the WebSocket attachment.
4
4
  *
5
5
  * Specified from Proteus's DeviceSocketHub (`cf-backend/src/user/device-hub.ts`)
6
- * and CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
6
+ * and its original CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
7
7
  * pattern into its two halves:
8
8
  * - a TAG is the immutable-at-accept lookup key and authorization — it
9
9
  * rides the hibernation state, which is why the rpc gate persists auth
@@ -3,7 +3,7 @@
3
3
  * state over the WebSocket attachment.
4
4
  *
5
5
  * Specified from Proteus's DeviceSocketHub (`cf-backend/src/user/device-hub.ts`)
6
- * and CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
6
+ * and its original CLI rpc gate (`cf-backend/src/cli/rpc-gate.ts`), which split the
7
7
  * pattern into its two halves:
8
8
  * - a TAG is the immutable-at-accept lookup key and authorization — it
9
9
  * rides the hibernation state, which is why the rpc gate persists auth
@@ -3,12 +3,14 @@
3
3
  * the one property that decides whether a retry is safe.
4
4
  *
5
5
  * Both consumers asked for this. Proteus hand-wrote the retry
6
- * (`cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
6
+ * (originally `cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
7
7
  * "An operation that appends, sends, charges or mints is never wrapped: a
8
8
  * dropped call there may already have run, so a retry is a correctness bug
9
9
  * wearing resilience as a costume." agent-core has no retry machinery at all
10
10
  * and its backlog calls the gap "the most production-proven gap in the
11
11
  * corpus". Here the rule is a type: `idempotent` retries, `mutating` cannot.
12
+ * A mutation earns a retry only by carrying an identity its callee applies
13
+ * at most once; it is then `idempotent` by construction (see `mutating`).
12
14
  *
13
15
  * What the platform contract requires, and this keeps:
14
16
  * - a FRESH stub per attempt. Cloudflare documents that many exceptions
@@ -20,6 +22,11 @@
20
22
  * object is what overloaded it.
21
23
  * - attempts and backoff are the consumer-proven bounds: 3 attempts total,
22
24
  * full-jitter delays in [0, 2**attempt * 60ms).
25
+ * - an `idempotent` call may also be HEDGED (`hedgeAfterMs`): an attempt
26
+ * that has not answered by then is joined by the same call on a fresh
27
+ * stub, both left running, the first success taken. Hedges count
28
+ * against the attempts, and a callee that joins a repeat to the call it
29
+ * is already serving makes one that did arrive cost nothing.
23
30
  *
24
31
  * The resolver MINTS a stub per call and the verb disposes each one it
25
32
  * minted — that ownership is what makes the fresh-stub retry real.
@@ -37,10 +44,38 @@ export interface DoCallRetryPolicy {
37
44
  maxAttempts?: number;
38
45
  baseDelayMs?: number;
39
46
  /**
40
- * Called once per retry, before its backoff delay, with the failure the
41
- * retry is answering. The consumer's logging seam: Proteus's hand-rolled
42
- * predecessor logged every retry so a flaky object is visible in Workers
43
- * Logs rather than silently absorbed, and `operation` names it there.
47
+ * No repeat — retry or hedge — starts once this long has passed since the
48
+ * first attempt did; the failure in hand surfaces instead. A mutation made
49
+ * repeatable by an identity its callee dedupes needs it: the callee keeps
50
+ * what answers a repeat for a bounded time, so the caller's repeats must
51
+ * stop well inside it. Unbounded when absent.
52
+ */
53
+ retryWindowMs?: number;
54
+ /**
55
+ * Hedge an attempt that has not answered after this long: send the same
56
+ * call again on a fresh stub while the first stays in flight. The caller
57
+ * gets the first success; an answer after it is disposed and dropped. That
58
+ * is only harmless when a second delivery of the call changes nothing — a
59
+ * read — and cheap only when the callee joins a repeat to the call it is
60
+ * already serving. A hedge is an attempt: it counts against `maxAttempts`,
61
+ * and has its own deadline. Never hedged when absent.
62
+ *
63
+ * With attempts overlapping, a failure decides less. A retryable one is
64
+ * retried after its backoff while attempts remain, and `overloaded` stops
65
+ * every further repeat; either way the call keeps waiting on the attempts
66
+ * still in flight, and fails with the last failure only once none is. Any
67
+ * other failure is the callee's answer — the call ran — and is the call's
68
+ * at once.
69
+ */
70
+ hedgeAfterMs?: number;
71
+ /**
72
+ * Called once per retry, as it starts — after its backoff delay — with the
73
+ * failure the retry is answering. A retry a hedge made unnecessary, or
74
+ * that no attempt is left for, is never announced. The consumer's logging
75
+ * seam: Proteus's hand-rolled predecessor logged every retry so a flaky
76
+ * object is visible in Workers Logs rather than silently absorbed, and
77
+ * `operation` names it there. A callback that throws fails the call with
78
+ * its error.
44
79
  */
45
80
  onRetry?(info: DoCallRetryInfo): void;
46
81
  }
@@ -49,7 +84,10 @@ export interface DoCallRetryPolicy {
49
84
  export interface DoCallRetryInfo {
50
85
  operation: string;
51
86
  classification: DoCallClass;
52
- /** The 1-based attempt that failed; the retry about to run is attempt+1. */
87
+ /**
88
+ * The 1-based number of the attempt that failed. Without hedging the retry
89
+ * is attempt+1; with it, other attempts may have started in between.
90
+ */
53
91
  attempt: number;
54
92
  maxAttempts: number;
55
93
  error: unknown;
@@ -79,9 +117,18 @@ export declare class DoCallError extends Error {
79
117
  }
80
118
  /**
81
119
  * Call another Durable Object with an operation that is safe to repeat: a
82
- * read, or a converge-to-a-value write. Transient failures retry on a fresh
83
- * stub with full-jitter backoff; overloaded and permanent failures surface
84
- * unchanged, as does the last error at exhaustion.
120
+ * read, a converge-to-a-value write, or a mutation carrying an identity its
121
+ * callee applies at most once (see {@link mutating}). Transient failures
122
+ * retry on a fresh stub with full-jitter backoff; overloaded and permanent
123
+ * failures surface unchanged, as does the last error once no attempt may be
124
+ * repeated — attempts spent, or the policy's retry window closed. With
125
+ * `hedgeAfterMs`, an attempt still unanswered by then is joined by another
126
+ * on a fresh stub, and the first answer is taken: a success, or the
127
+ * callee's own error. A transient or overloaded failure then ends the call
128
+ * only once no attempt is left in flight.
129
+ *
130
+ * A failure of the resolver or of `onRetry` is the caller's own, and fails
131
+ * the call with it at once.
85
132
  */
86
133
  export declare function idempotent<S, T>(operation: string, stub: DoStubResolver<S>, call: (stub: S) => Promise<T>, policy?: DoCallRetryPolicy): Promise<T>;
87
134
  /**
@@ -89,6 +136,21 @@ export declare function idempotent<S, T>(operation: string, stub: DoStubResolver
89
136
  * or mints. NEVER retried — a dropped call may already have run. Failure
90
137
  * surfaces as a {@link DoCallError} carrying the classification, so the
91
138
  * caller can tell a refusal from an indeterminate drop.
139
+ *
140
+ * The rule is about the call as sent, not the operation's kind. A mutation
141
+ * the callee applies at most once per identity the call carries is
142
+ * repeatable by construction: a repeat of one that already ran is answered
143
+ * from the callee's record and applies nothing. Nimbus has two:
144
+ * - delivered filesystem mutations (@nimbus-sh/core supervisor-delivery):
145
+ * a delivery id plus the callee INSTANCE's incarnation. The record lives
146
+ * in that instance's memory, and any other instance — or a callee that
147
+ * predates delivery — refuses the call permanently rather than apply it
148
+ * without one;
149
+ * - appends: writer, module incarnation and operation sequence, recorded
150
+ * durably until acknowledged.
151
+ * Such a call goes through {@link idempotent}, re-sending the same identity
152
+ * on every attempt, with a `retryWindowMs` inside the callee's retention of
153
+ * that record. Without such an identity, a mutation stays here.
92
154
  */
93
155
  export declare function mutating<S, T>(operation: string, stub: DoStubResolver<S>, call: (stub: S) => Promise<T>): Promise<T>;
94
156
  //# sourceMappingURL=do-calls.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"do-calls.d.ts","sourceRoot":"","sources":["../src/do-calls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAqC,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAU1G,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,OAAO,CAAC,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CACvC;AAED;2CAC2C;AAC3C,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,WAAW,CAAC;IAC5B,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAErD;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAElC,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,UAAU;IACxC,QAAQ,CAAC,cAAc,EAAE,WAAW;gBAF3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,YAAY,GAAG,UAAU,EAC/B,cAAc,EAAE,WAAW,EACpC,KAAK,EAAE,OAAO;CASjB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,EACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,MAAM,GAAE,iBAAsB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAoBZ;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EACjC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAUZ"}
1
+ {"version":3,"file":"do-calls.d.ts","sourceRoot":"","sources":["../src/do-calls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAqC,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAU1G,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CACvC;AAED;2CAC2C;AAC3C,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,WAAW,CAAC;IAC5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAErD;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAElC,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,UAAU;IACxC,QAAQ,CAAC,cAAc,EAAE,WAAW;gBAF3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,YAAY,GAAG,UAAU,EAC/B,cAAc,EAAE,WAAW,EACpC,KAAK,EAAE,OAAO;CASjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,MAAM,GAAE,iBAAsB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAmHZ;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EACjC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAUZ"}
package/dist/do-calls.js CHANGED
@@ -3,12 +3,14 @@
3
3
  * the one property that decides whether a retry is safe.
4
4
  *
5
5
  * Both consumers asked for this. Proteus hand-wrote the retry
6
- * (`cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
6
+ * (originally `cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
7
7
  * "An operation that appends, sends, charges or mints is never wrapped: a
8
8
  * dropped call there may already have run, so a retry is a correctness bug
9
9
  * wearing resilience as a costume." agent-core has no retry machinery at all
10
10
  * and its backlog calls the gap "the most production-proven gap in the
11
11
  * corpus". Here the rule is a type: `idempotent` retries, `mutating` cannot.
12
+ * A mutation earns a retry only by carrying an identity its callee applies
13
+ * at most once; it is then `idempotent` by construction (see `mutating`).
12
14
  *
13
15
  * What the platform contract requires, and this keeps:
14
16
  * - a FRESH stub per attempt. Cloudflare documents that many exceptions
@@ -20,6 +22,11 @@
20
22
  * object is what overloaded it.
21
23
  * - attempts and backoff are the consumer-proven bounds: 3 attempts total,
22
24
  * full-jitter delays in [0, 2**attempt * 60ms).
25
+ * - an `idempotent` call may also be HEDGED (`hedgeAfterMs`): an attempt
26
+ * that has not answered by then is joined by the same call on a fresh
27
+ * stub, both left running, the first success taken. Hedges count
28
+ * against the attempts, and a callee that joins a repeat to the call it
29
+ * is already serving makes one that did arrive cost nothing.
23
30
  *
24
31
  * The resolver MINTS a stub per call and the verb disposes each one it
25
32
  * minted — that ownership is what makes the fresh-stub retry real.
@@ -64,38 +71,157 @@ export class DoCallError extends Error {
64
71
  }
65
72
  /**
66
73
  * Call another Durable Object with an operation that is safe to repeat: a
67
- * read, or a converge-to-a-value write. Transient failures retry on a fresh
68
- * stub with full-jitter backoff; overloaded and permanent failures surface
69
- * unchanged, as does the last error at exhaustion.
74
+ * read, a converge-to-a-value write, or a mutation carrying an identity its
75
+ * callee applies at most once (see {@link mutating}). Transient failures
76
+ * retry on a fresh stub with full-jitter backoff; overloaded and permanent
77
+ * failures surface unchanged, as does the last error once no attempt may be
78
+ * repeated — attempts spent, or the policy's retry window closed. With
79
+ * `hedgeAfterMs`, an attempt still unanswered by then is joined by another
80
+ * on a fresh stub, and the first answer is taken: a success, or the
81
+ * callee's own error. A transient or overloaded failure then ends the call
82
+ * only once no attempt is left in flight.
83
+ *
84
+ * A failure of the resolver or of `onRetry` is the caller's own, and fails
85
+ * the call with it at once.
70
86
  */
71
- export async function idempotent(operation, stub, call, policy = {}) {
87
+ export function idempotent(operation, stub, call, policy = {}) {
72
88
  const maxAttempts = policy.maxAttempts ?? MAX_ATTEMPTS;
73
89
  const baseDelayMs = policy.baseDelayMs ?? BASE_DELAY_MS;
74
- for (let attempt = 1;; attempt++) {
75
- const minted = await stub();
76
- try {
77
- const result = await call(minted);
78
- disposeRpcResource(minted);
79
- return result;
80
- }
81
- catch (error) {
82
- // A stub that threw may be permanently broken; it is never reused.
83
- disposeRpcResource(minted);
90
+ const { hedgeAfterMs, retryWindowMs } = policy;
91
+ const startedAt = Date.now();
92
+ // The executor form: fabric's library target predates Promise.withResolvers.
93
+ return new Promise((resolve, reject) => {
94
+ const hedges = new Set();
95
+ let started = 0;
96
+ // Attempts in flight, or backing off before their retry: while one is,
97
+ // a failure is not the call's answer.
98
+ let live = 0;
99
+ // An attempt was shed as overloaded: nothing is repeated after it.
100
+ let refused = false;
101
+ let settled = false;
102
+ const settle = (answer) => {
103
+ if (settled)
104
+ return;
105
+ settled = true;
106
+ for (const timer of hedges)
107
+ clearTimeout(timer);
108
+ hedges.clear();
109
+ answer();
110
+ };
111
+ /** May another attempt start at `at`? */
112
+ const canRepeat = (at) => !settled && !refused && started < maxAttempts
113
+ && (retryWindowMs === undefined || at - startedAt <= retryWindowMs);
114
+ /** `error` ended an attempt that will not be repeated: the call's answer, once nothing else is live. */
115
+ const exhausted = (error) => {
116
+ if (live === 0)
117
+ settle(() => reject(error));
118
+ };
119
+ /** A failed attempt, numbered: repeat it after its backoff, or let it stand. */
120
+ const failed = async (number, error) => {
121
+ if (settled)
122
+ return;
84
123
  const classification = classifyDoCall(error);
85
- if (!isRetryableDoCall(classification) || attempt >= maxAttempts)
86
- throw error;
87
- policy.onRetry?.({ operation, classification, attempt, maxAttempts, error });
88
- await new Promise((resolve) => {
89
- setTimeout(resolve, Math.floor(Math.random() * 2 ** attempt * baseDelayMs));
124
+ if (classification === 'overloaded') {
125
+ // A shed call is no answer: nothing more is sent, and an attempt
126
+ // still in flight may yet answer.
127
+ refused = true;
128
+ exhausted(error);
129
+ return;
130
+ }
131
+ if (!isRetryableDoCall(classification)) {
132
+ // The call ran and its answer is this error — ENOENT is a read's answer as much as bytes are.
133
+ settle(() => reject(error));
134
+ return;
135
+ }
136
+ const delayMs = Math.floor(Math.random() * 2 ** number * baseDelayMs);
137
+ if (!canRepeat(Date.now() + delayMs)) {
138
+ exhausted(error);
139
+ return;
140
+ }
141
+ live++;
142
+ await new Promise((wake) => {
143
+ setTimeout(wake, delayMs);
90
144
  });
91
- }
92
- }
145
+ live--;
146
+ // A hedge may have taken the last attempt, or answered, meanwhile.
147
+ if (!canRepeat(Date.now())) {
148
+ exhausted(error);
149
+ return;
150
+ }
151
+ policy.onRetry?.({ operation, classification, attempt: number, maxAttempts, error });
152
+ attempt();
153
+ };
154
+ const run = async () => {
155
+ const number = ++started;
156
+ live++;
157
+ const minted = await stub();
158
+ if (settled) {
159
+ live--;
160
+ disposeRpcResource(minted);
161
+ return;
162
+ }
163
+ const hedge = hedgeAfterMs === undefined ? undefined : setTimeout(() => {
164
+ if (hedge !== undefined)
165
+ hedges.delete(hedge);
166
+ if (canRepeat(Date.now()))
167
+ attempt();
168
+ }, hedgeAfterMs);
169
+ if (hedge !== undefined)
170
+ hedges.add(hedge);
171
+ /** This attempt has its answer: it hedges no more, and its stub goes. */
172
+ const answered = () => {
173
+ if (hedge !== undefined) {
174
+ clearTimeout(hedge);
175
+ hedges.delete(hedge);
176
+ }
177
+ live--;
178
+ // A stub that threw may be permanently broken; none is ever reused.
179
+ disposeRpcResource(minted);
180
+ };
181
+ let result;
182
+ try {
183
+ result = await call(minted);
184
+ }
185
+ catch (error) {
186
+ answered();
187
+ await failed(number, error);
188
+ return;
189
+ }
190
+ answered();
191
+ if (settled) {
192
+ // Another attempt answered first: this answer is dropped, so nothing of it is kept.
193
+ disposeRpcResource(result);
194
+ return;
195
+ }
196
+ settle(() => resolve(result));
197
+ };
198
+ /** Start an attempt. Whatever it throws outside the call itself fails the call. */
199
+ const attempt = () => {
200
+ run().catch((error) => settle(() => reject(error)));
201
+ };
202
+ attempt();
203
+ });
93
204
  }
94
205
  /**
95
206
  * Call another Durable Object with an operation that appends, sends, charges
96
207
  * or mints. NEVER retried — a dropped call may already have run. Failure
97
208
  * surfaces as a {@link DoCallError} carrying the classification, so the
98
209
  * caller can tell a refusal from an indeterminate drop.
210
+ *
211
+ * The rule is about the call as sent, not the operation's kind. A mutation
212
+ * the callee applies at most once per identity the call carries is
213
+ * repeatable by construction: a repeat of one that already ran is answered
214
+ * from the callee's record and applies nothing. Nimbus has two:
215
+ * - delivered filesystem mutations (@nimbus-sh/core supervisor-delivery):
216
+ * a delivery id plus the callee INSTANCE's incarnation. The record lives
217
+ * in that instance's memory, and any other instance — or a callee that
218
+ * predates delivery — refuses the call permanently rather than apply it
219
+ * without one;
220
+ * - appends: writer, module incarnation and operation sequence, recorded
221
+ * durably until acknowledged.
222
+ * Such a call goes through {@link idempotent}, re-sending the same identity
223
+ * on every attempt, with a `retryWindowMs` inside the callee's retention of
224
+ * that record. Without such an identity, a mutation stays here.
99
225
  */
100
226
  export async function mutating(operation, stub, call) {
101
227
  const minted = await stub();
@@ -41,6 +41,7 @@
41
41
  * whichever acts second aborts or retires facets the other still tracks,
42
42
  * and the facet-id ledger here counts only the names this pool minted.
43
43
  */
44
+ import type { SqlDatabase } from '@nimbus-sh/core/runtime/os-contracts.js';
44
45
  /** `ctx.facets`, as the pool drives it — same surface the facet host uses. */
45
46
  export interface FacetPoolContainer {
46
47
  get(name: string, start: () => Promise<{
@@ -56,6 +57,8 @@ export interface FacetPoolContext {
56
57
  storage: {
57
58
  get(key: string): Promise<unknown> | unknown;
58
59
  put(key: string, value: unknown): Promise<void>;
60
+ /** The session's SQL, where the storage ledger (N18) records facet databases. */
61
+ sql?: SqlDatabase;
59
62
  };
60
63
  }
61
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"facet-pool.d.ts","sourceRoot":"","sources":["../src/facet-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AASH,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC;IACrE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;wCACwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE;QACP,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACjD,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,gEAAgE;IAChE,MAAM,IAAI,IAAI,CAAC;IACf,sEAAsE;IACtE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAKD,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CAE1D;AAED,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,gBAAgB;IAElD;;;;OAIG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GACvC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CAiD1B"}
1
+ {"version":3,"file":"facet-pool.d.ts","sourceRoot":"","sources":["../src/facet-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAQ3E,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC;IACrE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;wCACwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE;QACP,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,iFAAiF;QACjF,GAAG,CAAC,EAAE,WAAW,CAAC;KACnB,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,gEAAgE;IAChE,MAAM,IAAI,IAAI,CAAC;IACf,sEAAsE;IACtE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAKD,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CAE1D;AAED,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,gBAAgB;IAElD;;;;OAIG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GACvC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CAkD1B"}
@@ -41,6 +41,7 @@
41
41
  * whichever acts second aborts or retires facets the other still tracks,
42
42
  * and the facet-id ledger here counts only the names this pool minted.
43
43
  */
44
+ import { forgetFacetStorage } from '@nimbus-sh/core/runtime/storage-ledger.js';
44
45
  import { FACET_ID_LIFETIME_BUDGET, facetNameCount, recordFacetNameMinted, withFacetBudgetNamed, } from './budgets.js';
45
46
  /** Names this ctx's pool has already charged to the lifetime ledger. */
46
47
  const chargedNames = new WeakMap();
@@ -93,6 +94,8 @@ export class FacetPool {
93
94
  return;
94
95
  try {
95
96
  facets.delete(name);
97
+ if (this.ctx.storage.sql)
98
+ forgetFacetStorage(this.ctx.storage.sql, name);
96
99
  }
97
100
  catch (e) {
98
101
  throw new Error(`fabric: facet '${name}' was evicted but its storage was not reclaimed — `
@@ -1 +1 @@
1
- {"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOtD;;+EAE+E;AAC/E,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,qBAAqB,UAAmB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UAAqB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,uCAAuC;AACvC,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,CAAC,CAAC;CACT;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;;;GAQG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa;IAoBzE;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,UAAU,CAAC,CAAC,EAAE,CAAC,EACnB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,EAAE,CAAC;IASf,mEAAmE;IACnE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO;IAK7D;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;YAOvC,aAAa;YAoCb,eAAe;CA2J9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAUrE"}
1
+ {"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOtD;;+EAE+E;AAC/E,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,qBAAqB,UAAmB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UAAqB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,uCAAuC;AACvC,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,CAAC,CAAC;CACT;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;;;GAQG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa;IAoBzE;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,UAAU,CAAC,CAAC,EAAE,CAAC,EACnB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,EAAE,CAAC;IASf,mEAAmE;IACnE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO;IAK7D;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;YAOvC,aAAa;YAoCb,eAAe;CA6J9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAUrE"}
package/dist/fanout.js CHANGED
@@ -16,8 +16,9 @@
16
16
  import { serializeFunction } from './vendor/serialize.js';
17
17
  import { BindingError } from './vendor/errors.js';
18
18
  import { IsolatePool } from './isolate-pool.js';
19
+ import { hostRoute } from './composition.js';
19
20
  import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
20
- import { describeError, isDoOverloaded, isTransientDoReset } from '@nimbus-sh/platform/oom-classify.js';
21
+ import { classifyDoCall, describeError, isRetryableDoCall } from '@nimbus-sh/platform/oom-classify.js';
21
22
  import { hostNamespaceBinding, hostOpDispatch, } from './host-dispatch.js';
22
23
  /**
23
24
  * Threshold at which routing switches from coordinator-local loaders to
@@ -265,6 +266,7 @@ export class Fanout {
265
266
  // ... write into the peer's own VFS — invisible to the
266
267
  // user. See INSTALL-HONESTY-retro.md.
267
268
  coordinatorDoId: this.coordDoId,
269
+ coordinatorRoute: hostRoute() ?? undefined,
268
270
  // Credential source for peer-side writeBatchStream — the
269
271
  // invoking process pid, so package writes are authorized
270
272
  // as the user (not rejected as pid:0).
@@ -295,8 +297,9 @@ export class Fanout {
295
297
  return;
296
298
  }
297
299
  catch (err) {
298
- const schedule = isTransientDoReset(err) ? PEER_RETRY_BACKOFF_MS
299
- : isDoOverloaded(err) ? PEER_OVERLOAD_BACKOFF_MS
300
+ const cls = classifyDoCall(err);
301
+ const schedule = cls === 'overloaded' ? PEER_OVERLOAD_BACKOFF_MS
302
+ : isRetryableDoCall(cls) ? PEER_RETRY_BACKOFF_MS
300
303
  : null;
301
304
  if (schedule && attempt < PEER_TRANSIENT_RESET_RETRIES) {
302
305
  const backoff = schedule[Math.min(attempt, schedule.length - 1)];
@@ -53,9 +53,9 @@ export declare const FENCED_WORK_MAX_ATTEMPT = 1;
53
53
  * (staging, 2026-08-13): every observed reset struck seconds AFTER the launch
54
54
  * settled — the platform kills the object while the resident runs, which is
55
55
  * when a launch-scoped row had already been deleted and recovery had nothing
56
- * to find. A resident's facet cannot outlive its session instance (the
57
- * process host's held-open leg dies with it), so a row from a previous
58
- * generation always names a process that is genuinely gone.
56
+ * to find. A resident's facet can outlive its session instance (a pending
57
+ * timer keeps it running), but nothing routes to it from the next one, and a
58
+ * spawn that takes its facet name ends it first.
59
59
  */
60
60
  export interface FencedWorkRecord {
61
61
  pid: number;