@sema-agent/settings-schema 8.0.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fleet.d.ts CHANGED
@@ -18,6 +18,42 @@
18
18
  * the skeleton does not move).
19
19
  */
20
20
  import { z } from "zod";
21
+ /**
22
+ * The center's `/api/fleet/*` endpoints that are addressed from outside the center, relative to its origin. ONE spelling: whoever
23
+ * calls one (the engine, the executor) and whoever serves or routes it (the center, the deployment's Service / Ingress) imports it
24
+ * here — nobody writes the string. Membership rule: an endpoint is here iff this package types its body, or it belongs to the
25
+ * liveness group ({@link FLEET_LIVE_PATHS}); a center-internal route is not.
26
+ * - `instances` — `POST` = the heartbeat ({@link InstanceAnnouncement}; its 2xx reply carries the center's
27
+ * {@link CenterCapabilities} under {@link HEARTBEAT_REPLY_CAPABILITIES_KEY}), `DELETE` = the goodbye ({@link InstanceDeregistration}).
28
+ * - `usage` — `POST` a {@link UsageReport}.
29
+ * - `quotaLeases` — the quota lease ({@link QuotaLease}).
30
+ * - `servingLease` — `POST` a {@link ServingLeaseRequest}, answered with a {@link ServingLeaseResponse}.
31
+ * - `hostPlanner` — `GET` one host's planner record, for the executor (its body is the center's and the executor's contract, not
32
+ * this package's; it is here because the deployment routes it as a member of the liveness group).
33
+ * Notation: a value is a path TEMPLATE — a parameter is a whole segment written `{name}` (`hostPlanner` ends in `/{host}`, filled
34
+ * with the URL-encoded host id); the other values have none and are the path itself. A prefix router (an Ingress rule) takes the
35
+ * template up to its first `{`.
36
+ */
37
+ export declare const FLEET_PATHS: {
38
+ readonly instances: "/api/fleet/instances";
39
+ readonly usage: "/api/fleet/usage";
40
+ readonly quotaLeases: "/api/fleet/leases";
41
+ readonly servingLease: "/api/fleet/live/serving-lease";
42
+ readonly hostPlanner: "/api/fleet/live/host-planner/{host}";
43
+ };
44
+ /**
45
+ * The LIVENESS group — the fleet endpoints a center answers without its readiness gate and without its config-governance gates
46
+ * (they read no config domain and take service credentials only), so that a center whose management face is refused never turns
47
+ * into a fleet that gets no answers. A deployment routes exactly these past the readiness gate (on Kubernetes: a second Service
48
+ * with `publishNotReadyAddresses`, and the Ingress rules for them point at it). Membership is THIS list, not a path prefix — the
49
+ * heartbeat predates the `/api/fleet/live/` prefix; every {@link FLEET_PATHS} entry under that prefix is a member. The
50
+ * quota-lease and usage endpoints are not.
51
+ *
52
+ * The same two constants ship as JSON for tooling that cannot import TypeScript (a Helm chart, a smoke script): the subpath
53
+ * `@sema-agent/settings-schema/fleet-paths.json` is `{ "FLEET_PATHS": …, "FLEET_LIVE_PATHS": … }`, written from these values at
54
+ * build time — never edited by hand.
55
+ */
56
+ export declare const FLEET_LIVE_PATHS: readonly ["/api/fleet/instances", "/api/fleet/live/serving-lease", "/api/fleet/live/host-planner/{host}"];
21
57
  /** Lifecycle of a draining instance. `running`→`draining` on the stop signal; `drained` once in-flight hits
22
58
  * zero (clean) or the grace deadline forces the issue. Exit always follows `drained`. */
23
59
  export declare const DRAIN_PHASES: readonly ["running", "draining", "drained"];
@@ -45,13 +81,37 @@ export type DrainVerdict = "wait" | "exit_clean" | "exit_forced";
45
81
  /** The drain state machine's single decision point: keep waiting, exit clean (drained), or force the exit
46
82
  * (grace exhausted with legs still in flight — durable legs resume cross-instance via resumeFromRunId). */
47
83
  export declare function drainVerdict(s: DrainSnapshot): DrainVerdict;
84
+ export declare function isCanonicalGeneration(value: unknown): value is string;
85
+ /** Numeric order of two canonical generations: `-1` when `a` is older than `b`, `0` when equal, `1` when newer — `"9"` is
86
+ * older than `"10"`; any number of digits. Throws a `TypeError` on an input that is not canonical (a comparison that quietly
87
+ * returned some number would be a fence decision made on garbage); judge untrusted input with {@link isCanonicalGeneration}
88
+ * first — the wire shapes of this file already do. */
89
+ export declare function compareGeneration(a: string, b: string): -1 | 0 | 1;
90
+ /**
91
+ * The serving lease as the ENGINE holds it — a CLOSED set of four words, owned here (the engine's vocabulary, word for word;
92
+ * a reader that switches on it exhaustively is told by the compiler when a word is added):
93
+ * - `none` — the lease is not in effect: the center has not announced {@link CenterCapabilities}`.servingLease` (an older or
94
+ * degraded center). The engine behaves exactly as an engine without a lease — no renewals, no fence.
95
+ * - `granted` — the engine holds a lease; `leaseUntil` says until when (epoch ms, the engine's own computation from the
96
+ * granted `ttlMs`, see {@link ServingLeaseResponse}).
97
+ * - `expired` — the lease is in effect but no grant is valid right now (the center is unreachable, answered `unknown`, or has
98
+ * not granted one yet): the engine keeps serving until its own outage allowance runs out. Not a denial.
99
+ * - `denied` — the center denied this instance on positive evidence (the `denied` verdict): sticky; the engine is draining and
100
+ * stops at its deadline.
101
+ * Only a fleet member says it: an engine without a generation has no lease and carries no `leaseState` at all (there is no
102
+ * fifth word for "not a member" — absence is that).
103
+ */
104
+ export declare const LEASE_STATES: readonly ["none", "granted", "expired", "denied"];
105
+ export declare const LeaseState: z.ZodEnum<["none", "granted", "expired", "denied"]>;
106
+ export type LeaseState = z.infer<typeof LeaseState>;
48
107
  /**
49
108
  * "This instance must restart to apply config" — the engine's OWN observation (S-741), defined ONCE for both exits that
50
109
  * carry it: the fleet heartbeat ({@link InstanceAnnouncement}`.restart`, typed) and GET /health (`restart`, read through
51
110
  * {@link readRestartSignal} — {@link InstanceHealth} keeps carrying it untyped, see there). One mint point, two exits —
52
111
  * never a second judgement.
53
112
  *
54
- * Shape = what the server already ships on /health (`restart: { reasons, version, since }`). PRESENCE is the signal:
113
+ * Shape = what the server already ships on /health (`restart: { reasons, version, since }`, plus the optional `withheld`
114
+ * qualifier below). PRESENCE is the signal:
55
115
  * present ⇔ a restart is required, absent ⇔ not; there is deliberately no `required` boolean inside (a boolean next
56
116
  * to presence is redundant, and two spellings of one fact drift).
57
117
  * - `reasons` — which restart-to-apply slices differ from the value baked at boot (the vocabulary is the engine's;
@@ -60,19 +120,26 @@ export declare function drainVerdict(s: DrainSnapshot): DrainVerdict;
60
120
  * later publishes, so a "one restart per version" dedupe bounds restarts per config version, not per reason set).
61
121
  * Judged exactly as `readEffectiveWire` judges a config version (a finite number) — one quantity, one rule.
62
122
  * - `since` — epoch ms when this restart first became required (stable across refreshes while `reasons` holds).
123
+ * - `withheld` — optional: the ENGINE's own verdict that this beat must not be handed to a new generation ("a restart is
124
+ * required, but replacing this instance right now would lose state it holds"). Absent ≡ `false` — an explicit `false`
125
+ * on the wire reads the same as the key being absent, and a reader never mints the key when the producer did not send
126
+ * it. It is a qualifier INSIDE the restart signal, never a signal of its own: it exists only where `restart` does.
63
127
  */
64
128
  export declare const RestartSignal: z.ZodObject<{
65
129
  reasons: z.ZodArray<z.ZodString, "many">;
66
130
  version: z.ZodNumber;
67
131
  since: z.ZodNumber;
132
+ withheld: z.ZodOptional<z.ZodBoolean>;
68
133
  }, "strip", z.ZodTypeAny, {
69
134
  reasons: string[];
70
135
  version: number;
71
136
  since: number;
137
+ withheld?: boolean | undefined;
72
138
  }, {
73
139
  reasons: string[];
74
140
  version: number;
75
141
  since: number;
142
+ withheld?: boolean | undefined;
76
143
  }>;
77
144
  export type RestartSignal = z.infer<typeof RestartSignal>;
78
145
  /**
@@ -211,7 +278,11 @@ export declare const InstanceHealth: z.ZodObject<{
211
278
  drainingSince: z.ZodOptional<z.ZodNumber>;
212
279
  }, z.ZodTypeAny, "passthrough">>;
213
280
  export type InstanceHealth = z.infer<typeof InstanceHealth>;
214
- /** Readiness = the routability predicate (k8s readiness probe, gateway health filter, shell seam switch). */
281
+ /** Readiness = the routability predicate (k8s readiness probe, gateway health filter, shell seam switch).
282
+ * Its input is the boolean-`draining` dialect of {@link InstanceHealth}. An {@link EngineSignal} types `draining` as an
283
+ * object, so it does not typecheck here (on purpose: `!== true` would read an object-valued `draining` as "ready").
284
+ * The next major retires the boolean pair (`draining` / `drainingSince`) and gives both this predicate and
285
+ * {@link isRoutable} `EngineSignal` as their input. */
215
286
  export declare function isReadyForTraffic(h: Pick<InstanceHealth, "draining">): boolean;
216
287
  /** Heartbeat cadence the announcement declares (center judges staleness against the DECLARED cadence, so a
217
288
  * slow-heartbeat worker is not falsely reaped). */
@@ -222,7 +293,10 @@ export declare const STALE_HEARTBEAT_MULTIPLIER = 2;
222
293
  /** Register/heartbeat upsert (worker → center, D2: worker-active). ONE shape for both — registration is the
223
294
  * first heartbeat; every heartbeat re-announces the full row (idempotent upsert, no drift between a
224
295
  * "register" and a "renew" schema). `draining:true` is the instance SELF-removing from the routable set the
225
- * moment the stop signal lands — faster than any observer. */
296
+ * moment the stop signal lands — faster than any observer.
297
+ * Scope: the body carries NO `scope` — this shape has no such key, and one sent anyway is stripped. The row lands in the scope
298
+ * the CREDENTIAL names: under a per-worker token, the scope the token binds (a `?scope=` query parameter, when given, must equal
299
+ * it); under the shared token, the `?scope=` query parameter, absent ⇒ `"global"`. */
226
300
  export declare const InstanceAnnouncement: z.ZodObject<{
227
301
  instanceId: z.ZodString;
228
302
  /** worker (deployment) this instance belongs to — the dispatch key */
@@ -242,14 +316,17 @@ export declare const InstanceAnnouncement: z.ZodObject<{
242
316
  reasons: z.ZodArray<z.ZodString, "many">;
243
317
  version: z.ZodNumber;
244
318
  since: z.ZodNumber;
319
+ withheld: z.ZodOptional<z.ZodBoolean>;
245
320
  }, "strip", z.ZodTypeAny, {
246
321
  reasons: string[];
247
322
  version: number;
248
323
  since: number;
324
+ withheld?: boolean | undefined;
249
325
  }, {
250
326
  reasons: string[];
251
327
  version: number;
252
328
  since: number;
329
+ withheld?: boolean | undefined;
253
330
  }>>;
254
331
  /** S-768: present ⇔ this instance has at least one reportable model refusal — the SAME {@link ModelRefusalsWire} /health
255
332
  * carries, minted by the same getter; read it with {@link readModelRefusals}`(announcement)` (never off this field
@@ -277,6 +354,20 @@ export declare const InstanceAnnouncement: z.ZodObject<{
277
354
  entries: Record<string, string>;
278
355
  plane?: string | undefined;
279
356
  }>>>;
357
+ /** Fleet membership and the lease reading, flat on the heartbeat body under the names and values {@link EngineSignal} gives
358
+ * them (one mint point on the engine; this shape reuses that shape's schemas, not copies):
359
+ * - `generation` — the canonical decimal generation ({@link isCanonicalGeneration}); present ⇔ the instance is a fleet member.
360
+ * - `leaseState` — {@link LeaseState}; present ⇔ fleet member.
361
+ * - `leaseUntil` — epoch ms until which the held lease runs; present ⇔ `leaseState` is `granted`.
362
+ * The presence law between the three is the engine's; this shape does not judge it across keys. Every heartbeat re-announces
363
+ * the full row, so a later heartbeat without a key means the engine no longer says it (the stored row is replaced, never merged).
364
+ * Failure direction = meaning direction: these are evidence a center keeps, never routing input — a value that breaks its schema
365
+ * drops that ONE key to ABSENT (`.catch`) and the heartbeat is still accepted, so a lease word a newer engine adds can never make
366
+ * an older center refuse every heartbeat of that engine. A dropped key reads exactly like an unsaid one; the key may then be
367
+ * present with the value `undefined`. */
368
+ generation: z.ZodCatch<z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>>;
369
+ leaseState: z.ZodCatch<z.ZodOptional<z.ZodEnum<["none", "granted", "expired", "denied"]>>>;
370
+ leaseUntil: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
280
371
  }, "strip", z.ZodTypeAny, {
281
372
  draining: boolean;
282
373
  version: string;
@@ -289,11 +380,15 @@ export declare const InstanceAnnouncement: z.ZodObject<{
289
380
  reasons: string[];
290
381
  version: number;
291
382
  since: number;
383
+ withheld?: boolean | undefined;
292
384
  } | undefined;
293
385
  modelRefusals?: {
294
386
  entries: Record<string, string>;
295
387
  plane?: string | undefined;
296
388
  } | undefined;
389
+ generation?: string | undefined;
390
+ leaseState?: "none" | "granted" | "expired" | "denied" | undefined;
391
+ leaseUntil?: number | undefined;
297
392
  }, {
298
393
  version: string;
299
394
  instanceId: string;
@@ -305,9 +400,13 @@ export declare const InstanceAnnouncement: z.ZodObject<{
305
400
  reasons: string[];
306
401
  version: number;
307
402
  since: number;
403
+ withheld?: boolean | undefined;
308
404
  } | undefined;
309
405
  modelRefusals?: unknown;
310
406
  heartbeatMs?: number | undefined;
407
+ generation?: unknown;
408
+ leaseState?: unknown;
409
+ leaseUntil?: unknown;
311
410
  }>;
312
411
  export type InstanceAnnouncement = z.infer<typeof InstanceAnnouncement>;
313
412
  /** Explicit goodbye (stop signal handler) — removes the row immediately instead of waiting out staleness. */
@@ -346,14 +445,17 @@ export declare const InstanceRegistration: z.ZodObject<{
346
445
  reasons: z.ZodArray<z.ZodString, "many">;
347
446
  version: z.ZodNumber;
348
447
  since: z.ZodNumber;
448
+ withheld: z.ZodOptional<z.ZodBoolean>;
349
449
  }, "strip", z.ZodTypeAny, {
350
450
  reasons: string[];
351
451
  version: number;
352
452
  since: number;
453
+ withheld?: boolean | undefined;
353
454
  }, {
354
455
  reasons: string[];
355
456
  version: number;
356
457
  since: number;
458
+ withheld?: boolean | undefined;
357
459
  }>>;
358
460
  /** S-768: present ⇔ this instance has at least one reportable model refusal — the SAME {@link ModelRefusalsWire} /health
359
461
  * carries, minted by the same getter; read it with {@link readModelRefusals}`(announcement)` (never off this field
@@ -381,6 +483,20 @@ export declare const InstanceRegistration: z.ZodObject<{
381
483
  entries: Record<string, string>;
382
484
  plane?: string | undefined;
383
485
  }>>>;
486
+ /** Fleet membership and the lease reading, flat on the heartbeat body under the names and values {@link EngineSignal} gives
487
+ * them (one mint point on the engine; this shape reuses that shape's schemas, not copies):
488
+ * - `generation` — the canonical decimal generation ({@link isCanonicalGeneration}); present ⇔ the instance is a fleet member.
489
+ * - `leaseState` — {@link LeaseState}; present ⇔ fleet member.
490
+ * - `leaseUntil` — epoch ms until which the held lease runs; present ⇔ `leaseState` is `granted`.
491
+ * The presence law between the three is the engine's; this shape does not judge it across keys. Every heartbeat re-announces
492
+ * the full row, so a later heartbeat without a key means the engine no longer says it (the stored row is replaced, never merged).
493
+ * Failure direction = meaning direction: these are evidence a center keeps, never routing input — a value that breaks its schema
494
+ * drops that ONE key to ABSENT (`.catch`) and the heartbeat is still accepted, so a lease word a newer engine adds can never make
495
+ * an older center refuse every heartbeat of that engine. A dropped key reads exactly like an unsaid one; the key may then be
496
+ * present with the value `undefined`. */
497
+ generation: z.ZodCatch<z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>>;
498
+ leaseState: z.ZodCatch<z.ZodOptional<z.ZodEnum<["none", "granted", "expired", "denied"]>>>;
499
+ leaseUntil: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
384
500
  } & {
385
501
  registeredMs: z.ZodNumber;
386
502
  lastSeenMs: z.ZodNumber;
@@ -399,11 +515,15 @@ export declare const InstanceRegistration: z.ZodObject<{
399
515
  reasons: string[];
400
516
  version: number;
401
517
  since: number;
518
+ withheld?: boolean | undefined;
402
519
  } | undefined;
403
520
  modelRefusals?: {
404
521
  entries: Record<string, string>;
405
522
  plane?: string | undefined;
406
523
  } | undefined;
524
+ generation?: string | undefined;
525
+ leaseState?: "none" | "granted" | "expired" | "denied" | undefined;
526
+ leaseUntil?: number | undefined;
407
527
  observedReady?: boolean | undefined;
408
528
  }, {
409
529
  version: string;
@@ -418,18 +538,438 @@ export declare const InstanceRegistration: z.ZodObject<{
418
538
  reasons: string[];
419
539
  version: number;
420
540
  since: number;
541
+ withheld?: boolean | undefined;
421
542
  } | undefined;
422
543
  modelRefusals?: unknown;
423
544
  heartbeatMs?: number | undefined;
545
+ generation?: unknown;
546
+ leaseState?: unknown;
547
+ leaseUntil?: unknown;
424
548
  observedReady?: boolean | undefined;
425
549
  }>;
426
550
  export type InstanceRegistration = z.infer<typeof InstanceRegistration>;
427
551
  /** Silent past the declared cadence × multiplier ⇒ stale (auto-removed from the routable set). */
428
552
  export declare function isInstanceStale(r: Pick<InstanceRegistration, "lastSeenMs" | "heartbeatMs">, nowMs: number): boolean;
429
553
  /** Health fusion: routable ⇔ self-declared ready ∧ not draining ∧ not stale ∧ observer does not veto.
430
- * Two sources merge as an AND — EITHER source's "not ready" removes the instance (fail-closed). */
554
+ * Two sources merge as an AND — EITHER source's "not ready" removes the instance (fail-closed).
555
+ * Its input is the stored heartbeat row (boolean `draining`); it moves to {@link EngineSignal} in the same major as
556
+ * {@link isReadyForTraffic}. */
431
557
  export declare function isRoutable(r: InstanceRegistration, nowMs: number): boolean;
432
558
  export declare function routableInstances(rs: InstanceRegistration[], nowMs: number): InstanceRegistration[];
559
+ /**
560
+ * The engine's draining fact — ONE object wherever the engine says "I am draining": inside {@link EngineSignal}`.draining`
561
+ * and as the body of a {@link DrainReceipt}.
562
+ * - `actionId` — the action that put the engine into draining (idempotency key of the drain request).
563
+ * - `since` — epoch ms when draining began; it does not move when the same action repeats.
564
+ * - `deadlineAt` — epoch ms by which the engine stops on its own.
565
+ */
566
+ export declare const EngineDraining: z.ZodObject<{
567
+ actionId: z.ZodString;
568
+ since: z.ZodNumber;
569
+ deadlineAt: z.ZodNumber;
570
+ }, "strip", z.ZodTypeAny, {
571
+ since: number;
572
+ actionId: string;
573
+ deadlineAt: number;
574
+ }, {
575
+ since: number;
576
+ actionId: string;
577
+ deadlineAt: number;
578
+ }>;
579
+ export type EngineDraining = z.infer<typeof EngineDraining>;
580
+ /**
581
+ * Everything the ENGINE itself mints about its own state, as one object — the closed form of the engine-owned subset of the
582
+ * /health self-description ({@link InstanceHealth} is that self-description: an open record, `.passthrough()`, which also
583
+ * carries what the host adds — build, pid, port, store posture). One mint point on the engine; {@link readEngineSignal} reads
584
+ * this shape off a body whose keys sit flat on it (no wrapper key).
585
+ *
586
+ * Where it rides today: on NEITHER exit whole. The heartbeat ({@link InstanceAnnouncement}) and `/health` ({@link InstanceHealth})
587
+ * still speak the 8.0 dialect for the keys they share with this shape, in three ways:
588
+ * 1. `draining` is a BOOLEAN there, not the {@link EngineDraining} object. The heartbeat always carries it (`false` included);
589
+ * `/health` carries `draining: true` only while draining.
590
+ * 2. `/health` carries `ready` only while NOT ready (`ready: false`); a ready engine's `/health` body has no `ready` key.
591
+ * 3. The engine writes `configTargetVersion` / `configAppliedVersion` as numbers; this shape types them as strings.
592
+ * So `readEngineSignal` over a heartbeat body is always `undefined` (point 1), and over a `/health` body it is `undefined` whenever
593
+ * any of the three applies. What does ride both exits flat, under these names and with these values: `restart`, `instanceId`
594
+ * and the fleet-membership keys `generation` / `leaseState` / `leaseUntil` — on the heartbeat, read them off the parsed
595
+ * {@link InstanceAnnouncement} (which types all five). The next major retires the boolean pair (`draining` / `drainingSince`),
596
+ * has both exits carry this shape whole and moves {@link isReadyForTraffic} / {@link isRoutable} onto it; until then this is
597
+ * the target form, and `readEngineSignal` is the reader for a body that already speaks it.
598
+ *
599
+ * - `ready` — always said (never "absent while ready"): the engine can take new work right now.
600
+ * - `draining` — present ⇔ draining ({@link EngineDraining}). NOTE the dialect: {@link InstanceHealth}`.draining` and
601
+ * {@link InstanceAnnouncement}`.draining` are BOOLEANS (the 8.0 dialect, kept untouched); here it is an object. A body
602
+ * whose `draining` is a boolean is therefore not an `EngineSignal` and reads as `undefined`.
603
+ * - `restart` — the one {@link RestartSignal}, same schema object, no copy.
604
+ * - `generation` — echo of the env the config renderer set on this instance ({@link FLEET_GENERATION_ENV}), a canonical
605
+ * decimal string ({@link isCanonicalGeneration}). Absent ⇔ not a fleet member (a single-machine engine): it takes no lease
606
+ * and is not fenced.
607
+ * - `instanceId` — the engine's OWN process identity, minted by the engine every time the process starts (never read from
608
+ * env: replicas of one Deployment share one env, and a restarted container reuses its env, so env cannot tell two
609
+ * processes apart). It is present on a standalone engine too — only `generation` marks fleet membership.
610
+ * - `leaseState` — the serving lease as the engine holds it ({@link LeaseState}: `none` / `granted` / `expired` / `denied`);
611
+ * present ⇔ fleet member (the engine says it on every beat once it has a generation).
612
+ * - `leaseUntil` — epoch ms until which the engine holds its serving lease, as the engine computed it from the last
613
+ * `ttlMs` it was granted ({@link ServingLeaseResponse}); present ⇔ `leaseState` is `granted`.
614
+ * - `configTargetVersion` / `configAppliedVersion` — the config-center version the engine was told to converge on, and the
615
+ * one it has applied (opaque strings; equal ⇔ converged).
616
+ *
617
+ * Reading: `EngineSignal` is parsed with the default strip — a whole /health body goes in, only these keys come out. A
618
+ * shape violation in ANY key makes the whole signal `undefined` (it is never "the rest of it"). `undefined` means "no
619
+ * signal this reader can say" — an engine that predates the keys, or a malformed body — and is NEVER "ready": a caller on
620
+ * the routing axis treats it as not routable unless it deliberately falls back to the boolean dialect of
621
+ * {@link InstanceHealth}.
622
+ */
623
+ export declare const EngineSignal: z.ZodObject<{
624
+ ready: z.ZodBoolean;
625
+ draining: z.ZodOptional<z.ZodObject<{
626
+ actionId: z.ZodString;
627
+ since: z.ZodNumber;
628
+ deadlineAt: z.ZodNumber;
629
+ }, "strip", z.ZodTypeAny, {
630
+ since: number;
631
+ actionId: string;
632
+ deadlineAt: number;
633
+ }, {
634
+ since: number;
635
+ actionId: string;
636
+ deadlineAt: number;
637
+ }>>;
638
+ restart: z.ZodOptional<z.ZodObject<{
639
+ reasons: z.ZodArray<z.ZodString, "many">;
640
+ version: z.ZodNumber;
641
+ since: z.ZodNumber;
642
+ withheld: z.ZodOptional<z.ZodBoolean>;
643
+ }, "strip", z.ZodTypeAny, {
644
+ reasons: string[];
645
+ version: number;
646
+ since: number;
647
+ withheld?: boolean | undefined;
648
+ }, {
649
+ reasons: string[];
650
+ version: number;
651
+ since: number;
652
+ withheld?: boolean | undefined;
653
+ }>>;
654
+ generation: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
655
+ instanceId: z.ZodOptional<z.ZodString>;
656
+ leaseState: z.ZodOptional<z.ZodEnum<["none", "granted", "expired", "denied"]>>;
657
+ leaseUntil: z.ZodOptional<z.ZodNumber>;
658
+ configTargetVersion: z.ZodOptional<z.ZodString>;
659
+ configAppliedVersion: z.ZodOptional<z.ZodString>;
660
+ }, "strip", z.ZodTypeAny, {
661
+ ready: boolean;
662
+ draining?: {
663
+ since: number;
664
+ actionId: string;
665
+ deadlineAt: number;
666
+ } | undefined;
667
+ restart?: {
668
+ reasons: string[];
669
+ version: number;
670
+ since: number;
671
+ withheld?: boolean | undefined;
672
+ } | undefined;
673
+ instanceId?: string | undefined;
674
+ generation?: string | undefined;
675
+ leaseState?: "none" | "granted" | "expired" | "denied" | undefined;
676
+ leaseUntil?: number | undefined;
677
+ configTargetVersion?: string | undefined;
678
+ configAppliedVersion?: string | undefined;
679
+ }, {
680
+ ready: boolean;
681
+ draining?: {
682
+ since: number;
683
+ actionId: string;
684
+ deadlineAt: number;
685
+ } | undefined;
686
+ restart?: {
687
+ reasons: string[];
688
+ version: number;
689
+ since: number;
690
+ withheld?: boolean | undefined;
691
+ } | undefined;
692
+ instanceId?: string | undefined;
693
+ generation?: string | undefined;
694
+ leaseState?: "none" | "granted" | "expired" | "denied" | undefined;
695
+ leaseUntil?: number | undefined;
696
+ configTargetVersion?: string | undefined;
697
+ configAppliedVersion?: string | undefined;
698
+ }>;
699
+ export type EngineSignal = z.infer<typeof EngineSignal>;
700
+ /**
701
+ * The reader of {@link EngineSignal}: the shape's keys, flat on the given body, judged by the same guarded body reader as
702
+ * {@link readRestartSignal} and {@link readModelRefusals}, so the same odd input gets the same answer from all three. Shape
703
+ * mismatch, a missing `ready`, a non-object body, even a throwing accessor ⇒ `undefined` — never throws. See {@link EngineSignal}
704
+ * for what `undefined` means (not "ready") and for why today's heartbeat and `/health` bodies read as `undefined` here.
705
+ */
706
+ export declare function readEngineSignal(health: unknown): EngineSignal | undefined;
707
+ /**
708
+ * The env name a fleet member's generation travels under — the config renderer sets it, the engine reads it back and echoes
709
+ * it as {@link EngineSignal}`.generation`. Both sides import THIS constant; neither spells the name. The value is the
710
+ * generation this instance belongs to (opaque string); absent from the engine's env ⇔ not a fleet member (the standalone
711
+ * form). There is deliberately NO env name for the instance id: an instance's identity is minted by the engine per process
712
+ * ({@link EngineSignal}`.instanceId`), and env cannot carry it (see there).
713
+ */
714
+ export declare const FLEET_GENERATION_ENV = "FLEET_GENERATION";
715
+ /**
716
+ * The serving lease — the ONE fence between a superseded generation and the traffic it must stop taking. A fleet-member
717
+ * engine renews it with the center; the center answers per the lifecycle object it holds for that instance. Three constants
718
+ * fix the clock, all in ms:
719
+ * - {@link SERVING_LEASE_TTL_DEFAULT_MS} — the lease length when the operator sets none (10 min).
720
+ * - {@link SERVING_LEASE_TTL_FLOOR_MS} — the shortest lease there is: a configured TTL below it is treated as it (1 min),
721
+ * the same way {@link DRAIN_GRACE_FLOOR_MS} protects a fat-fingered grace.
722
+ * - {@link SERVING_LEASE_DRIFT_MARGIN_MS} — the center's clock-skew allowance: it adds this to a lease's recorded expiry
723
+ * before it counts that lease as over. It is DERIVED from {@link DEFAULT_HEARTBEAT_MS} (one cadence, one number), never a
724
+ * second constant that could drift from it.
725
+ */
726
+ export declare const SERVING_LEASE_TTL_DEFAULT_MS = 600000;
727
+ export declare const SERVING_LEASE_TTL_FLOOR_MS = 60000;
728
+ export declare const SERVING_LEASE_DRIFT_MARGIN_MS = 30000;
729
+ /**
730
+ * The ONE place a configured lease TTL becomes a usable one: not a number (absent, `NaN`, infinite, another type) ⇒
731
+ * {@link SERVING_LEASE_TTL_DEFAULT_MS}; a number below {@link SERVING_LEASE_TTL_FLOOR_MS} (zero and negatives included) ⇒ the
732
+ * floor; otherwise the number, truncated to whole ms, with no ceiling. It is for the ISSUING side's configuration. A reply's
733
+ * `ttlMs` is used exactly as granted and is never clamped upward — an engine that stretched a short grant to the floor would
734
+ * hold the lease past what the center recorded, which is the one thing the fence exists to prevent.
735
+ */
736
+ export declare function clampServingLeaseTtlMs(configuredMs: number | undefined): number;
737
+ /**
738
+ * Engine → center: "may this instance keep serving?" The identity triple names the instance exactly — a lease is never
739
+ * asked for by worker name alone. `generation` is the env echo ({@link FLEET_GENERATION_ENV}); `instanceId` is the engine's
740
+ * own per-process identity ({@link EngineSignal}`.instanceId`), so a restarted process is a NEW instance to the center.
741
+ * `scope` — the tenant scope the lease is asked in. A renewal is taken on a PER-WORKER token only — the credential binds
742
+ * (scope, worker) — so the credential decides the scope and this field can only agree: absent = the scope the token binds; present
743
+ * ⇒ it must equal that scope (a mismatch is refused, never rewritten either way). The shared token may not renew a lease at all
744
+ * (the center answers 403). Only fleet members ask: an engine without a generation (the standalone form) has no lease and sends
745
+ * nothing.
746
+ */
747
+ export declare const ServingLeaseRequest: z.ZodObject<{
748
+ worker: z.ZodString;
749
+ scope: z.ZodOptional<z.ZodString>;
750
+ generation: z.ZodEffects<z.ZodString, string, string>;
751
+ instanceId: z.ZodString;
752
+ }, "strip", z.ZodTypeAny, {
753
+ instanceId: string;
754
+ worker: string;
755
+ generation: string;
756
+ scope?: string | undefined;
757
+ }, {
758
+ instanceId: string;
759
+ worker: string;
760
+ generation: string;
761
+ scope?: string | undefined;
762
+ }>;
763
+ export type ServingLeaseRequest = z.infer<typeof ServingLeaseRequest>;
764
+ /**
765
+ * Why a lease is refused — a CLOSED set of exactly two words, owned here:
766
+ * - `superseded` — a newer generation of this worker has taken over; this instance is the old one.
767
+ * - `retired` — the center has retired this instance (or the worker it belongs to).
768
+ * Only {@link ServingLeaseResponse}'s `denied` arm carries a reason; `unknown` has none, by shape.
769
+ */
770
+ export declare const SERVING_LEASE_DENIED_REASONS: readonly ["superseded", "retired"];
771
+ export declare const ServingLeaseDeniedReason: z.ZodEnum<["superseded", "retired"]>;
772
+ export type ServingLeaseDeniedReason = z.infer<typeof ServingLeaseDeniedReason>;
773
+ /**
774
+ * Center → engine: the verdict, a CLOSED set of three words ({@link SERVING_LEASE_VERDICTS}) — the arms of one
775
+ * discriminated union, so a reader that switches on `verdict` is told by the compiler when a word is added.
776
+ * - `granted` — keep serving; `ttlMs` is how long the lease lasts, a DURATION, never a wall-clock instant: the engine counts
777
+ * it from the instant it SENT the request, on its own monotonic clock (`expiresAt = sendAt + ttlMs`), and the center keeps
778
+ * its own expiry from its store time — the two sides never compare clocks, so no clock agreement is needed.
779
+ * - `denied` — this instance must stop: `reason` is {@link ServingLeaseDeniedReason}. The ONLY arm that obliges an engine to
780
+ * stop serving, and it is issued on POSITIVE evidence only: the center keeps, per (scope, worker), a persisted generation
781
+ * floor that is never garbage-collected, and a request whose generation lies below it is `superseded`; `retired` likewise
782
+ * rests on a record the center holds. A center that merely holds no such record does not deny — it answers `unknown`.
783
+ * - `unknown` — the center holds no lifecycle object for this instance right now (restoring its store, migrating scopes, not
784
+ * yet claimed). It says NOTHING about whether the instance should serve, so it is not a kind of `denied` and absence of the
785
+ * object NEVER turns into a denial: it carries no reason, and an engine never drains on it. To the engine it is the same
786
+ * case as an unreachable center.
787
+ * This is the law for what a center SENDS. A reader does not apply it to `reason`: {@link readServingLeaseResponse} judges by
788
+ * the `verdict` key and keeps a `denied` a `denied` even when its reason is outside the set ({@link ServingLeaseReading}). A
789
+ * reply whose `verdict` itself is outside the three words (or a `granted` with no valid `ttlMs`) is not a verdict at all and
790
+ * reads as `undefined` — an unreachable center, never `denied`, never `granted`.
791
+ */
792
+ export declare const ServingLeaseResponse: z.ZodDiscriminatedUnion<"verdict", [z.ZodObject<{
793
+ verdict: z.ZodLiteral<"granted">;
794
+ ttlMs: z.ZodNumber;
795
+ }, "strip", z.ZodTypeAny, {
796
+ verdict: "granted";
797
+ ttlMs: number;
798
+ }, {
799
+ verdict: "granted";
800
+ ttlMs: number;
801
+ }>, z.ZodObject<{
802
+ verdict: z.ZodLiteral<"denied">;
803
+ reason: z.ZodEnum<["superseded", "retired"]>;
804
+ }, "strip", z.ZodTypeAny, {
805
+ reason: "superseded" | "retired";
806
+ verdict: "denied";
807
+ }, {
808
+ reason: "superseded" | "retired";
809
+ verdict: "denied";
810
+ }>, z.ZodObject<{
811
+ verdict: z.ZodLiteral<"unknown">;
812
+ }, "strip", z.ZodTypeAny, {
813
+ verdict: "unknown";
814
+ }, {
815
+ verdict: "unknown";
816
+ }>]>;
817
+ export type ServingLeaseResponse = z.infer<typeof ServingLeaseResponse>;
818
+ /** The reason word a READER puts in place of a `denied` reason it cannot place (outside {@link SERVING_LEASE_DENIED_REASONS},
819
+ * missing, not a string). Read-face only: a producer never sends it, and the strict {@link ServingLeaseResponse} refuses it. */
820
+ export declare const SERVING_LEASE_UNKNOWN_REASON = "unknown_reason";
821
+ /** The read face of {@link ServingLeaseResponse} — same three arms, with the `denied` arm tolerant about `reason`. */
822
+ declare const ServingLeaseReadFace: z.ZodDiscriminatedUnion<"verdict", [z.ZodObject<{
823
+ verdict: z.ZodLiteral<"granted">;
824
+ ttlMs: z.ZodNumber;
825
+ }, "strip", z.ZodTypeAny, {
826
+ verdict: "granted";
827
+ ttlMs: number;
828
+ }, {
829
+ verdict: "granted";
830
+ ttlMs: number;
831
+ }>, z.ZodObject<{
832
+ verdict: z.ZodLiteral<"denied">;
833
+ reason: z.ZodEffects<z.ZodUnknown, "superseded" | "retired" | "unknown_reason", unknown>;
834
+ }, "strip", z.ZodTypeAny, {
835
+ reason: "superseded" | "retired" | "unknown_reason";
836
+ verdict: "denied";
837
+ }, {
838
+ verdict: "denied";
839
+ reason?: unknown;
840
+ }>, z.ZodObject<{
841
+ verdict: z.ZodLiteral<"unknown">;
842
+ }, "strip", z.ZodTypeAny, {
843
+ verdict: "unknown";
844
+ }, {
845
+ verdict: "unknown";
846
+ }>]>;
847
+ /** The verdict words as a runtime list. `satisfies` bites a word the union does not have; the converse (a union arm missing
848
+ * from this list) is pinned by a test that reads the union's discriminators. */
849
+ export declare const SERVING_LEASE_VERDICTS: readonly ["granted", "denied", "unknown"];
850
+ /** Center side: the request body of a serving-lease renewal. Shape mismatch / non-object / throwing accessor ⇒ `undefined`,
851
+ * never throws (same guarded reader as every other read in this file). */
852
+ export declare function readServingLeaseRequest(body: unknown): ServingLeaseRequest | undefined;
853
+ /**
854
+ * What {@link readServingLeaseResponse} returns: {@link ServingLeaseResponse} plus the one thing a reader adds — a `denied`
855
+ * whose reason it could not place carries {@link SERVING_LEASE_UNKNOWN_REASON}. Two faces on purpose (the failure direction
856
+ * must match the meaning): the strict wire law says what a center may SEND (a closed two-word reason set); the reader judges
857
+ * by the `verdict` key alone, so a center that learns a third reason word cannot make an older engine read `denied` as
858
+ * `undefined` and keep serving. Branch on `verdict`; `reason` is for the log line.
859
+ */
860
+ export type ServingLeaseReading = z.output<typeof ServingLeaseReadFace>;
861
+ /** Engine side: the reply to a renewal, judged by its `verdict` key: `granted` needs a valid `ttlMs`, `denied` is `denied`
862
+ * whatever its `reason` (see {@link ServingLeaseReading}), `unknown` is `unknown`. A non-object, a throwing accessor, a
863
+ * `verdict` outside the three words or a `granted` without a valid `ttlMs` ⇒ `undefined`, never throws — and `undefined`
864
+ * is read like an unreachable center, never like `denied` and never like `granted`. */
865
+ export declare function readServingLeaseResponse(body: unknown): ServingLeaseReading | undefined;
866
+ /**
867
+ * What the CENTER says it can do: capability bits an engine reads before it starts using a center-side endpoint.
868
+ * - `servingLease` — the center answers serving-lease renewals ({@link ServingLeaseRequest} / {@link ServingLeaseResponse}).
869
+ *
870
+ * A bit is `true` or absent — one spelling for "not supported" (there is no `false`). Absent (an older center, a body without
871
+ * the key, a body that cannot be read) means the center does not support the lease, and the engine then neither renews a
872
+ * lease nor is fenced by one: this is the compatibility gate that lets an executor ship ahead of the center without pushing
873
+ * running engines into lease expiry. It is a gate, not a verdict — nothing in this shape says "the center refuses leases".
874
+ * A reader asks `readCenterCapabilities(body)?.servingLease === true` and starts the lease client only on that.
875
+ * Unknown bits are stripped (a newer center's extra bits never break an older reader); a known bit whose value is not `true`
876
+ * makes the whole read `undefined`, which reads exactly as absent.
877
+ * It rides in ONE place: the heartbeat's 2xx reply ({@link FLEET_PATHS}`.instances`), under {@link HEARTBEAT_REPLY_CAPABILITIES_KEY}.
878
+ * The center answers the bits per CALLER CREDENTIAL: under a per-worker token, by what the center supports; under the shared token
879
+ * `servingLease` is always absent (the serving-lease endpoint takes per-worker tokens only, so announcing it there would only push
880
+ * that caller into the unreachable arm).
881
+ */
882
+ export declare const CenterCapabilities: z.ZodObject<{
883
+ servingLease: z.ZodOptional<z.ZodLiteral<true>>;
884
+ }, "strip", z.ZodTypeAny, {
885
+ servingLease?: true | undefined;
886
+ }, {
887
+ servingLease?: true | undefined;
888
+ }>;
889
+ export type CenterCapabilities = z.infer<typeof CenterCapabilities>;
890
+ /**
891
+ * The key of the heartbeat's 2xx reply body under which the center puts its {@link CenterCapabilities} — the object's only carrier.
892
+ * The center writes `{ …, [HEARTBEAT_REPLY_CAPABILITIES_KEY]: caps }`; the engine reads
893
+ * `readCenterCapabilities(reply[HEARTBEAT_REPLY_CAPABILITIES_KEY])`, and only on a 2xx (a failed heartbeat says nothing about the
894
+ * bits). Nested, not flat: the reply's own keys and the capability bits never share a namespace. A reply without the key is an
895
+ * older center — no capability said.
896
+ */
897
+ export declare const HEARTBEAT_REPLY_CAPABILITIES_KEY = "capabilities";
898
+ /** Engine side: the center's capability object. Shape mismatch / non-object / throwing accessor ⇒ `undefined`, never throws
899
+ * (and `undefined` means "no capability said" — the same as every bit absent). */
900
+ export declare function readCenterCapabilities(body: unknown): CenterCapabilities | undefined;
901
+ /**
902
+ * The drain endpoint's request: "put this instance into draining, by this deadline". One persisted action, one validation,
903
+ * whether the center calls directly or an executor retries it. Two lanes, one shape: on the Docker lane (and for a direct
904
+ * call from the center) this HTTP request is how an instance is drained; on the Kubernetes lane no per-pod HTTP call is made
905
+ * (per-pod addressing and RBAC make it the wrong tool) — an instance is drained by its serving lease being denied
906
+ * ({@link ServingLeaseResponse}), after which the engine puts itself into draining.
907
+ * - `actionId` — idempotency key: the same id again echoes the first answer ({@link DrainReceipt}`.state` =
908
+ * `already_draining`) and changes nothing.
909
+ * - the identity — `scope?` / `worker` / `expectedGeneration?` / `instanceId?` — names WHICH instance the action is for. The
910
+ * engine compares it field by field against its own, absent against absent: an engine that is not a fleet member has no
911
+ * generation, so a request with no `expectedGeneration` is the standalone form, not a wildcard; `instanceId`, when given,
912
+ * is the engine's own per-process identity ({@link EngineSignal}`.instanceId`). A mismatch is the endpoint's refusal, not
913
+ * this shape's: this schema only says what a well-formed request looks like.
914
+ * - `deadlineAt` — epoch ms by which the engine stops on its own.
915
+ * - `term` — the claim term the sending executor holds for this action (center-issued, opaque); absent where no executor
916
+ * sits in between (a direct call, a hand-issued action).
917
+ */
918
+ export declare const DrainRequest: z.ZodObject<{
919
+ actionId: z.ZodString;
920
+ scope: z.ZodOptional<z.ZodString>;
921
+ worker: z.ZodString;
922
+ expectedGeneration: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
923
+ instanceId: z.ZodOptional<z.ZodString>;
924
+ deadlineAt: z.ZodNumber;
925
+ term: z.ZodOptional<z.ZodString>;
926
+ }, "strip", z.ZodTypeAny, {
927
+ worker: string;
928
+ actionId: string;
929
+ deadlineAt: number;
930
+ instanceId?: string | undefined;
931
+ scope?: string | undefined;
932
+ expectedGeneration?: string | undefined;
933
+ term?: string | undefined;
934
+ }, {
935
+ worker: string;
936
+ actionId: string;
937
+ deadlineAt: number;
938
+ instanceId?: string | undefined;
939
+ scope?: string | undefined;
940
+ expectedGeneration?: string | undefined;
941
+ term?: string | undefined;
942
+ }>;
943
+ export type DrainRequest = z.infer<typeof DrainRequest>;
944
+ /** What a drain request leaves the engine in: `draining` — this call put it into draining; `already_draining` — it already was
945
+ * (the same action repeated), and the receipt reports the first call's facts. */
946
+ export declare const DRAIN_RECEIPT_STATES: readonly ["draining", "already_draining"];
947
+ export declare const DrainReceiptState: z.ZodEnum<["draining", "already_draining"]>;
948
+ export type DrainReceiptState = z.infer<typeof DrainReceiptState>;
949
+ /** The drain endpoint's answer: the engine's draining fact ({@link EngineDraining} — the same object {@link EngineSignal}
950
+ * carries as `draining`, extended, not copied) plus how this call found the engine. */
951
+ export declare const DrainReceipt: z.ZodObject<{
952
+ actionId: z.ZodString;
953
+ since: z.ZodNumber;
954
+ deadlineAt: z.ZodNumber;
955
+ } & {
956
+ state: z.ZodEnum<["draining", "already_draining"]>;
957
+ }, "strip", z.ZodTypeAny, {
958
+ since: number;
959
+ actionId: string;
960
+ deadlineAt: number;
961
+ state: "draining" | "already_draining";
962
+ }, {
963
+ since: number;
964
+ actionId: string;
965
+ deadlineAt: number;
966
+ state: "draining" | "already_draining";
967
+ }>;
968
+ export type DrainReceipt = z.infer<typeof DrainReceipt>;
969
+ /** Engine side: the request body of the drain endpoint. Shape mismatch / non-object / throwing accessor ⇒ `undefined`, never throws. */
970
+ export declare function readDrainRequest(body: unknown): DrainRequest | undefined;
971
+ /** Caller side: the drain endpoint's answer. Shape mismatch / non-object / throwing accessor ⇒ `undefined`, never throws. */
972
+ export declare function readDrainReceipt(body: unknown): DrainReceipt | undefined;
433
973
  export declare const DISPATCH_STRATEGIES: readonly ["round-robin", "least-inflight", "consistent-hash"];
434
974
  export type DispatchStrategy = (typeof DISPATCH_STRATEGIES)[number];
435
975
  /** Per-worker dispatch policy (center-configured; the gateway is OPTIONAL — D3: TOC/self-maintained TOB go
@@ -749,3 +1289,4 @@ export interface AggregatedPrincipalUsage {
749
1289
  * view). Pure — storage/dedup of replayed batches is the caller's concern (key batches by
750
1290
  * worker+instanceId+windowStartMs). */
751
1291
  export declare function aggregateUsage(reports: UsageReport[]): Record<string, AggregatedPrincipalUsage>;
1292
+ export {};