@net-mesh/sdk 0.19.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.
@@ -0,0 +1,546 @@
1
+ /**
2
+ * Compute surface — `MeshDaemon` + `DaemonRuntime`.
3
+ *
4
+ * Stage 3 of `SDK_COMPUTE_SURFACE_PLAN.md`. Sub-step 1 lands the
5
+ * skeleton: a caller can build a runtime against an existing
6
+ * {@link MeshNode}, register a factory (stored but not yet
7
+ * invoked), start the runtime, and shut it down. Event delivery,
8
+ * migration, and snapshot/restore land in subsequent sub-steps.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { MeshNode, DaemonRuntime } from '@net-mesh/sdk';
13
+ *
14
+ * const mesh = await MeshNode.create({ bindAddr: '127.0.0.1:0', psk: '...' });
15
+ * const rt = DaemonRuntime.create(mesh);
16
+ *
17
+ * // Sub-step 1: register a factory shape the TS side can see.
18
+ * // Sub-step 2+ will actually invoke the returned object on
19
+ * // events delivered by Rust.
20
+ * rt.registerFactory('echo', () => ({
21
+ * name: 'echo',
22
+ * process: (event) => [event.payload],
23
+ * }));
24
+ *
25
+ * await rt.start();
26
+ * // ... daemons would run here (sub-step 3+) ...
27
+ * await rt.shutdown();
28
+ * ```
29
+ */
30
+ import { Identity } from './identity.js';
31
+ import { MeshNode } from './mesh.js';
32
+ /**
33
+ * Base class for daemon-layer errors: factory registration, runtime
34
+ * lifecycle, spawn/stop, migration. The Rust side prefixes every
35
+ * message with `daemon:`; this file peels the prefix and rethrows
36
+ * the typed class so TS callers can `catch (e: DaemonError)`.
37
+ */
38
+ export declare class DaemonError extends Error {
39
+ constructor(message: string);
40
+ }
41
+ /**
42
+ * Stable discriminator for migration-layer failures. Use
43
+ * `err.kind` in catch blocks rather than parsing messages.
44
+ *
45
+ * - `not-ready` — target runtime exists but hasn't called
46
+ * `start()`. Retriable; source auto-retries by default.
47
+ * - `factory-not-found` — target has no factory for the daemon's
48
+ * origin_hash. Terminal — target mis-configured.
49
+ * - `compute-not-supported` — target node is a bare `Mesh` with
50
+ * no `DaemonRuntime`. Terminal.
51
+ * - `state-failed` — snapshot encode/decode or restore failed.
52
+ * Terminal. `detail` carries the underlying message.
53
+ * - `already-migrating` — a migration is already in flight for
54
+ * the same origin_hash. Terminal on the duplicate attempt.
55
+ * - `identity-transport-failed` — envelope signature/unseal
56
+ * failure. Terminal. `detail` carries the underlying message.
57
+ * - `not-ready-timeout` — source exhausted its NotReady-retry
58
+ * budget. Terminal. `attempts` is the retry count.
59
+ * - `daemon-not-found` — orchestrator couldn't locate the daemon
60
+ * on the source node. Carries `originHash`.
61
+ * - `target-unavailable` — target node ID isn't in the source's
62
+ * peer table. Carries `nodeId`.
63
+ * - `wrong-phase` — internal phase-machine violation (shouldn't
64
+ * surface in practice; carries expected + actual phase).
65
+ * - `snapshot-too-large` — snapshot exceeds the transfer limit;
66
+ * carries `size` and `max`.
67
+ */
68
+ export type MigrationErrorKind = 'not-ready' | 'factory-not-found' | 'compute-not-supported' | 'state-failed' | 'already-migrating' | 'identity-transport-failed' | 'not-ready-timeout' | 'daemon-not-found' | 'target-unavailable' | 'wrong-phase' | 'snapshot-too-large' | 'unknown';
69
+ /**
70
+ * Typed migration failure. Subclass of {@link DaemonError} so
71
+ * `catch (e: DaemonError)` still matches; callers who want to
72
+ * discriminate use `e instanceof MigrationError` + `e.kind`.
73
+ *
74
+ * **Retriability:** only `kind === 'not-ready'` is retriable
75
+ * (the source SDK auto-retries on this by default). Everything
76
+ * else is terminal — a caller's own retry loop won't help.
77
+ */
78
+ export declare class MigrationError extends DaemonError {
79
+ readonly kind: MigrationErrorKind;
80
+ /** Number of NotReady retries on `not-ready-timeout`. */
81
+ readonly attempts?: number;
82
+ /** Daemon origin on `daemon-not-found` / `already-migrating`. */
83
+ readonly originHash?: number;
84
+ /** Node ID on `target-unavailable`. */
85
+ readonly nodeId?: bigint;
86
+ /** Size / max on `snapshot-too-large`. */
87
+ readonly size?: number;
88
+ readonly max?: number;
89
+ /** Underlying string detail on `state-failed` / `identity-transport-failed`. */
90
+ readonly detail?: string;
91
+ constructor(kind: MigrationErrorKind, message: string, extras?: {
92
+ attempts?: number;
93
+ originHash?: number;
94
+ nodeId?: bigint;
95
+ size?: number;
96
+ max?: number;
97
+ detail?: string;
98
+ });
99
+ }
100
+ /**
101
+ * Phase a migration is currently in. Order is monotonic:
102
+ * `snapshot` → `transfer` → `restore` → `replay` → `cutover` →
103
+ * `complete`. Once complete (or aborted), the orchestrator drops
104
+ * its record and {@link MigrationHandle.phase} returns `null`.
105
+ */
106
+ export type MigrationPhase = 'snapshot' | 'transfer' | 'restore' | 'replay' | 'cutover' | 'complete';
107
+ /**
108
+ * Options for {@link DaemonRuntime.startMigrationWith}. Omit any
109
+ * field to take the runtime default.
110
+ */
111
+ export interface MigrationOptions {
112
+ /**
113
+ * Seal the daemon's ed25519 seed into the outbound snapshot so
114
+ * the target keeps full signing capability. Default `true`;
115
+ * set `false` for pure compute daemons that only consume events
116
+ * and don't need to sign anything on the target.
117
+ */
118
+ readonly transportIdentity?: boolean;
119
+ /**
120
+ * Retry budget for `NotReady` targets, in milliseconds. Default
121
+ * 30_000 (30 s). Pass `0` to disable retry — the first
122
+ * `NotReady` surfaces as a terminal failure.
123
+ */
124
+ readonly retryNotReadyMs?: bigint;
125
+ }
126
+ /**
127
+ * A causal event delivered to a daemon's `process`. Sub-step 3
128
+ * will plumb this through NAPI; sub-step 1 declares the shape so
129
+ * the factory signature is callable today.
130
+ */
131
+ export interface CausalEvent {
132
+ /** 64-bit origin hash of the emitting entity. */
133
+ readonly originHash: bigint;
134
+ /** Sequence number in the emitter's causal chain. */
135
+ readonly sequence: bigint;
136
+ /** Opaque payload bytes. */
137
+ readonly payload: Buffer;
138
+ }
139
+ /**
140
+ * User-implemented daemon. The object returned by the factory
141
+ * passed to {@link DaemonRuntime.registerFactory}.
142
+ *
143
+ * `process` is synchronous by contract — do not return a Promise.
144
+ * Snapshot/restore are optional; stateless daemons omit them.
145
+ */
146
+ export interface MeshDaemon {
147
+ /** Stable human-readable name. Used only for diagnostics. */
148
+ readonly name: string;
149
+ /**
150
+ * Handle one inbound event. Return zero or more output payloads
151
+ * (buffers); each is wrapped in a fresh causal link by the host.
152
+ *
153
+ * Must be synchronous — the core's `process` contract is sync,
154
+ * and the TSFN bridge in sub-step 3 blocks the calling tokio
155
+ * task until this returns.
156
+ */
157
+ process(event: CausalEvent): Buffer[];
158
+ /** Optional: serialize current state for migration / persistence. */
159
+ snapshot?(): Buffer | null;
160
+ /** Optional: restore state from a snapshot produced by `snapshot`. */
161
+ restore?(state: Buffer): void;
162
+ /**
163
+ * Phase 6 of `CAPABILITY_SYSTEM_SDK_PLAN.md` — hard placement
164
+ * requirements declared at factory time. Drives the substrate's
165
+ * `MeshDaemon::required_capabilities`; missing tags veto
166
+ * placement (`StandardPlacement` returns `None` for any
167
+ * candidate node missing a required tag).
168
+ *
169
+ * Static — captured once when the factory returns; not
170
+ * re-queried per placement decision. Omit for "runs anywhere"
171
+ * defaults.
172
+ *
173
+ * Example:
174
+ * ```ts
175
+ * rt.registerFactory('inference', () => ({
176
+ * name: 'inference',
177
+ * process: (ev) => doWork(ev.payload),
178
+ * requiredCapabilities: {
179
+ * tags: ['hardware.gpu', 'hardware.gpu.vram_gb=24'],
180
+ * },
181
+ * optionalCapabilities: {
182
+ * tags: ['hardware.gpu.vram_gb=80'],
183
+ * },
184
+ * }));
185
+ * ```
186
+ */
187
+ requiredCapabilities?: import('./capabilities').CapabilitySet;
188
+ /**
189
+ * Phase 6 of `CAPABILITY_SYSTEM_SDK_PLAN.md` — soft placement
190
+ * preferences. Factor into per-axis scoring; missing optional
191
+ * tags don't veto placement (unlike `requiredCapabilities`).
192
+ * Omit for "no preferences" default.
193
+ */
194
+ optionalCapabilities?: import('./capabilities').CapabilitySet;
195
+ }
196
+ /** A zero-arg function returning a {@link MeshDaemon} or a Promise of one. */
197
+ export type DaemonFactory = () => MeshDaemon | Promise<MeshDaemon>;
198
+ /**
199
+ * Runtime statistics for a single daemon. Read via
200
+ * {@link DaemonHandle.stats}.
201
+ *
202
+ * All counters are monotonic for the daemon's lifetime. They reset
203
+ * to zero when the daemon is stopped and respawned — the core
204
+ * rebuilds the host, including on {@link DaemonRuntime.spawnFromSnapshot}.
205
+ */
206
+ export interface DaemonStats {
207
+ /** Total events processed since spawn. */
208
+ readonly eventsProcessed: bigint;
209
+ /** Total output events emitted since spawn. */
210
+ readonly eventsEmitted: bigint;
211
+ /** Total processing errors surfaced from `process`. */
212
+ readonly errors: bigint;
213
+ /** Number of snapshots taken (manual + auto combined). */
214
+ readonly snapshotsTaken: bigint;
215
+ }
216
+ /**
217
+ * Host configuration for a daemon. Omit a field to take the
218
+ * runtime default.
219
+ */
220
+ export interface DaemonHostConfig {
221
+ /**
222
+ * Auto-snapshot cadence in events processed. `0` (the default) =
223
+ * manual snapshots only.
224
+ */
225
+ readonly autoSnapshotInterval?: bigint;
226
+ /** Maximum events to buffer before forcing a snapshot. */
227
+ readonly maxLogEntries?: number;
228
+ /**
229
+ * Maximum time (milliseconds) the Rust side will wait for a JS
230
+ * `process` / `snapshot` / `restore` callback to return before
231
+ * surfacing a `DaemonError` with a timeout message. Default
232
+ * `60_000` (60 s).
233
+ *
234
+ * **Why it exists.** The core daemon registry holds a per-daemon
235
+ * mutex across `process`. If a user callback re-enters the runtime
236
+ * synchronously on that same daemon, or the Node main thread is
237
+ * blocked and the TSFN callback can't fire, an unbounded wait
238
+ * would deadlock silently. A bounded wait converts the deadlock
239
+ * into a typed error so the daemon's event becomes one failure
240
+ * instead of a frozen runtime.
241
+ *
242
+ * Set a shorter value (e.g. 500) in tests that intentionally
243
+ * stall the callback and assert the timeout path. Set a longer
244
+ * value for daemons that legitimately do heavy sync work per
245
+ * event.
246
+ */
247
+ readonly callbackTimeoutMs?: number;
248
+ }
249
+ /**
250
+ * Handle to a running daemon. Returned by
251
+ * {@link DaemonRuntime.spawn}; pass its `originHash` back to
252
+ * {@link DaemonRuntime.stop} to tear the daemon down.
253
+ *
254
+ * Cloning the JS object shares the same underlying daemon.
255
+ * Dropping the handle does **not** stop the daemon — callers must
256
+ * call `stop` explicitly.
257
+ */
258
+ export declare class DaemonHandle {
259
+ private readonly inner;
260
+ /**
261
+ * 64-bit hash of the daemon's identity — the key used by the
262
+ * registry, factory registry, and migration dispatcher.
263
+ */
264
+ get originHash(): bigint;
265
+ /**
266
+ * Full 32-byte `EntityId` (ed25519 public key) of the daemon's
267
+ * identity. Returned as a `Buffer` to match the convention used
268
+ * by `Identity.entityId`.
269
+ */
270
+ get entityId(): Buffer;
271
+ /**
272
+ * Current runtime statistics for this daemon. Reads a live
273
+ * atomic snapshot from the registry — cheap enough to poll.
274
+ *
275
+ * Throws {@link DaemonError} if the daemon has been stopped.
276
+ */
277
+ stats(): DaemonStats;
278
+ }
279
+ /**
280
+ * Handle to an in-flight migration. Returned by
281
+ * {@link DaemonRuntime.startMigration} /
282
+ * {@link DaemonRuntime.startMigrationWith}.
283
+ *
284
+ * Dropping the handle does NOT cancel the migration — the
285
+ * orchestrator keeps driving it to completion in the background.
286
+ * Keep the handle to observe phase transitions or request abort.
287
+ */
288
+ export declare class MigrationHandle {
289
+ private readonly inner;
290
+ /** 64-bit origin hash of the daemon being migrated. */
291
+ get originHash(): bigint;
292
+ /** Node ID of the source (currently hosting) node. */
293
+ get sourceNode(): bigint;
294
+ /** Node ID of the target (post-cutover) node. */
295
+ get targetNode(): bigint;
296
+ /**
297
+ * Current migration phase, or `null` once the migration has
298
+ * left the orchestrator's records (terminal success or abort).
299
+ * Callers distinguish success from abort by remembering the
300
+ * last non-null phase they observed.
301
+ */
302
+ phase(): MigrationPhase | null;
303
+ /**
304
+ * Async iterator that yields each distinct migration phase as
305
+ * the orchestrator transitions through them, and terminates
306
+ * cleanly once the migration reaches a terminal state (either
307
+ * `complete` on success, or abort / failure — the orchestrator
308
+ * record is gone either way).
309
+ *
310
+ * **Usage pattern:**
311
+ * ```ts
312
+ * const mig = await rt.startMigration(origin, a, b);
313
+ * const phases: MigrationPhase[] = [];
314
+ * for await (const phase of mig.phases()) {
315
+ * phases.push(phase);
316
+ * }
317
+ * // Inspect `phases.at(-1)` — `'complete'` vs anything else
318
+ * // distinguishes success from abort / failure.
319
+ * ```
320
+ *
321
+ * **Call site ordering:** iterate as soon as the handle is
322
+ * returned. If you await `wait()` first and then call
323
+ * `phases()`, the orchestrator record may already be cleared
324
+ * and the iterator yields nothing.
325
+ *
326
+ * **Sampling cadence:** polls every 50 ms — matching the Rust
327
+ * SDK's `wait()` cadence. Phase transitions faster than that
328
+ * may be missed; acceptable for Stage 1 since real migrations
329
+ * spend hundreds of ms per phase on network round-trips. A
330
+ * broadcast-channel push replacement is documented as future
331
+ * work in `DAEMON_IDENTITY_MIGRATION_PLAN.md`.
332
+ */
333
+ phases(): AsyncGenerator<MigrationPhase, void, void>;
334
+ /**
335
+ * Block until the migration reaches a terminal state. Resolves
336
+ * on `complete`; rejects with {@link DaemonError} on abort or
337
+ * structured failure (target unavailable, restore failed, etc.).
338
+ *
339
+ * No wall-clock timeout — a migration stalled against an
340
+ * unresponsive peer blocks indefinitely. Use
341
+ * {@link MigrationHandle.waitWithTimeout} for a bound.
342
+ */
343
+ wait(): Promise<void>;
344
+ /**
345
+ * Like {@link wait} with a caller-controlled timeout (in
346
+ * milliseconds). On timeout the orchestrator record is aborted
347
+ * and the promise rejects with {@link DaemonError}.
348
+ */
349
+ waitWithTimeout(timeoutMs: bigint): Promise<void>;
350
+ /**
351
+ * Request cancellation of the migration. Best-effort: past
352
+ * `cutover` the routing flip cannot be undone cleanly, and
353
+ * this call resolves without aborting.
354
+ */
355
+ cancel(): Promise<void>;
356
+ }
357
+ /**
358
+ * Per-mesh compute runtime. Holds the kind-keyed factory table and
359
+ * drives the `Registering → Ready → ShuttingDown` lifecycle.
360
+ *
361
+ * Construct via {@link create}; the runtime shares the given mesh's
362
+ * underlying `MeshNode` (no second socket). Shutting down the
363
+ * runtime does NOT shut down the mesh — the caller owns that.
364
+ */
365
+ export declare class DaemonRuntime {
366
+ private readonly inner;
367
+ /**
368
+ * TS-side factory table, keyed by `kind`. `registerFactory`
369
+ * inserts here; `spawn` looks up and invokes. Duplicates the
370
+ * kind set that lives on the NAPI side — the NAPI copy drives
371
+ * migration-targeting and the `already registered` check at
372
+ * registration time; this map is what actually gets *called*.
373
+ */
374
+ private readonly factories;
375
+ private constructor();
376
+ /**
377
+ * Build a compute runtime against an existing {@link MeshNode}.
378
+ */
379
+ static create(mesh: MeshNode): DaemonRuntime;
380
+ /**
381
+ * Promote to `Ready`. Installs the migration subprotocol handler.
382
+ * Idempotent on an already-ready runtime; rejects on a runtime
383
+ * that has been shut down.
384
+ */
385
+ start(): Promise<void>;
386
+ /**
387
+ * Tear down the runtime. Drains daemons, clears factory
388
+ * registrations, uninstalls the migration handler. Idempotent:
389
+ * a second call on an already-shut-down runtime is a no-op.
390
+ */
391
+ shutdown(): Promise<void>;
392
+ /**
393
+ * `true` iff the runtime has transitioned to `Ready` and has not
394
+ * yet begun shutting down.
395
+ */
396
+ isReady(): boolean;
397
+ /** Number of daemons currently registered with the runtime. */
398
+ daemonCount(): number;
399
+ /**
400
+ * Register a factory closure under `kind`. The factory returns a
401
+ * {@link MeshDaemon}-shaped object. Second registration of the
402
+ * same `kind` throws {@link DaemonError}.
403
+ *
404
+ * Sub-step 1 stores the factory but does not invoke it — event
405
+ * dispatch to daemon `process` lands in sub-step 3.
406
+ *
407
+ * ## Migration targeting
408
+ *
409
+ * `registerFactory` alone is **not sufficient** to accept
410
+ * inbound migrations — it registers the kind-to-factory mapping
411
+ * only on the SDK side. Migrations lookup by `origin_hash`, not
412
+ * by kind. Future sub-steps will surface `expectMigration` and
413
+ * `registerMigrationTargetIdentity` for that wiring.
414
+ */
415
+ registerFactory(kind: string, factory: DaemonFactory): void;
416
+ /**
417
+ * Spawn a daemon of `kind` under the given {@link Identity}.
418
+ *
419
+ * Invokes the user-supplied factory (registered via
420
+ * {@link DaemonRuntime.registerFactory}), extracts the
421
+ * returned daemon's `process` / `snapshot` / `restore`
422
+ * methods, and hands each to NAPI as a separate JS function.
423
+ * NAPI builds a `ThreadsafeFunction` per method so the
424
+ * eventual event-dispatch path (sub-step 3) can call them
425
+ * from any tokio task.
426
+ *
427
+ * **Sub-step 2b** (current): method TSFNs are stored on the
428
+ * Rust side but **not yet invoked**. `process` / `snapshot` /
429
+ * `restore` behave as no-ops. Sub-step 3 wires the full
430
+ * round-trip so events land in the JS daemon.
431
+ *
432
+ * `kind` must have been registered first — spawning an
433
+ * unregistered kind throws {@link DaemonError}.
434
+ */
435
+ spawn(kind: string, identity: Identity, config?: DaemonHostConfig): Promise<DaemonHandle>;
436
+ /**
437
+ * Spawn a daemon of `kind` from a previously-taken snapshot.
438
+ * Parallel to {@link DaemonRuntime.spawn} but seeds the
439
+ * daemon's initial state from `snapshotBytes` by calling its
440
+ * `restore` method before any events land.
441
+ *
442
+ * `snapshotBytes` must be the exact `Buffer` returned by a
443
+ * prior call to {@link DaemonRuntime.snapshot}; mismatched or
444
+ * corrupted bytes surface as `daemon: snapshot decode failed`.
445
+ *
446
+ * `kind` must be registered and the caller's {@link Identity}
447
+ * must match the snapshot's `entityId` — a mismatch throws
448
+ * {@link DaemonError} before any side effects.
449
+ */
450
+ spawnFromSnapshot(kind: string, identity: Identity, snapshotBytes: Buffer, config?: DaemonHostConfig): Promise<DaemonHandle>;
451
+ /**
452
+ * Take a snapshot of a running daemon by `originHash`. Returns
453
+ * the daemon's serialized state bytes, or `null` if the daemon
454
+ * is stateless (no `snapshot` method, or it returned `null`).
455
+ *
456
+ * The returned `Buffer` is opaque to the caller — the wire
457
+ * format is the core's `StateSnapshot` encoding, including
458
+ * version headers and the chain link at the snapshot point.
459
+ * Feed it unchanged to {@link DaemonRuntime.spawnFromSnapshot}
460
+ * to restore the daemon on another node or after a restart.
461
+ */
462
+ snapshot(originHash: bigint): Promise<Buffer | null>;
463
+ /**
464
+ * Stop a daemon, removing it from the runtime's registry.
465
+ * Idempotent during `ShuttingDown`; rejects with
466
+ * {@link DaemonError} during `Registering` or when the origin
467
+ * is unknown.
468
+ */
469
+ stop(originHash: bigint): Promise<void>;
470
+ /**
471
+ * Deliver a single causal event to a live daemon and return
472
+ * the daemon's output buffers. Routes through the core
473
+ * `DaemonRegistry::deliver` → `MeshDaemon::process` path,
474
+ * which invokes the JS `process(event)` callback registered
475
+ * at spawn time and waits for its return.
476
+ *
477
+ * Direct ingress — Stage 1 convenience. Mesh-dispatched
478
+ * delivery (via the causal subprotocol on an inbound packet)
479
+ * lands in a later stage; this method stays as test sugar + a
480
+ * manual-trigger surface.
481
+ *
482
+ * Throws {@link DaemonError} if `originHash` doesn't match a
483
+ * live daemon, if the daemon's `process` throws, or if the
484
+ * runtime is shutting down.
485
+ */
486
+ deliver(originHash: bigint, event: CausalEvent): Promise<Buffer[]>;
487
+ /**
488
+ * Initiate a migration for the daemon identified by
489
+ * `originHash`, moving it from `sourceNode` to `targetNode`.
490
+ *
491
+ * Returns a {@link MigrationHandle} whose `wait()` resolves
492
+ * when the migration reaches a terminal state. On local-source
493
+ * migrations (`sourceNode === mesh.nodeId`) the snapshot is
494
+ * taken synchronously inside this call; on remote-source
495
+ * migrations the orchestrator drives the state machine via
496
+ * inbound wire messages.
497
+ *
498
+ * Both node IDs are `u64` — pass as `bigint` to avoid silent
499
+ * precision loss past 2^53.
500
+ */
501
+ startMigration(originHash: bigint, sourceNode: bigint, targetNode: bigint): Promise<MigrationHandle>;
502
+ /**
503
+ * {@link startMigration} with caller-supplied options. Use this
504
+ * to opt out of identity transport (when the daemon doesn't
505
+ * need to sign on the target) or to tune the NotReady-retry
506
+ * budget.
507
+ */
508
+ startMigrationWith(originHash: bigint, sourceNode: bigint, targetNode: bigint, opts: MigrationOptions): Promise<MigrationHandle>;
509
+ /**
510
+ * Declare that a migration will land on this node for the given
511
+ * `originHash` of `kind`. Registers a placeholder factory; the
512
+ * migration snapshot's identity envelope supplies the real
513
+ * keypair at restore time.
514
+ *
515
+ * Must be called BEFORE the source initiates the migration —
516
+ * the target dispatcher checks for a factory entry when the
517
+ * inbound `SnapshotReady` lands, and rejects with
518
+ * `FactoryNotFound` if nothing is registered.
519
+ *
520
+ * The source must migrate with `transportIdentity: true`
521
+ * (default). Without the envelope the dispatcher emits
522
+ * `IdentityTransportFailed` because the placeholder has no
523
+ * keypair. Use {@link registerMigrationTargetIdentity} for the
524
+ * explicit public-identity-migration case.
525
+ */
526
+ expectMigration(kind: string, originHash: bigint, config?: DaemonHostConfig): void;
527
+ /**
528
+ * Pre-register a target-side identity for a migration that
529
+ * will NOT carry an identity envelope (source used
530
+ * `transportIdentity: false`). The target holds the matching
531
+ * {@link Identity}; the dispatcher restores the daemon with
532
+ * that identity instead of overriding it from an envelope.
533
+ *
534
+ * For the common envelope-transport case, prefer
535
+ * {@link expectMigration} — the caller doesn't need to know
536
+ * the daemon's private key ahead of time.
537
+ */
538
+ registerMigrationTargetIdentity(kind: string, identity: Identity, config?: DaemonHostConfig): void;
539
+ /**
540
+ * Query the orchestrator's current migration phase for
541
+ * `originHash`, or `null` if no migration is in flight for
542
+ * that origin. Works on any node — source, target, or an
543
+ * observer that heard the migration on the mesh.
544
+ */
545
+ migrationPhase(originHash: bigint): MigrationPhase | null;
546
+ }