@slock-ai/computer 0.0.13 → 0.0.15

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,554 @@
1
+ type DaemonState = {
2
+ running: true;
3
+ pid: number;
4
+ } | {
5
+ running: false;
6
+ };
7
+ type ServerHealth = "ok" | "degraded" | "offline";
8
+ interface ServerStatusRow {
9
+ serverId: string;
10
+ serverSlug: string | null;
11
+ serverMachineId: string;
12
+ serverUrl: string;
13
+ attachedAt: string | null;
14
+ daemonLogPath: string;
15
+ daemon: DaemonState;
16
+ /** v6 §11 health enum surfaced in `status` table (PR-H §3.3). */
17
+ health: ServerHealth;
18
+ }
19
+ interface ComputerStatusReport {
20
+ slockHome: string;
21
+ loggedIn: boolean;
22
+ userId: string | null;
23
+ loginServerUrl: string | null;
24
+ userSessionError: string | null;
25
+ supervisor: DaemonState & {
26
+ logPath: string;
27
+ };
28
+ servers: ServerStatusRow[];
29
+ }
30
+
31
+ interface RunnerListItem {
32
+ agentId: string;
33
+ name: string;
34
+ status: string;
35
+ model: string;
36
+ runtime: string;
37
+ }
38
+
39
+ /**
40
+ * Per-runner lifecycle states (§3.2). One state at a time per runner.
41
+ *
42
+ * starting — spawned, not yet reporting ready
43
+ * running — healthy, reporting heartbeats
44
+ * degraded — alive but not satisfying health invariants (e.g. crash
45
+ * budget near breach, see §1/§2)
46
+ * crashed — observed exit / fatal signal
47
+ * stopped — intentional stop (operator-driven via `runners stop` or
48
+ * supervisor shutdown)
49
+ */
50
+ declare const RUNNER_STATE_VALUES: readonly ["starting", "running", "degraded", "crashed", "stopped"];
51
+ type RunnerState = (typeof RUNNER_STATE_VALUES)[number];
52
+ declare function isRunnerState(value: unknown): value is RunnerState;
53
+ /**
54
+ * Service (supervisor) lifecycle states (§3.2). Mirrors runner shape but
55
+ * narrower — the service is a singleton per install root.
56
+ *
57
+ * starting — pidfile written, socket not yet listening
58
+ * running — socket listening, heartbeat active
59
+ * degraded — service alive but one or more invariants violated
60
+ * (e.g. SERVICE_DEGRADED §7.3 crash-budget breach)
61
+ * stopping — graceful shutdown in flight (SERVICE_SHUTTING_DOWN
62
+ * §7.3 emitted to clients)
63
+ * stopped — pidfile cleared, socket closed
64
+ */
65
+ declare const SERVICE_STATE_VALUES: readonly ["starting", "running", "degraded", "stopping", "stopped"];
66
+ type ServiceState = (typeof SERVICE_STATE_VALUES)[number];
67
+ declare function isServiceState(value: unknown): value is ServiceState;
68
+
69
+ /**
70
+ * IPC error code family — the seed that `ServiceClient.request` and
71
+ * `ServiceClient.events` propagate. Forward-only post v9-sign: additions
72
+ * are additive minor library bumps; renames / removals are major.
73
+ *
74
+ * Full §7.3 enumeration (SETUP/ATTACH/MIGRATE/UPGRADE/SERVICE families)
75
+ * lands in a follow-up reconcile commit that also unifies the codes
76
+ * currently emitted via `ComputerServiceError` across services/*.
77
+ */
78
+ declare const IPC_ERROR_CODES: readonly ["IPC_FRAME_TOO_LARGE", "IPC_PROTOCOL_VERSION_UNSUPPORTED", "IPC_PROTOCOL_HANDSHAKE_FAILED", "IPC_HEARTBEAT_TIMEOUT", "IPC_MALFORMED_FRAME", "IPC_REQUEST_TIMEOUT", "IPC_REQUEST_CANCELED", "IPC_CLIENT_CLOSED"];
79
+ type IpcErrorCode = (typeof IPC_ERROR_CODES)[number];
80
+ /**
81
+ * Runtime narrow guard — exposed so consumers can pattern-match without
82
+ * pulling in a generic `as` cast. The CI gate (§7.4) over a final
83
+ * `ERROR_CODES` union will subsume this once the reconcile lands.
84
+ */
85
+ declare function isIpcErrorCode(value: unknown): value is IpcErrorCode;
86
+ /**
87
+ * Caller-facing options for `ServiceClient.request`. Pairs active cancel
88
+ * (`signal` — primary G-stage unmount path) with passive deadline
89
+ * (`timeoutMs` — library-side safety net). On either path the client
90
+ * emits an IPC `cancel` frame for the in-flight request id and rejects
91
+ * the returned promise with `IPC_REQUEST_CANCELED` or
92
+ * `IPC_REQUEST_TIMEOUT` respectively (§4.4).
93
+ */
94
+ interface RequestOptions {
95
+ signal?: AbortSignal;
96
+ timeoutMs?: number;
97
+ }
98
+ /**
99
+ * Typed RPC table — keys are kebab-case wire literals (§5.5), values are
100
+ * per-method `{ params, result }` pairs. The v9 sign-lock seed listed
101
+ * here is the §3.2 6-method floor; new methods are additive minor bumps
102
+ * (extending this map).
103
+ *
104
+ * State-reader results (`service-status` / `runner-status` /
105
+ * `list-runners`) are pinned to the lib reader return types — the
106
+ * D-stage §4 IPC handlers in PR-impl-2 satisfy the wire surface by
107
+ * delegating to those readers (mechanical `satisfies`).
108
+ *
109
+ * Mutation results (`upgrade-start` / `reset-service` / `reset-runner`)
110
+ * remain `unknown` here — they pin alongside the PR-impl-3 §X migration /
111
+ * verb pipeline that owns the corresponding handlers.
112
+ */
113
+ interface RequestMethodMap {
114
+ "service-status": {
115
+ params: void;
116
+ result: ServiceStatusResult;
117
+ };
118
+ "runner-status": {
119
+ params: {
120
+ serverId: string;
121
+ };
122
+ result: RunnerStatusResult;
123
+ };
124
+ "list-runners": {
125
+ params: void;
126
+ result: ListRunnersResult;
127
+ };
128
+ "upgrade-start": {
129
+ params: {
130
+ targetVersion?: string;
131
+ };
132
+ result: UpgradeStartResult;
133
+ };
134
+ "reset-service": {
135
+ params: void;
136
+ result: ResetServiceResult;
137
+ };
138
+ "reset-runner": {
139
+ params: {
140
+ serverId: string;
141
+ };
142
+ result: ResetRunnerResult;
143
+ };
144
+ }
145
+ /**
146
+ * Server-pushed event over `ServiceClient.events`. The discriminant field
147
+ * is `kind` (§5.5 — established at v9.7 sign); values are past-participle
148
+ * kebab-case literals. The 6-kind enumeration is the v9.8 §4.4 floor.
149
+ *
150
+ * Payload shapes finalize alongside §4 IPC impl (PR-impl-2, liuliu-owned);
151
+ * the discriminator is locked here so consumers can write exhaustive
152
+ * switches today.
153
+ *
154
+ * Note: v9.8 §4.4 has an example block still showing the v9.7-pre field
155
+ * name `topic` + present-tense values; §5.5 is the byte-pin source of
156
+ * truth (`kind` + past-participle). The §4.4 example will be reconciled
157
+ * in a follow-up doc-clean pass.
158
+ */
159
+ type ServiceEvent = {
160
+ kind: "service-state-changed";
161
+ payload: unknown;
162
+ } | {
163
+ kind: "runner-state-changed";
164
+ payload: unknown;
165
+ } | {
166
+ kind: "runner-attached";
167
+ payload: unknown;
168
+ } | {
169
+ kind: "runner-detached";
170
+ payload: unknown;
171
+ } | {
172
+ kind: "upgrade-log-appended";
173
+ payload: unknown;
174
+ } | {
175
+ kind: "heartbeat";
176
+ payload: unknown;
177
+ };
178
+ /**
179
+ * Library-side error envelope thrown by `ServiceClient`. `code` is a
180
+ * member of the §7 closed-set; `cause` is retained in-process only and
181
+ * MUST NOT cross IPC / CLI fail boundaries (§7.4 redaction).
182
+ */
183
+ declare class ServiceClientError extends Error {
184
+ readonly code: IpcErrorCode;
185
+ readonly cause?: unknown;
186
+ constructor(code: IpcErrorCode, message: string, cause?: unknown);
187
+ }
188
+ /**
189
+ * Typed IPC client. The implementation in PR-impl-2 §4 satisfies this
190
+ * interface; this commit only locks the surface.
191
+ *
192
+ * Termination semantics (§4.7):
193
+ * - `events` completes naturally (`Symbol.asyncIterator` return) on any
194
+ * socket-close path (graceful local/remote, transient, TCP RST). The
195
+ * `for-await` loop exits without throwing — idiomatic reconnect is a
196
+ * plain outer `while (!stopped) { … }` wrap, no try/catch.
197
+ * - `events` THROWS `ServiceClientError` only on protocol-level
198
+ * failures: handshake mismatch, frame parse, frame > 1 MiB.
199
+ * The disjoint split lets consumers distinguish "retry" (iterator end)
200
+ * from "give up" (throw) without runtime classification.
201
+ */
202
+ interface ServiceClient {
203
+ events: AsyncIterable<ServiceEvent>;
204
+ request<M extends keyof RequestMethodMap>(method: M, params: RequestMethodMap[M]["params"], options?: RequestOptions): Promise<RequestMethodMap[M]["result"]>;
205
+ close(): Promise<void>;
206
+ }
207
+ /**
208
+ * Connection options. `protocolVersion` lets a consumer opt-in to a
209
+ * specific handshake version; the service negotiates highest mutually
210
+ * supported (§4.3) and may reject with `IPC_PROTOCOL_VERSION_UNSUPPORTED`.
211
+ */
212
+ interface ConnectServiceOptions {
213
+ protocolVersion?: number;
214
+ }
215
+ /**
216
+ * Open a typed IPC connection to the Computer service. Throws
217
+ * `ServiceClientError` on handshake failure. Concrete implementation
218
+ * lands in PR-impl-2 §4 (liuliu-owned); the type is locked here so
219
+ * `apps/slock-computer-app` and other library consumers can type-check
220
+ * call sites before the transport ships.
221
+ */
222
+ type ConnectService = (installRoot: string, options?: ConnectServiceOptions) => Promise<ServiceClient>;
223
+
224
+ /**
225
+ * Canonical lib-side name for the `service-status` reader result, aligned
226
+ * with the wire method literal. Identical shape to `ComputerStatusReport`
227
+ * (re-exported above for callers that prefer the historical internal
228
+ * name).
229
+ *
230
+ * SECRET-FREE INVARIANT: the report carries server attachment IDENTITY
231
+ * and pid/liveness derived from filesystem state but NEVER the user
232
+ * access token or any `sk_computer_*` credential (status.ts §3.3.1
233
+ * redline, enforced by `status.test.ts` token-leak guards).
234
+ */
235
+ type ServiceStatusResult = ComputerStatusReport;
236
+ /**
237
+ * Per-server block in `ListRunnersResult.servers[]`. The discriminator
238
+ * preserves the underlying `RunnersClient.list()` outcome so a single
239
+ * server's `unauthorized` / `error` does NOT mask another server's data.
240
+ */
241
+ type RunnerListPerServer = {
242
+ serverId: string;
243
+ serverSlug: string | null;
244
+ status: "ok";
245
+ whitelist: string[];
246
+ runners: RunnerListItem[];
247
+ } | {
248
+ serverId: string;
249
+ serverSlug: string | null;
250
+ status: "unauthorized";
251
+ } | {
252
+ serverId: string;
253
+ serverSlug: string | null;
254
+ status: "error";
255
+ code: string;
256
+ };
257
+ /**
258
+ * Aggregate result of `listRunners(installRoot, opts?)`. When `opts.serverId`
259
+ * is omitted, `servers[]` contains one block per attached server (zero
260
+ * blocks if the Computer has no attachments). When `opts.serverId` is set,
261
+ * `servers[]` contains exactly that server's block, or the call throws
262
+ * `StateReaderError("NOT_ATTACHED")` if the id is not attached.
263
+ */
264
+ interface ListRunnersResult {
265
+ servers: RunnerListPerServer[];
266
+ }
267
+ /**
268
+ * Result of `readRunnerStatus(installRoot, serverId)` — per-server state
269
+ * row plus the runners running on it. Unauthorized / error discriminate
270
+ * the runners-API outcome while still surfacing the `server` row so the
271
+ * caller has the daemon state context.
272
+ */
273
+ type RunnerStatusResult = {
274
+ status: "ok";
275
+ server: ServerStatusRow;
276
+ whitelist: string[];
277
+ runners: RunnerListItem[];
278
+ } | {
279
+ status: "unauthorized";
280
+ server: ServerStatusRow;
281
+ } | {
282
+ status: "error";
283
+ server: ServerStatusRow;
284
+ code: string;
285
+ };
286
+ /**
287
+ * State-reader error closed set. Library boundary errors only — does NOT
288
+ * include the CLI ambient-rule errors (`NO_ATTACHMENT` / `AMBIGUOUS_SERVER`)
289
+ * which are v4 §6 ergonomics handled by the CLI wrapper, not the lib
290
+ * primitive.
291
+ */
292
+ declare const STATE_READER_ERROR_CODES: readonly ["NOT_ATTACHED", "INVALID_ATTACHMENT"];
293
+ type StateReaderErrorCode = (typeof STATE_READER_ERROR_CODES)[number];
294
+ /**
295
+ * Library-side error envelope thrown by state-readers. Lib readers
296
+ * NEVER call `fail()` / `process.exit` — CLI wrappers translate this to
297
+ * `fail(code, message)` at the boundary; D-stage IPC handlers map it to
298
+ * an IPC error frame.
299
+ */
300
+ declare class StateReaderError extends Error {
301
+ readonly code: StateReaderErrorCode;
302
+ constructor(code: StateReaderErrorCode, message: string);
303
+ }
304
+
305
+ /**
306
+ * Result of `reset-service` (`slock-computer reset --service`). Clears
307
+ * the service-level `crashHistory` and transitions service state
308
+ * `degraded → running`. MUST NOT kill runners (§1.3).
309
+ *
310
+ * `previousState` reports the state observed prior to the reset write —
311
+ * for callers that want to log/surface "was already running" vs
312
+ * "recovered from degraded". `clearedCrashCount` counts the entries
313
+ * removed from `service.state.json::crashHistory`.
314
+ */
315
+ interface ResetServiceResult {
316
+ status: "ok";
317
+ previousState: ServiceState;
318
+ clearedCrashCount: number;
319
+ }
320
+ /**
321
+ * Result of `reset-runner` (`slock-computer reset --runner <slug>`).
322
+ * Clears the per-runner `crashHistory` and transitions runner state
323
+ * `degraded → running`. MUST NOT respawn or kill the runner process
324
+ * (§2.4).
325
+ *
326
+ * `status: "not-found"` is returned when the resolved `serverId` does
327
+ * not correspond to an attached server (the CLI ambient `--server` slug
328
+ * resolver returns its own error before the handler is invoked; this
329
+ * branch defends the IPC path where the caller passes a serverId
330
+ * directly).
331
+ */
332
+ type ResetRunnerResult = {
333
+ status: "ok";
334
+ serverId: string;
335
+ previousState: RunnerState;
336
+ clearedCrashCount: number;
337
+ } | {
338
+ status: "not-found";
339
+ serverId: string;
340
+ };
341
+ /**
342
+ * Result of `upgrade-start` (§12 phase pipeline trigger). The same
343
+ * pipeline is funneled by manual CLI / auto scheduler / IPC; this
344
+ * result describes the trigger outcome only — the per-phase events
345
+ * stream over `ServiceEvent` (`upgrade-log-appended` kind).
346
+ *
347
+ * `status: "already-running"` is returned when an upgrade is already
348
+ * in flight; `upgradeId` then refers to the in-flight upgrade so the
349
+ * caller can attach to its event stream instead of starting a new one.
350
+ */
351
+ interface UpgradeStartResult {
352
+ status: "started" | "already-running";
353
+ upgradeId: string;
354
+ targetVersion: string;
355
+ }
356
+ /**
357
+ * Identity record for a legacy `@slock-ai/daemon` machine candidate that
358
+ * `detectLegacyMigration` discovered locally on this Computer install
359
+ * root. The shape is identity-only — it never carries credentials.
360
+ *
361
+ * `machineId` is the legacy daemon's lock-id (`machine-<sha256(apiKey)
362
+ * [0..15]>`). It is stable across legacy daemon restarts but changes if
363
+ * the user rotates the api key. The id is what the user would see in
364
+ * `<installRoot>/machines/` directory listings, and is what
365
+ * `AdoptLegacyService.migrate()` uses to find the lock owner file
366
+ * during migration.
367
+ *
368
+ * `machineName` is the host display name observed in the legacy
369
+ * daemon's `daemon.lock/owner.json` (typically `os.hostname()` at
370
+ * legacy daemon spawn time). Best-effort, may be undefined for stale
371
+ * lock files.
372
+ *
373
+ * `serverUrl` is the API server the legacy daemon was attached to.
374
+ * Used by the migrate prompt to surface "you are about to migrate
375
+ * machine X attached to <server>" before the user accepts.
376
+ */
377
+ interface LegacyMachineCandidate {
378
+ machineId: string;
379
+ machineName?: string;
380
+ serverUrl?: string;
381
+ }
382
+ /**
383
+ * §X migration detection result — the discriminated union returned by
384
+ * `detectLegacyMigration(installRoot, loggedInUserId)`.
385
+ *
386
+ * Per RFC §X.2:
387
+ * - `match`: exactly one local legacy daemon machine. The setup flow
388
+ * SHOULD prompt the user to migrate before falling back to fresh
389
+ * setup. Migration itself is delegated to `AdoptLegacyService.
390
+ * migrate()` (preserved as internal helper per §X.6 — 11 typed
391
+ * errors / 4 events / Result discriminator byte-identical).
392
+ * - `no-match`: zero local legacy daemon machines. Fresh setup
393
+ * proceeds without any migrate prompt.
394
+ * - `ambiguous`: two or more local legacy daemon machines. Per §X.2
395
+ * "no auto-listed-pick fallback" — fresh setup proceeds without
396
+ * any migrate prompt; the user is expected to start the legacy
397
+ * daemon they want migrated first to disambiguate.
398
+ *
399
+ * Fingerprint discipline (best-effort non-authoritative — Hao
400
+ * `msg=4d170194`): the candidate set is computed from local
401
+ * `<installRoot>/machines/machine-*` directory state. It identifies
402
+ * "this host has legacy daemon installs" — it does NOT prove ownership.
403
+ * Final adoption still walks the existing
404
+ * `AdoptLegacyService.migrate()` credential validation path; a match
405
+ * is a prompt-gate, not a trust-gate.
406
+ */
407
+ /**
408
+ * Closed set of `MigrationDetection.kind` discriminator values. Mirrors
409
+ * the `IPC_ERROR_CODES` / `RUNNER_STATE_VALUES` `as const` tuple pattern
410
+ * so consumers can pattern-match exhaustively and so a future §7.4 CI
411
+ * gate can verify the runtime kind against the type union without an
412
+ * `as` cast (state-machine-modeling discipline — correlated/mutex
413
+ * properties land as a closed-set state-enum).
414
+ */
415
+ declare const MIGRATION_DETECTION_KINDS: readonly ["match", "no-match", "ambiguous"];
416
+ type MigrationDetectionKind = (typeof MIGRATION_DETECTION_KINDS)[number];
417
+ /** Runtime narrow guard for `MigrationDetection.kind`. */
418
+ declare function isMigrationDetectionKind(value: unknown): value is MigrationDetectionKind;
419
+ type MigrationDetection = {
420
+ kind: "match";
421
+ machineId: string;
422
+ machineName?: string;
423
+ serverUrl?: string;
424
+ } | {
425
+ kind: "no-match";
426
+ } | {
427
+ kind: "ambiguous";
428
+ candidates: LegacyMachineCandidate[];
429
+ };
430
+
431
+ /**
432
+ * Detect whether `installRoot` already contains legacy
433
+ * `@slock-ai/daemon` machine state that could be migrated to a
434
+ * Computer attachment under the logged-in user.
435
+ *
436
+ * Returns a discriminated `MigrationDetection`:
437
+ * - `match` (exactly one candidate) — caller SHOULD prompt the user
438
+ * to migrate before fresh setup (§X.2 step 3).
439
+ * - `no-match` (zero candidates) — caller proceeds with fresh
440
+ * setup, NO prompt (§X.2 step 5).
441
+ * - `ambiguous` (two-or-more candidates) — caller proceeds with
442
+ * fresh setup, NO prompt (§X.2 step 5; "no auto-listed-pick
443
+ * fallback").
444
+ *
445
+ * Lib-pure: no env reads, no terminal IO, no process.exit. Filesystem
446
+ * errors are swallowed and treated as "no evidence" (per the doc-
447
+ * block above).
448
+ *
449
+ * @param installRoot The Slock home (`SLOCK_HOME`) to scan. Resolved
450
+ * by the CLI wrapper via `resolveSlockHome`.
451
+ * @param loggedInUserId Reserved for the server-roster intersection
452
+ * (forward-compat per §X.2 step 1). v0 does not consume this value;
453
+ * accept the typed argument so consumers of this function can
454
+ * already pass it.
455
+ */
456
+ declare function detectLegacyMigration(installRoot: string, loggedInUserId: string): Promise<MigrationDetection>;
457
+
458
+ /**
459
+ * Read the Computer-level aggregate status from `installRoot`. Identical
460
+ * shape to `buildStatusReport(installRoot)` — exposed under the
461
+ * wire-aligned name `readServiceStatus` for IPC `service-status` parity.
462
+ */
463
+ declare function readServiceStatus(installRoot: string): Promise<ServiceStatusResult>;
464
+ /**
465
+ * Read a single attached server's daemon state row plus the runners
466
+ * currently running on it. Throws `StateReaderError("NOT_ATTACHED")` if
467
+ * `serverId` is not in the attached set, or `"INVALID_ATTACHMENT"` if
468
+ * the attachment.json under that id is unreadable.
469
+ */
470
+ declare function readRunnerStatus(installRoot: string, serverId: string): Promise<RunnerStatusResult>;
471
+ /**
472
+ * List runners across attached servers. With no `serverId` filter,
473
+ * returns one block per attached server (zero if no attachments). With
474
+ * a `serverId` filter, returns exactly that server's block or throws
475
+ * `StateReaderError("NOT_ATTACHED")`.
476
+ *
477
+ * Per-server discriminator preserves `unauthorized` / `error` outcomes
478
+ * so consumers can pattern-match without losing context.
479
+ */
480
+ declare function listRunners(installRoot: string, opts?: {
481
+ serverId?: string;
482
+ }): Promise<ListRunnersResult>;
483
+
484
+ type UpgradeTrigger = "cli" | "web" | "tray";
485
+ /**
486
+ * v8.3.3 §4.4 (6) closed-set, verbatim 7 values. Adding / renaming /
487
+ * deleting any value requires an RFC bump (locked discipline). This
488
+ * constant array is the runtime source of truth for `assertUpgradeLogEntry`
489
+ * membership check; the type alias below mirrors it.
490
+ */
491
+ declare const UPGRADE_ERROR_CODES: readonly ["UPGRADE_DEPS_CHANGED", "UPGRADE_NETWORK_FAILED", "UPGRADE_INTEGRITY_FAILED", "UPGRADE_SWAP_FAILED", "UPGRADE_RESTART_FAILED", "UPGRADE_NO_TARGET", "UPGRADE_ALREADY_RUNNING"];
492
+ type UpgradeErrorCode = (typeof UPGRADE_ERROR_CODES)[number];
493
+ interface UpgradeBundle {
494
+ computerVersion: string;
495
+ /**
496
+ * Forensic-only per v8.3.2 cleanup. May be omitted; consumers MUST
497
+ * NOT treat presence/absence as a user-facing contract. The bundled
498
+ * daemon version plumbing (read from Computer package
499
+ * `dependencies["@slock-ai/daemon"]`) lands as a follow-up; until
500
+ * then writers emit just `computerVersion` and downstream readers
501
+ * default to "unknown" when the field is absent.
502
+ */
503
+ bundledDaemonVersion?: string;
504
+ }
505
+ /**
506
+ * v8.3.3 audit-log entry shape, discriminated on `outcome`. Splitting
507
+ * the union means:
508
+ * - `outcome: "ok"` forbids `errorCode` at the type level
509
+ * (`errorCode?: never`)
510
+ * - `outcome: "err"` requires `errorCode: UpgradeErrorCode` (closed-set
511
+ * 7-value membership enforced by TS)
512
+ *
513
+ * Together with the runtime `assertUpgradeLogEntry` guard below, this
514
+ * eliminates the prior "caller-discipline only" silent contract drift
515
+ * (Hao caveat msg=308d99db — `errorCode?: string` allowed any string +
516
+ * silent omission on `err` paths).
517
+ */
518
+ type UpgradeLogEntry = UpgradeLogEntryOk | UpgradeLogEntryErr;
519
+ interface UpgradeLogEntryOk {
520
+ /** ISO timestamp via formatUpgradeLogTimestamp(); caller may omit to auto-fill. */
521
+ at?: string;
522
+ fromBundle: UpgradeBundle;
523
+ toBundle: UpgradeBundle;
524
+ channel: string;
525
+ trigger: UpgradeTrigger;
526
+ outcome: "ok";
527
+ /** Forbidden on ok entries — discriminated-union enforcement. */
528
+ errorCode?: never;
529
+ }
530
+ interface UpgradeLogEntryErr {
531
+ at?: string;
532
+ fromBundle: UpgradeBundle;
533
+ toBundle: UpgradeBundle;
534
+ channel: string;
535
+ trigger: UpgradeTrigger;
536
+ outcome: "err";
537
+ /** Required iff outcome === "err"; one of the v8.3.3 closed 7-value set. */
538
+ errorCode: UpgradeErrorCode;
539
+ }
540
+ /**
541
+ * Runtime assertion: `entry` is a valid `UpgradeLogEntry`. Throws
542
+ * `TypeError` with an actionable message on contract violation. Defense
543
+ * in depth against JS-interop callers, JSON-deserialized payloads, and
544
+ * future type-system escapes.
545
+ *
546
+ * Validations:
547
+ * - `outcome === "ok"` AND `errorCode` is set → reject
548
+ * - `outcome === "err"` AND `errorCode` is missing → reject
549
+ * - `outcome === "err"` AND `errorCode` not in 7-value closed set → reject
550
+ * - Both bundle objects must have a non-empty `computerVersion` string
551
+ */
552
+ declare function assertUpgradeLogEntry(entry: UpgradeLogEntry): void;
553
+
554
+ export { type ComputerStatusReport, type ConnectService, type ConnectServiceOptions, type DaemonState, IPC_ERROR_CODES, type IpcErrorCode, type LegacyMachineCandidate, type ListRunnersResult, MIGRATION_DETECTION_KINDS, type MigrationDetection, type MigrationDetectionKind, RUNNER_STATE_VALUES, type RequestMethodMap, type RequestOptions, type ResetRunnerResult, type ResetServiceResult, type RunnerListItem as RunnerInfo, type RunnerListPerServer, type RunnerState, type RunnerStatusResult, SERVICE_STATE_VALUES, STATE_READER_ERROR_CODES, type ServerHealth, type ServerStatusRow, type ServiceClient, ServiceClientError, type ServiceEvent, type ServiceState, type ServiceStatusResult, StateReaderError, type StateReaderErrorCode, UPGRADE_ERROR_CODES, type UpgradeBundle, type UpgradeErrorCode, type UpgradeLogEntry, type UpgradeLogEntryErr, type UpgradeLogEntryOk, type UpgradeStartResult, type UpgradeTrigger, assertUpgradeLogEntry, detectLegacyMigration, isIpcErrorCode, isMigrationDetectionKind, isRunnerState, isServiceState, listRunners, readRunnerStatus, readServiceStatus };