@braedonsaunders/appkit-desk 0.6.0 → 0.7.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/README.md CHANGED
@@ -112,6 +112,12 @@ stops, the disk persists, applications cold-start on `resume`. Keep-alive jobs
112
112
  die with the lease, and their termination is recorded as `job_exit` so no
113
113
  process ever runs where an operator cannot see it.
114
114
 
115
+ `host.shutdown(deskId)` is a restart, not a destroy: a resident desk is synced
116
+ and shut down instead of parked, a parked desk's memory snapshot is discarded,
117
+ and the overlay stays, so the next `start` or `resume` cold boots from disk.
118
+ Use it when a desk must come back fresh — new memory or vCPU settings, or a
119
+ guest wedged in a way a resume would bring straight back.
120
+
115
121
  Residency is bounded by a hard capacity cap. Starts beyond the cap queue FIFO
116
122
  rather than overcommitting host memory. `host.stats()` reports
117
123
  `{ resident, queued, capacity, suspended, lastStartedAt, lastSuspendedAt,
@@ -179,7 +185,8 @@ append-only record that cannot be edited afterward.
179
185
 
180
186
  `onEvent` receives a closed union — `shell_command`, `app_launch`, `click`,
181
187
  `type`, `key`, `scroll`, `drag`, `window_focus`, `screen_open`,
182
- `screen_close`, `handover_begin`, `handover_end`, `job_start`, `job_exit` —
188
+ `screen_close`, `handover_begin`, `handover_end`, `job_start`, `job_exit`,
189
+ `snapshot_cpu_fallback` —
183
190
  each stamped with `deskId` and an ISO timestamp. The union is defined here so
184
191
  the consumer's ledger and this package agree on the taxonomy; persistence is
185
192
  entirely the consumer's concern.
@@ -231,3 +238,23 @@ masking boundary changes.
231
238
  Do not fall back to unconfined execution when this package reports an
232
239
  unsupported host. A deployment without KVM loses the desk ability entirely —
233
240
  the same fail-closed posture as the rest of AppKit.
241
+
242
+ ## Routed guest networking and TCP services
243
+
244
+ `deskNetworkIdentity(index)` returns a deterministic TAP name and MAC address. `createDeskNetwork({ gateway, dns, egress, chainPrefix?, allowHostPorts?, prefixLength?, runner?, enableForwarding? })` provides routed point-to-point TAP setup. Set `egress: { mode: 'open' }` to masquerade guest-originated IPv4 traffic for arbitrary TCP and UDP; IPv6 traffic on the TAP is blocked with `ip6tables` so it cannot bypass host isolation; new inbound forwarded connections are dropped. Set `egress: { mode: 'proxied', httpProxyPort, httpsProxyPort }` to transparently redirect HTTP and HTTPS to proxy listeners, pin DNS to the supplied resolver, and drop other guest egress. The proxy supplies destination policy. Both modes drop new connections to host services, except TCP and UDP ports explicitly listed in `allowHostPorts` on the gateway address; proxied mode also permits its proxy listeners. Established replies to host-initiated guest connections are accepted.
245
+
246
+ The default iptables chain prefix is `desk`. Existing deployments using the `bh` chains must supply `chainPrefix: 'bh'` to retain those names during a rolling upgrade and avoid orphaning the old rules. `ensure({ tap, guest })` must finish before boot; `teardown({ tap, guest })` removes its rules and TAP after the VM stops. Configure Linux IPv4 forwarding, NET_ADMIN, `ip`, and `iptables` in the host namespace; open mode also requires `ip6tables`. When changing egress modes, tear down the old mode before installing the new one. `configureGuest(desk, { tap, guest })` configures the guest interface, route, and resolver through exec. `syncDeskGuestClock(desk, now?)` sets the guest clock after wake. Failures are reported to the caller. Operations on one TAP are serialized within the network instance; use one instance per namespace. Rule replacement uses multiple iptables commands and is not atomic: call `ensure` before the VM starts, and do not refresh rules on a live TAP.
247
+
248
+ Supply `network: { tapDevice, macAddress, guestAddress }` to `host.start`, then `await desk.connect(port)` returns a Node `Duplex` TCP stream to the guest. The caller owns the stream; suspension closes it. A service must listen on the guest interface, and its protocol and authentication remain the application's responsibility. `connectGuest` on host options injects a dialer for testing.
249
+
250
+ With `parkWithMemory`, new snapshots carry a host CPU signature. A changed signature skips restore and cold boots; Cloud Hypervisor's specific incompatible-CPUID restore error also triggers a cold boot. These outcomes increment `stats().snapshotCpuFallbacks`, set `lastSnapshotCpuFallback`, and emit `snapshot_cpu_fallback`, rather than setting `lastError`. Existing snapshots without a signature are attempted normally. Signatures are conservative CPU identity and feature fingerprints, not a guarantee of CPUID compatibility; a cold boot loses the parked process state. Keep snapshots and their overlays together.
251
+
252
+ For a disposable real-host check, provide a guest image with the desk agent and Python 3, Cloud Hypervisor, KVM, and a free TAP name and IP pair. Run from the repository root in the target network namespace with privileges to manage TAP and iptables:
253
+
254
+ ```sh
255
+ DESK_CHECK_TAP=dsk9001 DESK_CHECK_GUEST=172.30.0.2 DESK_CHECK_GATEWAY=172.30.0.1 \
256
+ DESK_CHECK_IMAGE_ROOT=/data/desk-image DESK_CHECK_BASE_IMAGE=/data/desk-image/base.img \
257
+ DESK_CHECK_TMPDIR=/data/tmp pnpm --filter @braedonsaunders/appkit-desk exec tsx scripts/check-kvm.mts
258
+ ```
259
+
260
+ Set `DESK_CHECK_KERNEL_CMDLINE` for images that require a custom root device. The check creates and removes a disposable VM, overlay, snapshot, TAP and firewall rules. It verifies a guest HTTP stream before and after memory restore; it does not verify the proxy or cross-CPU migration. Choose addresses that do not overlap existing routes.
package/events.d.ts CHANGED
@@ -104,6 +104,9 @@ export type DeskEventDetail = {
104
104
  kind: 'screen_open';
105
105
  width: number;
106
106
  height: number;
107
+ } | {
108
+ kind: 'snapshot_cpu_fallback';
109
+ reason: 'signature_mismatch' | 'incompatible_cpuid';
107
110
  } | {
108
111
  kind: 'screen_close';
109
112
  } | {
package/events.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3D,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACtE,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAA;CAC9B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,MAAM,CAAA;AAE5C;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEjD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAAA;AAElD,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;AAEnE;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,eAAe,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,SAAS,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAAA;CAAE,GACtF;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,GAC1E;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC9E;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAEvF,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC,MAAM,CAAC,CAAA;AAEnD,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAExE;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,iBAAiB,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;CACX,GACD;IACE,IAAI,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAEL;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,CAAC,OAAO,EAAE;QAClB,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;QACf,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;QACvB,SAAS,EAAE,OAAO,CAAA;KACnB,GAAG,OAAO,CAAA;IACX,WAAW,CAAC,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAA;IACjF,aAAa,CAAC,CAAC,OAAO,EAAE;QACtB,MAAM,EAAE,MAAM,CAAA;QACd,KAAK,EAAE,iBAAiB,CAAA;QACxB,KAAK,EAAE,MAAM,CAAA;KACd,GAAG,OAAO,CAAA;CACZ;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,KAAK,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CACvC"}
package/events.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG","sourcesContent":["/**\n * The typed desk event stream and the ports a consumer supplies.\n *\n * AppKit owns the mechanism of running a desk; the consumer owns the record.\n * Every governed thing that happens on a desk is delivered to `onEvent` as one\n * member of a closed union so the consumer's append-only ledger can persist it\n * without inventing its own taxonomy. Handover boundaries additionally reach\n * the `audit` port — and, while a handover is active, they are the *only*\n * thing that reaches any port. See the masking contract on `DeskPorts`.\n */\n\nexport type DeskPointerButton = 'left' | 'middle' | 'right'\n\nexport interface DeskPoint {\n x: number\n y: number\n}\n\nexport interface WindowInfo {\n id: string\n title: string\n appId: string | null\n}\n\n/**\n * One node of the focused application's AT-SPI accessibility tree, when the\n * application exposes one. Perception is pixels-primary; the tree is\n * opportunistic and may be `null` at any time.\n */\nexport interface A11yNode {\n id: string\n role: string\n name: string | null\n actions: readonly string[]\n bounds: { x: number; y: number; width: number; height: number } | null\n children: readonly A11yNode[]\n}\n\n/**\n * How the pixels of a live frame are encoded on the wire.\n *\n * Two jobs, two formats. `observe()` is always lossless PNG because it feeds a\n * model's vision and anchors coordinates, and it is asked for rarely. The\n * `frames()` stream feeds a human driving the screen: there the scarce\n * resource is bytes on the transport, not fidelity, so a lossy `jpeg` is\n * roughly an order of magnitude smaller per frame and is what makes a live\n * view feel like a remote desktop rather than a slideshow.\n */\nexport type DeskFrameFormat = 'png' | 'jpeg'\n\n/**\n * The two kinds of unit a video stream is made of.\n *\n * `init` is the header a decoder needs before it can make sense of anything —\n * for fragmented MP4, `ftyp` plus `moov`. It arrives once per encoder start and\n * a consumer that never receives it decodes nothing at all, silently. `media`\n * is one fragment of pictures. Ordering is load-bearing: init first, then media\n * from a keyframe onwards.\n */\nexport type DeskVideoChunkKind = 'init' | 'media'\n\nexport type DeskHandoverScope = 'view' | 'control'\n\nexport type DeskHandoverEndReason = 'ended' | 'expired' | 'revoked'\n\n/**\n * The closed union of recordable desk activity. The consumer's ledger stores\n * these rows; the union is defined here so the mechanism and the record agree\n * on the taxonomy without the package ever touching a database.\n */\nexport type DeskEventDetail =\n | {\n kind: 'shell_command'\n command: string\n args: readonly string[]\n cwd: string | null\n exitCode: number | null\n signal: string | null\n }\n | { kind: 'app_launch'; appId: string; args: readonly string[] }\n | { kind: 'click'; x: number; y: number; button: DeskPointerButton; clicks: 1 | 2 }\n | { kind: 'type'; text: string }\n | { kind: 'key'; combo: string }\n | { kind: 'scroll'; x: number; y: number; dx: number; dy: number }\n | { kind: 'drag'; from: DeskPoint; to: DeskPoint }\n | { kind: 'window_focus'; window: WindowInfo }\n | { kind: 'screen_open'; width: number; height: number }\n | { kind: 'screen_close' }\n | { kind: 'handover_begin'; actor: string | null; scope: DeskHandoverScope }\n | { kind: 'handover_end'; actor: string | null; durationMs: number }\n | { kind: 'job_start'; jobId: string; command: string; args: readonly string[] }\n | { kind: 'job_exit'; jobId: string; exitCode: number | null; signal: string | null }\n\nexport type DeskEventKind = DeskEventDetail['kind']\n\nexport type DeskEvent = DeskEventDetail & { deskId: string; at: string }\n\n/**\n * Handover boundaries as the audit port sees them: who took over, under what\n * scope, and for how long — never what they typed.\n */\nexport type DeskAuditEntry =\n | {\n kind: 'handover_begin'\n deskId: string\n actor: string | null\n scope: DeskHandoverScope\n ttlMs: number\n at: string\n }\n | {\n kind: 'handover_end'\n deskId: string\n actor: string | null\n durationMs: number\n reason: DeskHandoverEndReason\n at: string\n }\n\n/**\n * Consumer-supplied policy. Absent hooks allow: the package is mechanism, and\n * a deployment that wants governance supplies it here. A hook returning\n * `false` makes the corresponding call throw before anything reaches the\n * guest.\n */\nexport interface DeskPolicyPort {\n allowExec?(request: {\n deskId: string\n command: string\n args: readonly string[]\n keepAlive: boolean\n }): boolean\n allowScreen?(request: { deskId: string; width: number; height: number }): boolean\n allowHandover?(request: {\n deskId: string\n scope: DeskHandoverScope\n ttlMs: number\n }): boolean\n}\n\n/**\n * The ports a consumer supplies to `createDeskHost`.\n *\n * Masking contract (load-bearing): while a handover is active, input events —\n * `click`, `type`, `key`, `scroll`, `drag` — and the screen-derived events\n * `window_focus` and `app_launch` never reach `onEvent`, and frames are never\n * emitted to `frames()` consumers. Only `handover_begin` and `handover_end`\n * cross the boundary, carrying actor and duration. Getting this wrong leaks\n * credentials into an append-only record.\n */\nexport interface DeskPorts {\n policy?: DeskPolicyPort\n onEvent: (event: DeskEvent) => void\n audit: (entry: DeskAuditEntry) => void\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG","sourcesContent":["/**\n * The typed desk event stream and the ports a consumer supplies.\n *\n * AppKit owns the mechanism of running a desk; the consumer owns the record.\n * Every governed thing that happens on a desk is delivered to `onEvent` as one\n * member of a closed union so the consumer's append-only ledger can persist it\n * without inventing its own taxonomy. Handover boundaries additionally reach\n * the `audit` port — and, while a handover is active, they are the *only*\n * thing that reaches any port. See the masking contract on `DeskPorts`.\n */\n\nexport type DeskPointerButton = 'left' | 'middle' | 'right'\n\nexport interface DeskPoint {\n x: number\n y: number\n}\n\nexport interface WindowInfo {\n id: string\n title: string\n appId: string | null\n}\n\n/**\n * One node of the focused application's AT-SPI accessibility tree, when the\n * application exposes one. Perception is pixels-primary; the tree is\n * opportunistic and may be `null` at any time.\n */\nexport interface A11yNode {\n id: string\n role: string\n name: string | null\n actions: readonly string[]\n bounds: { x: number; y: number; width: number; height: number } | null\n children: readonly A11yNode[]\n}\n\n/**\n * How the pixels of a live frame are encoded on the wire.\n *\n * Two jobs, two formats. `observe()` is always lossless PNG because it feeds a\n * model's vision and anchors coordinates, and it is asked for rarely. The\n * `frames()` stream feeds a human driving the screen: there the scarce\n * resource is bytes on the transport, not fidelity, so a lossy `jpeg` is\n * roughly an order of magnitude smaller per frame and is what makes a live\n * view feel like a remote desktop rather than a slideshow.\n */\nexport type DeskFrameFormat = 'png' | 'jpeg'\n\n/**\n * The two kinds of unit a video stream is made of.\n *\n * `init` is the header a decoder needs before it can make sense of anything —\n * for fragmented MP4, `ftyp` plus `moov`. It arrives once per encoder start and\n * a consumer that never receives it decodes nothing at all, silently. `media`\n * is one fragment of pictures. Ordering is load-bearing: init first, then media\n * from a keyframe onwards.\n */\nexport type DeskVideoChunkKind = 'init' | 'media'\n\nexport type DeskHandoverScope = 'view' | 'control'\n\nexport type DeskHandoverEndReason = 'ended' | 'expired' | 'revoked'\n\n/**\n * The closed union of recordable desk activity. The consumer's ledger stores\n * these rows; the union is defined here so the mechanism and the record agree\n * on the taxonomy without the package ever touching a database.\n */\nexport type DeskEventDetail =\n | {\n kind: 'shell_command'\n command: string\n args: readonly string[]\n cwd: string | null\n exitCode: number | null\n signal: string | null\n }\n | { kind: 'app_launch'; appId: string; args: readonly string[] }\n | { kind: 'click'; x: number; y: number; button: DeskPointerButton; clicks: 1 | 2 }\n | { kind: 'type'; text: string }\n | { kind: 'key'; combo: string }\n | { kind: 'scroll'; x: number; y: number; dx: number; dy: number }\n | { kind: 'drag'; from: DeskPoint; to: DeskPoint }\n | { kind: 'window_focus'; window: WindowInfo }\n | { kind: 'screen_open'; width: number; height: number }\n | { kind: 'snapshot_cpu_fallback'; reason: 'signature_mismatch' | 'incompatible_cpuid' }\n | { kind: 'screen_close' }\n | { kind: 'handover_begin'; actor: string | null; scope: DeskHandoverScope }\n | { kind: 'handover_end'; actor: string | null; durationMs: number }\n | { kind: 'job_start'; jobId: string; command: string; args: readonly string[] }\n | { kind: 'job_exit'; jobId: string; exitCode: number | null; signal: string | null }\n\nexport type DeskEventKind = DeskEventDetail['kind']\n\nexport type DeskEvent = DeskEventDetail & { deskId: string; at: string }\n\n/**\n * Handover boundaries as the audit port sees them: who took over, under what\n * scope, and for how long — never what they typed.\n */\nexport type DeskAuditEntry =\n | {\n kind: 'handover_begin'\n deskId: string\n actor: string | null\n scope: DeskHandoverScope\n ttlMs: number\n at: string\n }\n | {\n kind: 'handover_end'\n deskId: string\n actor: string | null\n durationMs: number\n reason: DeskHandoverEndReason\n at: string\n }\n\n/**\n * Consumer-supplied policy. Absent hooks allow: the package is mechanism, and\n * a deployment that wants governance supplies it here. A hook returning\n * `false` makes the corresponding call throw before anything reaches the\n * guest.\n */\nexport interface DeskPolicyPort {\n allowExec?(request: {\n deskId: string\n command: string\n args: readonly string[]\n keepAlive: boolean\n }): boolean\n allowScreen?(request: { deskId: string; width: number; height: number }): boolean\n allowHandover?(request: {\n deskId: string\n scope: DeskHandoverScope\n ttlMs: number\n }): boolean\n}\n\n/**\n * The ports a consumer supplies to `createDeskHost`.\n *\n * Masking contract (load-bearing): while a handover is active, input events —\n * `click`, `type`, `key`, `scroll`, `drag` — and the screen-derived events\n * `window_focus` and `app_launch` never reach `onEvent`, and frames are never\n * emitted to `frames()` consumers. Only `handover_begin` and `handover_end`\n * cross the boundary, carrying actor and duration. Getting this wrong leaks\n * credentials into an append-only record.\n */\nexport interface DeskPorts {\n policy?: DeskPolicyPort\n onEvent: (event: DeskEvent) => void\n audit: (entry: DeskAuditEntry) => void\n}\n"]}
package/host.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from 'node:buffer';
2
+ import type { Duplex } from 'node:stream';
2
3
  import { type DeskLauncherIdentity } from './plan.js';
3
4
  import { type DeskBackend } from './backend.js';
4
5
  import type { A11yNode, DeskFrameFormat, DeskHandoverScope, DeskPoint, DeskPointerButton, DeskPorts, DeskVideoChunkKind, WindowInfo } from './events.js';
@@ -34,6 +35,8 @@ export interface DeskHostOptions {
34
35
  launcherIdentity?: DeskLauncherIdentity;
35
36
  backend?: DeskBackend;
36
37
  ports: DeskPorts;
38
+ /** Override TCP dialing for tests or a host-specific transport. */
39
+ connectGuest?: (address: string, port: number) => Promise<Duplex>;
37
40
  /** Hard cap on concurrently resident desks; starts beyond it queue FIFO. */
38
41
  capacity?: number;
39
42
  idleSuspendMs?: number;
@@ -64,6 +67,10 @@ export interface DeskHostOptions {
64
67
  * occurrence is indistinguishable, from inside the guest, from a failing host.
65
68
  */
66
69
  parkWithMemory?: boolean;
70
+ /** Identifies the host CPU for conservative snapshot portability checks. */
71
+ cpuSignature?: () => Promise<string>;
72
+ readSnapshotSignature?: (directory: string) => Promise<string | null>;
73
+ writeSnapshotSignature?: (directory: string, signature: string) => Promise<void>;
67
74
  /** Create a snapshot directory; defaults to `mkdir(path, { recursive: true })`. */
68
75
  makeSnapshotDir?: (path: string) => Promise<void>;
69
76
  /**
@@ -112,6 +119,7 @@ export interface DeskStartOptions {
112
119
  network?: {
113
120
  tapDevice?: string;
114
121
  macAddress?: string;
122
+ guestAddress?: string;
115
123
  };
116
124
  }
117
125
  export interface DeskExecOptions {
@@ -297,6 +305,8 @@ export interface DeskScreenService {
297
305
  export interface DeskHandle {
298
306
  readonly deskId: string;
299
307
  renewLease(ms: number): void;
308
+ /** Open a host-to-guest TCP stream; the caller owns and closes it. */
309
+ connect(port: number): Promise<Duplex>;
300
310
  exec(options: DeskExecOptions & {
301
311
  keepAlive: true;
302
312
  }): Promise<DeskJob>;
@@ -325,11 +335,25 @@ export interface DeskHostStats {
325
335
  /** Which desk reconnected last: enough to go and look at the right one. */
326
336
  lastReconnectDeskId: string | null;
327
337
  lastError: string | null;
338
+ snapshotCpuFallbacks: number;
339
+ lastSnapshotCpuFallback: {
340
+ deskId: string;
341
+ at: string;
342
+ reason: 'signature_mismatch' | 'incompatible_cpuid';
343
+ } | null;
328
344
  }
329
345
  export interface DeskHost {
330
346
  start(options: DeskStartOptions): Promise<DeskHandle>;
331
347
  resume(deskId: string): Promise<DeskHandle>;
332
348
  suspend(deskId: string): Promise<void>;
349
+ /**
350
+ * Stop a desk WITHOUT its memory, keeping its overlay: the next `start` or
351
+ * `resume` cold boots from disk. This is a restart, not a destroy — the disk
352
+ * is the desk and stays exactly where it is. A resident desk is synced and
353
+ * shut down rather than parked; a suspended desk's parked memory snapshot is
354
+ * discarded. Unknown desks are a no-op.
355
+ */
356
+ shutdown(deskId: string): Promise<void>;
333
357
  destroy(deskId: string): Promise<void>;
334
358
  stats(): DeskHostStats;
335
359
  /**
@@ -418,4 +442,8 @@ export interface DeskHostVerification {
418
442
  * the backend, asks it two questions, and discards it.
419
443
  */
420
444
  export declare function verifyDeskHost(options: VerifyDeskHostOptions): Promise<DeskHostVerification>;
445
+ /** Cloud Hypervisor's specific restore error; do not classify generic CPUID mentions. */
446
+ export declare function isIncompatibleCpuSnapshotError(error: unknown): boolean;
447
+ /** Hash the host's CPU identity and advertised feature set, excluding per-core counters. */
448
+ export declare function hostCpuSignature(): Promise<string>;
421
449
  //# sourceMappingURL=host.d.ts.map
package/host.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAML,KAAK,oBAAoB,EAC1B,MAAM,QAAQ,CAAA;AACf,OAAO,EAEL,KAAK,WAAW,EAGjB,MAAM,WAAW,CAAA;AAWlB,OAAO,KAAK,EACV,QAAQ,EAGR,eAAe,EAEf,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EACjB,SAAS,EACT,kBAAkB,EAClB,UAAU,EACX,MAAM,UAAU,CAAA;AAEjB,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,uBAAuB,QAAa,CAAA;AACjD,eAAO,MAAM,gBAAgB,QAAc,CAAA;AAC3C,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,qBAAqB,CAAA;AACtD,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAAU,CAAA;AACzD,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAC7C,eAAO,MAAM,gBAAgB,MAAM,CAAA;AA0BnC,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB,CAAC,EAAE,oBAAoB,CAAA;IACvC,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,KAAK,EAAE,SAAS,CAAA;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACtC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACxC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,mFAAmF;IACnF,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACtD;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAA;IACrB,OAAO,EAAE,SAAS,UAAU,EAAE,CAAA;IAC9B,OAAO,EAAE,UAAU,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,MAAM,EAAE,eAAe,CAAA;IACvB,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,kBAAkB,CAAA;IACxB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAAA;IACnC;;;;;;;;;OASG;IACH,KAAK,EAAE,eAAe,CAAA;IACtB,IAAI,EAAE;QAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAA;IAC/D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9D,SAAS,EAAE;QAAE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAA;IAC1E;;;;;;;;;;OAUG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE;QACf,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,eAAe,CAAA;KACzB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAA;IAC5B;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE;QACd,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,GAAG,aAAa,CAAC,cAAc,CAAC,CAAA;IACjC,QAAQ,EAAE;QACR,KAAK,CAAC,OAAO,EAAE;YACb,KAAK,EAAE,MAAM,CAAA;YACb,KAAK,CAAC,EAAE,iBAAiB,CAAA;YACzB,KAAK,CAAC,EAAE,MAAM,CAAA;SACf,GAAG,OAAO,CAAC;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC5B,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;QACpB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;KACzB,CAAA;CACF;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC5E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG;QAAE,SAAS,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACtE,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACzD,0DAA0D;IAC1D,IAAI,IAAI,OAAO,EAAE,CAAA;IACjB,MAAM,EAAE,iBAAiB,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IACrD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAC3C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,KAAK,IAAI,aAAa,CAAA;IACtB;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACzB;AA0CD,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAkpCjE;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,gBAAgB,CAAC,EAAE,oBAAoB,CAAA;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACtC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACxC,SAAS,CAAC,EAAE,MAAM,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,+EAA+E;IAC/E,SAAS,EAAE,OAAO,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,MAAM,CAAA;IACxB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9D,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA4B/B;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,MAAM,CAAA;IACxB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9D,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAW/B;AAcD,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,OAAO,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA4ElG"}
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
package/host.js CHANGED
@@ -10,11 +10,12 @@
10
10
  */
11
11
  import { existsSync } from 'node:fs';
12
12
  import { execFile } from 'node:child_process';
13
- import { mkdir, rm, statfs, writeFile } from 'node:fs/promises';
13
+ import { mkdir, readFile, rm, statfs, writeFile } from 'node:fs/promises';
14
14
  import { tmpdir } from 'node:os';
15
15
  import { dirname, join } from 'node:path';
16
- import { randomUUID } from 'node:crypto';
16
+ import { createHash, randomUUID } from 'node:crypto';
17
17
  import { Buffer } from 'node:buffer';
18
+ import { connect as tcpConnect, isIP } from 'node:net';
18
19
  import { buildDeskLaunchPlan, cleanDeskId, DEFAULT_KVM_PATH, DEFAULT_VMM_PATH, DeskError, } from './plan.js';
19
20
  import { cloudHypervisorBackend, } from './backend.js';
20
21
  import { parseCapabilities, parseClipboardText, parseExecResult, parseHandoverBegun, parseJobStarted, parseObservation, } from './protocol.js';
@@ -93,6 +94,8 @@ export function createDeskHost(options) {
93
94
  let lastStartedAt = null;
94
95
  let lastSuspendedAt = null;
95
96
  let lastError = null;
97
+ let snapshotCpuFallbacks = 0;
98
+ let lastSnapshotCpuFallback = null;
96
99
  let reconnects = 0;
97
100
  let lastReconnectAt = null;
98
101
  let lastReconnectDeskId = null;
@@ -276,13 +279,16 @@ export function createDeskHost(options) {
276
279
  function snapshotDirFor(overlayPath, deskId) {
277
280
  return join(dirname(overlayPath), `${deskId}.snapshot`);
278
281
  }
279
- async function teardown(record) {
282
+ async function teardown(record, keepMemory = true) {
280
283
  if (record.status !== 'resident')
281
284
  return false;
282
285
  await endHandover(record, 'revoked');
283
286
  for (const stop of [...record.streamStops])
284
287
  stop();
285
288
  record.streamStops.clear();
289
+ for (const stop of record.relayStops)
290
+ stop();
291
+ record.relayStops.clear();
286
292
  if (record.screen?.running) {
287
293
  record.screen.running = false;
288
294
  emit(record, { kind: 'screen_close' });
@@ -318,14 +324,18 @@ export function createDeskHost(options) {
318
324
  // the disk alone is exactly what is left whenever a snapshot is discarded,
319
325
  // which happens on every failed restore. Syncing first makes discarding a
320
326
  // snapshot always safe instead of usually safe.
321
- const parking = parkWithMemory && typeof machine.park === 'function';
322
- if (parking)
327
+ const parking = keepMemory && parkWithMemory && typeof machine.park === 'function';
328
+ // A shutdown that throws memory away leaves the disk as the only state, so
329
+ // it is flushed exactly as a park is.
330
+ if (parking || !keepMemory)
323
331
  await syncGuest(record, machine);
324
332
  try {
325
333
  if (parking) {
326
334
  const snapshotDir = snapshotDirFor(record.startOptions.overlayPath, record.deskId);
327
335
  await makeSnapshotDir(snapshotDir);
328
336
  await machine.park(snapshotDir);
337
+ const signature = await (options.cpuSignature ?? hostCpuSignature)();
338
+ await (options.writeSnapshotSignature ?? writeSnapshotSignature)(snapshotDir, signature);
329
339
  }
330
340
  else {
331
341
  await machine.shutdown();
@@ -430,7 +440,15 @@ export function createDeskHost(options) {
430
440
  snapshotDir,
431
441
  ...(restore ? { restore: true } : {}),
432
442
  }, { pathExists, deviceExists });
433
- const resuming = parkWithMemory && pathExists(snapshotDir);
443
+ let resuming = parkWithMemory && pathExists(snapshotDir);
444
+ if (resuming) {
445
+ const saved = await (options.readSnapshotSignature ?? readSnapshotSignature)(snapshotDir);
446
+ if (saved !== null && saved !== await (options.cpuSignature ?? hostCpuSignature)()) {
447
+ await removeSnapshot(snapshotDir);
448
+ resuming = false;
449
+ recordCpuFallback(deskId, 'signature_mismatch');
450
+ }
451
+ }
434
452
  let machine;
435
453
  try {
436
454
  machine = resuming
@@ -450,8 +468,11 @@ export function createDeskHost(options) {
450
468
  // boot. Safe by construction — the guest never ran, so the disk is
451
469
  // exactly as the pre-park sync left it.
452
470
  const why = errorMessage(error);
453
- await removeSnapshot(snapshotDir).catch(() => undefined);
454
- lastError = `Desk ${deskId} could not resume from its snapshot and was cold booted: ${why}`;
471
+ await removeSnapshot(snapshotDir);
472
+ if (isIncompatibleCpuSnapshotError(error))
473
+ recordCpuFallback(deskId, 'incompatible_cpuid');
474
+ else
475
+ lastError = `Desk ${deskId} could not resume from its snapshot and was cold booted: ${why}`;
455
476
  // Falls through to the shared record path below with a cold machine.
456
477
  machine = await backend.boot(planFor(false));
457
478
  }
@@ -479,6 +500,7 @@ export function createDeskHost(options) {
479
500
  handle: null,
480
501
  timer: null,
481
502
  streamStops: new Set(),
503
+ relayStops: new Set(),
482
504
  };
483
505
  records.set(deskId, record);
484
506
  }
@@ -490,6 +512,7 @@ export function createDeskHost(options) {
490
512
  record.jobs = new Map();
491
513
  record.screen = null;
492
514
  record.streamStops = new Set();
515
+ record.relayStops = new Set();
493
516
  }
494
517
  const current = record;
495
518
  const epoch = current.epoch;
@@ -735,6 +758,32 @@ export function createDeskHost(options) {
735
758
  touch(record);
736
759
  armTimer(record);
737
760
  },
761
+ async connect(port) {
762
+ requireMachine(record, epoch);
763
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
764
+ throw new DeskError('Guest TCP port must be between 1 and 65535.');
765
+ const address = record.startOptions.network?.guestAddress;
766
+ if (!address || isIP(address) !== 4)
767
+ throw new DeskError('A guest IPv4 address is required to connect to a desk port.');
768
+ touch(record);
769
+ try {
770
+ const stream = await (options.connectGuest ?? connectGuestTcp)(address, port);
771
+ try {
772
+ requireMachine(record, epoch);
773
+ }
774
+ catch (error) {
775
+ stream.destroy();
776
+ throw error;
777
+ }
778
+ const stop = () => stream.destroy();
779
+ record.relayStops.add(stop);
780
+ stream.once('close', () => record.relayStops.delete(stop));
781
+ return stream;
782
+ }
783
+ catch (error) {
784
+ throw new DeskError(`Cannot connect to guest ${address}:${port}: ${errorMessage(error)}`);
785
+ }
786
+ },
738
787
  exec: execImplementation,
739
788
  jobs() {
740
789
  return [...record.jobs.values()]
@@ -1121,6 +1170,19 @@ export function createDeskHost(options) {
1121
1170
  await teardown(record);
1122
1171
  });
1123
1172
  }
1173
+ async function shutdown(deskId) {
1174
+ const id = cleanDeskId(deskId);
1175
+ await serializeLifecycle(id, async () => {
1176
+ const record = records.get(id);
1177
+ if (!record)
1178
+ return;
1179
+ await teardown(record, false);
1180
+ // A desk parked earlier still has its memory image on disk; dropping it is
1181
+ // what makes the next boot cold. The overlay is untouched — only `destroy`
1182
+ // may remove it.
1183
+ await removeSnapshot(snapshotDirFor(record.startOptions.overlayPath, record.deskId));
1184
+ });
1185
+ }
1124
1186
  /**
1125
1187
  * Delete a desk's overlay. The ONLY place in this module that removes a
1126
1188
  * desk's disk, and it is reachable from `destroy` alone — see the contract
@@ -1191,6 +1253,11 @@ export function createDeskHost(options) {
1191
1253
  }
1192
1254
  return suspendedCount;
1193
1255
  }
1256
+ function recordCpuFallback(deskId, reason) {
1257
+ snapshotCpuFallbacks += 1;
1258
+ lastSnapshotCpuFallback = { deskId, reason, at: iso(now()) };
1259
+ ports.onEvent({ kind: 'snapshot_cpu_fallback', deskId, reason, at: iso(now()) });
1260
+ }
1194
1261
  function stats() {
1195
1262
  let suspended = 0;
1196
1263
  for (const record of records.values()) {
@@ -1208,9 +1275,11 @@ export function createDeskHost(options) {
1208
1275
  lastReconnectAt,
1209
1276
  lastReconnectDeskId,
1210
1277
  lastError,
1278
+ snapshotCpuFallbacks,
1279
+ lastSnapshotCpuFallback,
1211
1280
  };
1212
1281
  }
1213
- return { start, resume, suspend, destroy, stats, sweep };
1282
+ return { start, resume, suspend, shutdown, destroy, stats, sweep };
1214
1283
  }
1215
1284
  /**
1216
1285
  * Whether the overlay directory's filesystem can make reflink clones.
@@ -1402,4 +1471,38 @@ async function defaultFreeBytes(path) {
1402
1471
  function errorMessage(error) {
1403
1472
  return error instanceof Error ? error.message : String(error);
1404
1473
  }
1474
+ async function connectGuestTcp(address, port) {
1475
+ return new Promise((resolve, reject) => {
1476
+ const socket = tcpConnect({ host: address, port });
1477
+ socket.setTimeout(10_000, () => socket.destroy(new Error('Guest TCP connection timed out.')));
1478
+ socket.once('error', reject);
1479
+ socket.once('connect', () => { socket.setTimeout(0); socket.removeListener('error', reject); resolve(socket); });
1480
+ });
1481
+ }
1482
+ /** Cloud Hypervisor's specific restore error; do not classify generic CPUID mentions. */
1483
+ export function isIncompatibleCpuSnapshotError(error) {
1484
+ return /Detected incompatible CPUID entry/.test(errorMessage(error));
1485
+ }
1486
+ /** Hash the host's CPU identity and advertised feature set, excluding per-core counters. */
1487
+ export async function hostCpuSignature() {
1488
+ const cpuinfo = await readFile('/proc/cpuinfo', 'utf8');
1489
+ const first = cpuinfo.split(/\n\s*\n/)[0] ?? '';
1490
+ const identity = first.split('\n').filter((line) => /^(vendor_id|cpu family|model\s|model name|stepping|flags|microcode)\s*:/.test(line)).join('\n');
1491
+ if (!identity)
1492
+ throw new DeskError('Cannot determine host CPU signature.');
1493
+ return createHash('sha256').update(identity).digest('hex');
1494
+ }
1495
+ async function readSnapshotSignature(directory) {
1496
+ try {
1497
+ return (await readFile(join(directory, 'appkit-cpu-signature'), 'utf8')).trim();
1498
+ }
1499
+ catch (error) {
1500
+ if (error.code === 'ENOENT')
1501
+ return null;
1502
+ throw error;
1503
+ }
1504
+ }
1505
+ async function writeSnapshotSignature(directory, signature) {
1506
+ await writeFile(join(directory, 'appkit-cpu-signature'), signature, { mode: 0o600 });
1507
+ }
1405
1508
  //# sourceMappingURL=host.js.map