@nimbus-sh/fabric 0.1.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +487 -0
  3. package/dist/alarms.d.ts +134 -0
  4. package/dist/alarms.d.ts.map +1 -0
  5. package/dist/alarms.js +214 -0
  6. package/dist/bindings.d.ts +316 -0
  7. package/dist/bindings.d.ts.map +1 -0
  8. package/dist/bindings.js +678 -0
  9. package/dist/ctx-exports.d.ts +47 -0
  10. package/dist/ctx-exports.d.ts.map +1 -0
  11. package/dist/ctx-exports.js +54 -0
  12. package/dist/facet-image-store.d.ts +112 -0
  13. package/dist/facet-image-store.d.ts.map +1 -0
  14. package/dist/facet-image-store.js +181 -0
  15. package/dist/fanout-pool.d.ts +223 -0
  16. package/dist/fanout-pool.d.ts.map +1 -0
  17. package/dist/fanout-pool.js +368 -0
  18. package/dist/index.d.ts +26 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +25 -0
  21. package/dist/inner-do-registry.d.ts +41 -0
  22. package/dist/inner-do-registry.d.ts.map +1 -0
  23. package/dist/inner-do-registry.js +51 -0
  24. package/dist/launch-journal.d.ts +170 -0
  25. package/dist/launch-journal.d.ts.map +1 -0
  26. package/dist/launch-journal.js +154 -0
  27. package/dist/launch-pacer.d.ts +173 -0
  28. package/dist/launch-pacer.d.ts.map +1 -0
  29. package/dist/launch-pacer.js +193 -0
  30. package/dist/loader-ledger.d.ts +57 -0
  31. package/dist/loader-ledger.d.ts.map +1 -0
  32. package/dist/loader-ledger.js +91 -0
  33. package/dist/loader-pool.d.ts +315 -0
  34. package/dist/loader-pool.d.ts.map +1 -0
  35. package/dist/loader-pool.js +666 -0
  36. package/dist/process-fabric.d.ts +524 -0
  37. package/dist/process-fabric.d.ts.map +1 -0
  38. package/dist/process-fabric.js +388 -0
  39. package/dist/process-host.d.ts +132 -0
  40. package/dist/process-host.d.ts.map +1 -0
  41. package/dist/process-host.js +444 -0
  42. package/dist/vendor/errors.d.ts +24 -0
  43. package/dist/vendor/errors.d.ts.map +1 -0
  44. package/dist/vendor/errors.js +46 -0
  45. package/dist/vendor/serialize.d.ts +3 -0
  46. package/dist/vendor/serialize.d.ts.map +1 -0
  47. package/dist/vendor/serialize.js +25 -0
  48. package/dist/vendor/types.d.ts +69 -0
  49. package/dist/vendor/types.d.ts.map +1 -0
  50. package/dist/vendor/types.js +4 -0
  51. package/dist/workerd-facet-host.d.ts +207 -0
  52. package/dist/workerd-facet-host.d.ts.map +1 -0
  53. package/dist/workerd-facet-host.js +508 -0
  54. package/dist/ws-hibernation-config.d.ts +73 -0
  55. package/dist/ws-hibernation-config.d.ts.map +1 -0
  56. package/dist/ws-hibernation-config.js +93 -0
  57. package/package.json +62 -0
  58. package/src/alarms.ts +275 -0
  59. package/src/bindings.ts +871 -0
  60. package/src/ctx-exports.ts +77 -0
  61. package/src/facet-image-store.ts +196 -0
  62. package/src/fanout-pool.ts +503 -0
  63. package/src/index.ts +26 -0
  64. package/src/inner-do-registry.ts +58 -0
  65. package/src/launch-journal.ts +229 -0
  66. package/src/launch-pacer.ts +231 -0
  67. package/src/loader-ledger.ts +112 -0
  68. package/src/loader-pool.ts +984 -0
  69. package/src/process-fabric.ts +729 -0
  70. package/src/process-host.ts +566 -0
  71. package/src/vendor/errors.ts +56 -0
  72. package/src/vendor/serialize.ts +37 -0
  73. package/src/vendor/types.ts +75 -0
  74. package/src/workerd-facet-host.ts +694 -0
  75. package/src/ws-hibernation-config.ts +123 -0
@@ -0,0 +1,524 @@
1
+ /**
2
+ * process-fabric.ts — the resident-process scheduler, and the process half of
3
+ * the substrate it runs on.
4
+ *
5
+ * Every long-lived process Nimbus runs — node servers, python/ruby socket
6
+ * servers, an agent TUI and its headless server — runs as a **DO Facet**:
7
+ * a named child actor whose class comes from a dynamic worker, opened by
8
+ * `openResidentFacet` in `workerd-facet-host.ts`.
9
+ *
10
+ * ctx.facets.get(`proc-${pid}`, () => ({
11
+ * class: env.LOADER.get(workerKey, buildConfig)
12
+ * .getDurableObjectClass('NimbusProcess'),
13
+ * }))
14
+ *
15
+ * There is ONE process implementation. What varies is WHOSE `ctx` and `env`
16
+ * that call runs against — the user's own session DO, or a sibling DO acting
17
+ * as a host — and that choice is a single deployment-wide config value read in
18
+ * `process-host.ts`. Nothing here, and nothing above here, branches on
19
+ * which program is running: no spawn site picks its own substrate, and no
20
+ * program name, mode or payload size reaches the selection.
21
+ *
22
+ * What each substrate costs, all of it measured on the production
23
+ * compatibility shape (see `process-host.ts` for the operator-facing
24
+ * version of this table):
25
+ *
26
+ * facet — spawn 8-16 ms warm. Memory independent: its OWN ~208 MiB
27
+ * envelope, identical whether the coordinator holds 0 or 128 MiB,
28
+ * with 1,664 MiB live across 8 facets + parent. CPU SHARED with
29
+ * its siblings, because facets are separate isolates inside one
30
+ * actor thread: awaiting I/O yields that thread completely (a
31
+ * sibling's RPC latency while a facet parks on a socket, on stdin
32
+ * or on an outbound call is indistinguishable from idle) but
33
+ * sustained CPU stalls every sibling for its full duration —
34
+ * a python HTTP server at 32-way saturation held siblings under
35
+ * 1.06 s (p50 231 ms), an attached full-screen TUI held them at the
36
+ * 77 ms idle baseline, and a deliberate 9,956 ms CPU burn stalled
37
+ * them for 9,966 ms.
38
+ * peer — spawn 242-359 ms, because every spawn pays a DO create plus a
39
+ * SQLite open. Memory AND CPU both independent: the process runs
40
+ * in a different workerd process, verified per placement rather
41
+ * than assumed (see `_place` in `process-host.ts`).
42
+ *
43
+ * Both give the process its own SQLite. Neither changes what the process is:
44
+ * the runner, the boot spec, the class name, the writer handshake and the
45
+ * lifecycle contract are the same code either way.
46
+ *
47
+ * The facet's SUPERVISOR binding is minted for the COORDINATOR's doId, so
48
+ * every syscall — VFS read/write, stdout/stderr frames, stdin pump,
49
+ * registerPort, loopback HTTP — lands on the user's session DO wherever the
50
+ * process runs. Because that binding is minted by an actor rather than by a
51
+ * stateless entrypoint, it lives as long as the process does; nothing has to
52
+ * hold a call open to keep it alive.
53
+ *
54
+ * Boot specs
55
+ * ──────────
56
+ * A resident process boots from one of two specs, and in both cases the module
57
+ * map is assembled LAZILY inside the loader's cache-miss callback — so the
58
+ * artifact sources are materialized only when the facet actually starts, and
59
+ * only for as long as the load takes:
60
+ *
61
+ * staged — an embedder-defined stage spec; the registered
62
+ * {@link StagedBootAssembler} fetches the artifact sources
63
+ * (Nimbus's staged artifacts come from ASSETS).
64
+ * code — a generated module map (node / python / ruby runners). Fixed-size
65
+ * module text rides inline; anything sized by the user's disk is
66
+ * named BY VFS PATH and read through the injected disk reader. A
67
+ * ruby server's `ruby+stdlib.wasm` alone is 34.3 MiB and a node
68
+ * facet's disk snapshot reached 44 MB for pi.
69
+ *
70
+ * By-path is what lets a boot spec reach EITHER substrate. Inline, pi's node
71
+ * snapshot serialized to 44,252,709 bytes and died at workerd's 32 MiB RPC
72
+ * ceiling the moment it had to cross to a peer; named by path it sends zero
73
+ * bytes, and the host reads them off the coordinator's own disk through the
74
+ * `ResidentDiskReader` it was given.
75
+ */
76
+ import { z } from 'zod/v4';
77
+ import type { RouteableFacetTarget } from '@nimbus-sh/core/runtime/os-contracts.js';
78
+ /**
79
+ * The class every generated resident runner exports. One name for every
80
+ * runtime: the fabric names it unconditionally, so nothing about which program
81
+ * is running reaches this module.
82
+ */
83
+ export declare const RESIDENT_PROCESS_CLASS = "NimbusProcess";
84
+ /**
85
+ * Runner contract for `startProcess()`. A property of the generated runner,
86
+ * not of placement:
87
+ *
88
+ * lifetime — the call is held open for the process's whole life and settles
89
+ * only at exit (an attached TUI or its held-open server, attached-TTY node).
90
+ * boot — the call returns a boot payload once the process is up and the
91
+ * facet stays resident as the coordinator's named child actor
92
+ * (node servers, the python/ruby socket runners).
93
+ */
94
+ export type StartContract = 'lifetime' | 'boot';
95
+ /**
96
+ * A generated module map. Only bounded, fixed-size module text rides inline;
97
+ * anything whose size is a function of the user's disk is named by VFS path
98
+ * and read when the facet loads, so the bytes are transient rather than
99
+ * resident in the coordinator's heap.
100
+ */
101
+ export declare const ResidentCodeSpecSchema: z.ZodObject<{
102
+ compatibilityDate: z.ZodString;
103
+ compatibilityFlags: z.ZodArray<z.ZodString>;
104
+ mainModule: z.ZodString;
105
+ modules: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
106
+ wasm: z.ZodCustom<ArrayBuffer, ArrayBuffer>;
107
+ }, z.core.$strip>]>>;
108
+ vfsWasmModules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
109
+ vfsTextModules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
110
+ }, z.core.$strip>;
111
+ export type ResidentCodeSpec = z.infer<typeof ResidentCodeSpecSchema>;
112
+ /**
113
+ * The boot-spec union, with the staged arm's payload validated by the
114
+ * embedder's own stage schema. The fabric defines the SHAPE of the union —
115
+ * `staged` boots assemble through the registered {@link StagedBootAssembler},
116
+ * `code` boots through {@link residentLoaderConfig} — but what a stage IS
117
+ * belongs to whoever registered the assembler, so the schema is composed
118
+ * rather than fixed. The embedder parses with this at its RPC trust boundary;
119
+ * the assembler re-validates at use either way.
120
+ */
121
+ export declare function residentBootSpecSchema<Stage extends z.ZodType>(stageSchema: Stage): z.ZodDiscriminatedUnion<[z.ZodObject<{
122
+ kind: z.ZodLiteral<"staged">;
123
+ stage: Stage;
124
+ }, z.core.$strip>, z.ZodObject<{
125
+ kind: z.ZodLiteral<"code">;
126
+ code: z.ZodObject<{
127
+ compatibilityDate: z.ZodString;
128
+ compatibilityFlags: z.ZodArray<z.ZodString>;
129
+ mainModule: z.ZodString;
130
+ modules: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
131
+ wasm: z.ZodCustom<ArrayBuffer, ArrayBuffer>;
132
+ }, z.core.$strip>]>>;
133
+ vfsWasmModules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
134
+ vfsTextModules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
135
+ }, z.core.$strip>;
136
+ }, z.core.$strip>], "kind">;
137
+ export type ResidentBootSpec = {
138
+ kind: 'staged';
139
+ stage: unknown;
140
+ } | {
141
+ kind: 'code';
142
+ code: ResidentCodeSpec;
143
+ };
144
+ /**
145
+ * Assemble a complete Worker Loader config from a staged-artifact spec. The
146
+ * embedder supplies this: a stage names artifact sources only the embedder
147
+ * knows how to fetch (Nimbus's largest staged artifact is a ~23 MB module map from
148
+ * ASSETS), and the assembler runs inside the loader's cache-miss callback so
149
+ * those sources are materialized only while the facet actually loads.
150
+ * `env` is whichever hosting actor's env the facet is opened with.
151
+ */
152
+ export type StagedBootAssembler = (env: unknown, stage: unknown) => Promise<object>;
153
+ /** Registered once at composition time, first-write-wins. */
154
+ export declare function setStagedBootAssembler(assembler: StagedBootAssembler): void;
155
+ export declare function requireStagedBootAssembler(): StagedBootAssembler;
156
+ /**
157
+ * Where a generated module source is materialized so a boot spec can name it.
158
+ *
159
+ * Outside any user working tree on purpose. The passes that build a node
160
+ * facet's snapshot enumerate the process's cwd, so an image written under one
161
+ * would be swept into the next snapshot — and that snapshot is what produced
162
+ * the image, so each spawn would grow the thing it just wrote.
163
+ *
164
+ * Kernel-owned and world-readable: the generator writes as CRED_KERNEL, and
165
+ * every process reads through a supervisor binding that enforces its own
166
+ * credential. Mode 0644 is what makes the read succeed for any process by
167
+ * construction rather than by a privilege carve-out in the permission layer,
168
+ * and leaves the bytes beyond reach of the user whose program they encode.
169
+ */
170
+ export declare const FACET_IMAGE_DIR = "var/lib/nimbus/facet-images";
171
+ /**
172
+ * An image is named by the SHA-256 of its own bytes, so its name IS its
173
+ * integrity check and a stale image is not something to invalidate but
174
+ * something that cannot be addressed: different generated text is a different
175
+ * path.
176
+ *
177
+ * What that actually dedups, measured on a deployed worker rather than
178
+ * assumed: a RESTART resolves to the image already there, because the fabric
179
+ * replays one unchanged boot spec. Two separate spawns of the same tool do
180
+ * NOT, whenever the generated text carries anything per-process: an
181
+ * attached-TTY spawn bakes `NIMBUS_CP_CHILD_PID` into `__NIMBUS_ARGS`, so `pi`
182
+ * twice wrote two images (2c3a90ad… then c5b74f1a…). A spawn with no attached
183
+ * TTY has no pid in its args and does dedup. Lifting argv/env/pid out of the
184
+ * generated text into `startArgs` would make every image per-PROGRAM and
185
+ * shareable across spawns and sessions; the sweep bounds the store either way.
186
+ */
187
+ export declare function facetImageDigest(source: string): Promise<string>;
188
+ export declare function facetImagePath(digest: string): string;
189
+ /**
190
+ * The digest an image path claims, for the reader's verify-on-read. Content
191
+ * addressing only holds if the bytes are checked against the name they were
192
+ * fetched under; without that a truncated or overwritten image boots as
193
+ * silently-wrong code, which in a facet surfaces as an unattributable
194
+ * "Cannot find module" a long way from the corruption.
195
+ */
196
+ export declare function facetImagePathDigest(path: string): string | null;
197
+ /**
198
+ * Reads the members a boot spec named by path off the SESSION's disk — the
199
+ * coordinator's, always, whichever substrate is doing the reading.
200
+ *
201
+ * The session supplies it, because it owns the filesystem and the credential
202
+ * the kernel reads its own image store with; the fabric never learns either.
203
+ * A host that runs inside the coordinator answers synchronously off the local
204
+ * VFS; a host that runs elsewhere answers over the supervisor RPC. That is the
205
+ * whole of the difference, and it is why the return type is widened rather
206
+ * than the reader duplicated.
207
+ */
208
+ export interface ResidentDiskReader {
209
+ readFile(path: string): Uint8Array | Promise<Uint8Array>;
210
+ }
211
+ /**
212
+ * Complete a resident-process module map: read every member the spec named by
213
+ * path, verifying each generated image against the digest its own path claims.
214
+ * Runs inside the loader's cache-miss callback, so the bytes exist only for
215
+ * the duration of the load.
216
+ */
217
+ export declare function residentLoaderConfig(spec: ResidentCodeSpec, disk: ResidentDiskReader): Promise<Record<string, unknown>>;
218
+ /**
219
+ * The identity a resident process's SUPERVISOR binding is minted for. Always
220
+ * the COORDINATOR's — a process hosted somewhere else still reads and writes
221
+ * the user's disk, and still reports to the user's process table.
222
+ */
223
+ export interface ResidentSupervisorProps {
224
+ doId: string;
225
+ pid: number;
226
+ writerId: string;
227
+ }
228
+ /** Everything a host needs to run one process. Substrate-free by construction. */
229
+ export interface ProcessHostParams {
230
+ /** Supervisor-assigned pid of the process entry on the coordinator. */
231
+ pid: number;
232
+ /** Keyed dynamic-worker identity (`nimbus-process:${doId}:${pid}`). */
233
+ workerKey: string;
234
+ /** What the process boots from. */
235
+ boot: ResidentBootSpec;
236
+ /** Binds the facet-local append sequence to this concrete incarnation. */
237
+ writerId: string;
238
+ /** Forwarded verbatim to the runner's startProcess. */
239
+ startArgs: unknown;
240
+ }
241
+ /**
242
+ * One resident process, as its coordinator sees it. Identical in meaning on
243
+ * every substrate — that identity IS the abstraction, so a divergence here is
244
+ * a bug rather than a documented difference.
245
+ */
246
+ export interface HostedProcess {
247
+ /**
248
+ * The runner's startProcess payload. The runner is started as part of
249
+ * opening the host, so this is a handle on that one boot — awaiting it twice
250
+ * is safe and never re-starts anything. A `lifetime` runner settles it at
251
+ * exit; a host that dies before then rejects it.
252
+ */
253
+ readonly started: Promise<unknown>;
254
+ /**
255
+ * Rejects if the HOST dies under a process that is already up — the one
256
+ * failure a substrate can suffer that the process itself never reports.
257
+ *
258
+ * It is not symmetric, and pretending otherwise is what leaks a process. A
259
+ * facet dies only with the Durable Object that owns it, which takes the
260
+ * coordinator and this handle with it, so there is nothing to observe and
261
+ * this never settles; its death shows up at the next use, loudly. A peer can
262
+ * die on its own, the held host leg says so, and throwing that away would
263
+ * leave a `boot`-contract process routing to a corpse until someone killed
264
+ * it by hand.
265
+ */
266
+ readonly lost: Promise<never>;
267
+ /** Inbound HTTP for the process's registered ports. */
268
+ handleHttpRequest(request: Request): Promise<Response>;
269
+ /**
270
+ * Inbound WebSocket upgrade, on fetch semantics for every hop. A 101
271
+ * Response owns a live socket, which the parts-based RPC path cannot carry.
272
+ */
273
+ handleWebSocketRequest(request: Request): Promise<Response>;
274
+ /**
275
+ * Idempotent teardown. Settles only once the process is actually gone —
276
+ * on a remote host that is a round trip, and the writer identity this
277
+ * incarnation holds may not be retired before it completes.
278
+ */
279
+ release(): Promise<void>;
280
+ /** Human-readable placement, for the NIMBUS_DEBUG process-log line. */
281
+ describe(): string;
282
+ }
283
+ /**
284
+ * How a whole session-filesystem image can reach a process on a substrate, and
285
+ * what stops it.
286
+ *
287
+ * This is the one place the two substrates are NOT interchangeable, so it is
288
+ * stated rather than smoothed over. Everything else about a process is the
289
+ * same code either way; this is not, and an operator flipping the config is
290
+ * changing it.
291
+ */
292
+ export interface ProcessImageDelivery {
293
+ /**
294
+ * Whether the hosting actor can hand a process its whole SQLite by
295
+ * copy-on-write, present before the process's first instruction.
296
+ *
297
+ * `same-object` — possible in principle: the host and the source live in one
298
+ * Durable Object, which is the only scope `ctx.facets.clone` works in.
299
+ * Measured on production workerd at 18-31 ms for a 45.73 MB pi-shaped
300
+ * corpus and 34-54 ms for 1 GB — flat across a 256x size range, because
301
+ * nothing is copied.
302
+ * `impossible` — and not for want of an implementation. Clone is
303
+ * same-Durable-Object, bookmarks are same-Durable-Object, and workerd
304
+ * exposes no `VACUUM INTO`, no `ATTACH` and no `sqlite3_backup` to reach
305
+ * across one. A peer-hosted process can only ever receive an image
306
+ * through `moduleCeilingBytes` below, or by streaming it.
307
+ *
308
+ * Reachable in PRODUCTION but not from a type checker or `wrangler dev`, and
309
+ * the difference is worth stating precisely because inferring one from the
310
+ * other is how a wrong claim gets written down. `@cloudflare/workers-types`
311
+ * 4.20260605.1 declares `get`/`abort`/`delete` and no `clone`, and the pinned
312
+ * workerd is 1.20260603.1 — but the deployed runtime is Cloudflare's, not the
313
+ * one wrangler bundles, and there it is present and works: enumerating the
314
+ * binding on a live Worker at this repo's own compatibility_date returns
315
+ * `["abort","clone","constructor","delete","get"]`, and a clone into a
316
+ * destination of a DIFFERENT class had all 500 seeded files readable from the
317
+ * destination's CONSTRUCTOR. No compat-date gate. So calling it is a
318
+ * lockfile-and-types problem, not a platform one.
319
+ *
320
+ * The hazard that comes with it, measured rather than assumed: ANY `src`
321
+ * that does not resolve to a populated facet — a typo, a name not created
322
+ * yet, not merely the obvious `''`/`'.'`/`'/'` — silently EMPTIES the
323
+ * destination and reports success. Validation has to be positive on both
324
+ * ends: the source exists and is populated before, the destination is
325
+ * non-empty after. A blocklist of bad names would pass a typo straight
326
+ * through and wipe a process's filesystem while returning ok. Enforced by
327
+ * `cloneFacetStorage` in the workerd host, which is the one way the fabric
328
+ * calls clone.
329
+ */
330
+ readonly reflink: 'same-object' | 'impossible';
331
+ /**
332
+ * Bytes one process's whole module map may carry — the channel that does
333
+ * work today, on both substrates, because the loader runs on whichever actor
334
+ * hosts the facet. Enforced where the map is assembled, since the loader's
335
+ * own refusal names no member.
336
+ */
337
+ readonly moduleCeilingBytes: number;
338
+ /**
339
+ * Whether the process's SQLite is spent out of the SESSION's storage budget
340
+ * or its own. This cuts the opposite way from `reflink` and is why neither
341
+ * substrate simply wins: a facet shares roughly 10 GiB with the session root
342
+ * and every sibling and clone under it, with no copy-on-write credit — N
343
+ * forks of an X-byte image need X*(N+1) — and crossing it does not raise an
344
+ * error, it resets the object with "Internal error in Durable Object storage
345
+ * caused object to be reset". A peer brings its own budget per host.
346
+ */
347
+ readonly storageSharedWithSession: boolean;
348
+ }
349
+ /**
350
+ * The substrate a resident process runs on. One implementation per hosting
351
+ * mechanism, one selection for the whole deployment — see
352
+ * `process-host.ts`.
353
+ */
354
+ /**
355
+ * A one-shot's module map: every member inline.
356
+ *
357
+ * Deliberately without {@link ResidentCodeSpec}'s by-path members. A resident
358
+ * process names its large members by VFS path because the map has to reach
359
+ * whichever actor ends up hosting it; a one-shot's is assembled and consumed
360
+ * inside a single call, so a path buys nothing and a host that accepted one
361
+ * would be promising a read it never performs.
362
+ */
363
+ export interface OneShotCodeSpec {
364
+ compatibilityDate: string;
365
+ compatibilityFlags: string[];
366
+ mainModule: string;
367
+ modules: Record<string, string | {
368
+ wasm: ArrayBuffer;
369
+ }>;
370
+ }
371
+ /**
372
+ * Everything a host needs to run one program to completion.
373
+ *
374
+ * Separate from {@link ProcessHostParams} because the two differ in whether
375
+ * anything survives the call, and every other difference follows from that: a
376
+ * one-shot has no route target, no independent death to observe and no
377
+ * residency to release. One spec carrying all of it would leave three members
378
+ * meaningless for half its uses.
379
+ */
380
+ export interface OneShotParams {
381
+ /** Supervisor-assigned pid — the identity the callback capability reports as. */
382
+ pid: number;
383
+ /**
384
+ * Binds this run's VFS appends to this concrete incarnation. Supplied by the
385
+ * caller rather than minted here so it can revoke the identity it authorised
386
+ * instead of one it has to read back.
387
+ */
388
+ writerId: string;
389
+ /**
390
+ * The module map, assembled on demand.
391
+ *
392
+ * A thunk, and that is load-bearing rather than stylistic. The map is the
393
+ * largest thing a session builds — pi's is ~23 MB — and it is dead the moment
394
+ * the loader has taken it. Building it inside the load is what keeps it out
395
+ * of the caller's frame, which would otherwise hold a second full copy of the
396
+ * program for as long as the program runs.
397
+ */
398
+ code(): Promise<OneShotCodeSpec>;
399
+ /** The invocation. Its body carries argv/env/cwd; its signal bounds the run. */
400
+ request: Request;
401
+ /**
402
+ * Called before any capability able to write as `writerId` exists, and only
403
+ * if this host can mint one at all. Granting append authority to an identity
404
+ * nothing will ever present would leave a writer live with no writer.
405
+ */
406
+ onWriterActivated(writerId: string): void;
407
+ /**
408
+ * Called once the program is loaded and about to be entered.
409
+ *
410
+ * The boundary between paying for the isolate and paying for the program.
411
+ * They are separate costs with separate fixes — a 12 s exec was once read as
412
+ * a slow load and was a fresh isolate parsing a 23 MB map — and only the host
413
+ * can see where one ends and the other begins.
414
+ */
415
+ onLoaded?(): void;
416
+ }
417
+ export interface ProcessHost {
418
+ /** What this substrate can and cannot deliver, for operators and callers. */
419
+ readonly imageDelivery: ProcessImageDelivery;
420
+ /**
421
+ * Run one program to completion and hand its response to `consume`.
422
+ *
423
+ * Scoped to the call rather than returned, because the isolate that produced
424
+ * the response must outlive the reading of its body. A host that released its
425
+ * stubs before the caller had read would sever a body still streaming, and
426
+ * one that buffered instead would hold a second copy of every result — the
427
+ * cost the thunk above exists to avoid. `consume` runs while the program's
428
+ * resources are still held; they are released as it returns.
429
+ */
430
+ runOnce<T>(params: OneShotParams, consume: (response: Response) => Promise<T>): Promise<T>;
431
+ open(params: ProcessHostParams): Promise<HostedProcess>;
432
+ }
433
+ /**
434
+ * How a caller supplies the substrate a process manager will run programs on.
435
+ *
436
+ * A factory rather than a finished {@link ProcessHost}, because the substrate
437
+ * needs the disk its processes boot from and only the manager can produce one:
438
+ * that reader answers as the credential that WROTE the boot images and
439
+ * deliberately uncached, since they are the largest files a session holds.
440
+ * Demanding a finished host would make every caller reproduce that policy, and
441
+ * a second copy of a credential rule is a second thing to keep in step.
442
+ *
443
+ * The parameters are exactly what a manager already holds, so the deployment's
444
+ * own selector (`processHostFor`) satisfies this type as it stands — the
445
+ * workerd substrate is named, not wrapped.
446
+ */
447
+ export type ProcessHostFactory = (ctx: DurableObjectState, env: unknown, disk: () => ResidentDiskReader) => ProcessHost;
448
+ /**
449
+ * Resource handle for one resident process — the whole surface the kernel
450
+ * above this module sees: `booted()` for the boot payload, `done` for death,
451
+ * `kill()` for teardown, `routeTarget` for inbound HTTP. Substrate-free: the
452
+ * kernel cannot tell from it where the process is running, and never asks.
453
+ *
454
+ * `done` settles when the process ends: for a `lifetime` runner that is its
455
+ * held-open startProcess settling (resolve on exit, reject on host death);
456
+ * for a `boot` runner it is the kill that releases the host.
457
+ *
458
+ * The handle is disposable so FacetManager's existing per-pid resource
459
+ * tracking tears a process down exactly the way it releases any other
460
+ * per-process resource.
461
+ */
462
+ export declare class ResidentProcessHandle {
463
+ #private;
464
+ readonly done: Promise<void>;
465
+ /**
466
+ * Inbound-HTTP target for PortRegistry: the running facet's own stub. A
467
+ * facet is a child actor, so its stub stays usable in request contexts long
468
+ * after the one that created it — which is the whole reason a resident
469
+ * process can serve a port at all.
470
+ */
471
+ readonly routeTarget: RouteableFacetTarget;
472
+ constructor(init: {
473
+ done: Promise<void>;
474
+ booted: () => Promise<unknown>;
475
+ routeTarget: RouteableFacetTarget;
476
+ kill: () => void;
477
+ describe: () => string;
478
+ });
479
+ /**
480
+ * The runner's startProcess payload. The runner is started as part of the
481
+ * spawn, so this is a handle on that one boot — awaiting it twice is safe
482
+ * and never re-starts anything. For a `lifetime` runner it settles at exit.
483
+ */
484
+ booted(): Promise<unknown>;
485
+ get killed(): boolean;
486
+ /** Human-readable placement, for the NIMBUS_DEBUG process-log line. */
487
+ describePlacement(): string;
488
+ /** Idempotent: abort the facet and release its isolate. */
489
+ kill(): void;
490
+ }
491
+ export interface ResidentProcessSpawn {
492
+ /** Declared by the runner the primitive generates. */
493
+ startContract: StartContract;
494
+ /** Supervisor-assigned pid of the process entry on the coordinator. */
495
+ pid: number;
496
+ /** Keyed dynamic-worker identity (`nimbus-process:${doId}:${pid}`). */
497
+ workerKey: string;
498
+ /** What the facet boots from. */
499
+ boot: ResidentBootSpec;
500
+ /** Forwarded verbatim to the runner's startProcess. */
501
+ startArgs?: unknown;
502
+ /**
503
+ * Called before any concrete host capability can expose this writer.
504
+ * A spawn must not proceed unless the supervisor accepts the authority.
505
+ */
506
+ onWriterActivated: (writerId: string) => void;
507
+ /** Called only after the concrete host resources for this writer are revoked. */
508
+ onWriterRetired: (writerId: string) => void;
509
+ }
510
+ export declare class ProcessFabric {
511
+ private readonly host;
512
+ constructor(host: ProcessHost);
513
+ /**
514
+ * Boot a resident process on this deployment's substrate and return its
515
+ * handle. Resolves once the process is up and its runner has been started;
516
+ * rejects on boot failure.
517
+ *
518
+ * There is no decision in here. The substrate was chosen once, for the
519
+ * deployment, and the only thing this method knows about it is the
520
+ * `ProcessHost` interface.
521
+ */
522
+ startResidentProcess(spawn: ResidentProcessSpawn): Promise<ResidentProcessHandle>;
523
+ }
524
+ //# sourceMappingURL=process-fabric.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-fabric.d.ts","sourceRoot":"","sources":["../src/process-fabric.ts"],"names":[],"mappings":"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"}