@fungi.computer/computer 0.0.1 → 0.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.
Files changed (41) hide show
  1. package/README.md +38 -10
  2. package/dist/computer-backends.d.ts +11 -12
  3. package/dist/computer-backends.js +4 -7
  4. package/dist/exec-output.d.ts +13 -0
  5. package/dist/exec-output.js +24 -0
  6. package/dist/execution-lifecycle.d.ts +35 -0
  7. package/dist/execution-lifecycle.js +78 -0
  8. package/dist/files-patch.d.ts +12 -4
  9. package/dist/files-patch.js +8 -14
  10. package/dist/files.d.ts +65 -15
  11. package/dist/files.js +152 -31
  12. package/dist/fleet/backing.js +2 -2
  13. package/dist/fleet/index.d.ts +1 -1
  14. package/dist/fleet/index.js +1 -1
  15. package/dist/fleet/inventory-wire.d.ts +4 -0
  16. package/dist/fleet/inventory-wire.js +5 -0
  17. package/dist/fleet/inventory.js +4 -4
  18. package/dist/fleet/lifecycle.d.ts +7 -1
  19. package/dist/fleet/lifecycle.js +23 -6
  20. package/dist/fleet/wire.d.ts +28 -28
  21. package/dist/fleet/wire.js +9 -9
  22. package/dist/index.d.ts +33 -15
  23. package/dist/index.js +426 -109
  24. package/dist/internet-access.d.ts +4 -0
  25. package/dist/internet-access.js +14 -0
  26. package/dist/local-backend-replay.d.ts +10 -0
  27. package/dist/local-backend-replay.js +43 -0
  28. package/dist/local-backend.d.ts +2 -1
  29. package/dist/local-backend.js +4 -4
  30. package/dist/on-box-vm.d.ts +15 -4
  31. package/dist/on-box-vm.js +43 -20
  32. package/dist/operation-record.d.ts +1 -1
  33. package/dist/operation-record.js +1 -1
  34. package/dist/process-inventory.d.ts +3 -1
  35. package/dist/process-inventory.js +2 -2
  36. package/dist/team-candidate-builder.mjs +8428 -337
  37. package/package.json +17 -24
  38. package/dist/light-shell.d.ts +0 -44
  39. package/dist/light-shell.js +0 -376
  40. package/dist/operation-contract.d.ts +0 -456
  41. package/dist/operation-contract.js +0 -452
package/dist/files.js CHANGED
@@ -1,4 +1,8 @@
1
- import { replaceOutputSchema, } from "./operation-contract.js";
1
+ import { WorkspaceFileStore } from "@fungi.computer/cloudflare-computer/tools";
2
+ import { posix } from "node:path";
3
+ import { SearchVisibility } from "@fungi.computer/knapsack/files-search";
4
+ import { imageReadResult, imageSniffBytes, imageTooLargeMessage, maximumImageBytes, sniffImageMimeType, } from "@fungi.computer/knapsack/files-read";
5
+ import { replaceOutputSchema, } from "@fungi.computer/computer-contract";
2
6
  const utf8 = new TextEncoder();
3
7
  function entryType(entry) {
4
8
  if (entry.isSymbolicLink)
@@ -52,7 +56,7 @@ export function readBoundedWorkspaceText(provider, path, maximumBytes) {
52
56
  }
53
57
  /** Adapt native Workspace.fs at the one real file boundary. */
54
58
  export function createWorkspaceFilesPort(workspace, owner) {
55
- return Object.freeze({
59
+ const port = {
56
60
  async read(input) {
57
61
  const stat = await workspace.fs.stat(input.path);
58
62
  if (!stat.isFile)
@@ -69,10 +73,22 @@ export function createWorkspaceFilesPort(workspace, owner) {
69
73
  truncated: false,
70
74
  };
71
75
  }
72
- const pageBytes = await readBytes(workspace, input.path, input.offset, Math.min(input.limit, stat.size - input.offset));
73
- if (pageBytes.byteLength === 0) {
76
+ // The first read covers the sniff window whatever the caller's limit.
77
+ const windowBytes = await readBytes(workspace, input.path, input.offset, Math.min(input.offset === 0
78
+ ? Math.max(input.limit, imageSniffBytes)
79
+ : input.limit, stat.size - input.offset));
80
+ if (windowBytes.byteLength === 0) {
74
81
  throw new Error(`Files path changed during read: ${input.path}`);
75
82
  }
83
+ const mimeType = input.offset === 0 ? sniffImageMimeType(windowBytes) : undefined;
84
+ if (mimeType !== undefined) {
85
+ if (stat.size > maximumImageBytes)
86
+ throw new Error(imageTooLargeMessage(input.path, stat.size, maximumImageBytes));
87
+ return imageReadResult(input.path, mimeType, windowBytes.byteLength === stat.size
88
+ ? windowBytes
89
+ : await readBytes(workspace, input.path, 0, stat.size));
90
+ }
91
+ const pageBytes = windowBytes.subarray(0, input.limit);
76
92
  const page = decodeUtf8Page(pageBytes, input.path, input.offset + pageBytes.byteLength < stat.size);
77
93
  const nextOffset = input.offset + page.byteLength;
78
94
  return {
@@ -83,54 +99,159 @@ export function createWorkspaceFilesPort(workspace, owner) {
83
99
  truncated: nextOffset < stat.size,
84
100
  };
85
101
  },
86
- async list(input) {
87
- const rows = await workspace.fs.readdir(input.path, {
88
- limit: input.limit + 1,
89
- });
90
- const entries = rows.slice(0, input.limit).map((entry) => {
91
- const path = childPath(input.path, entry.name);
92
- return {
93
- path,
94
- name: entry.name,
95
- type: entryType(entry),
96
- size: entry.size,
97
- modifiedAt: entry.mtime,
98
- };
99
- });
102
+ async list(input, context) {
103
+ const output = await searchWorkspaceDirectory(workspace, { ...input, offset: 0 }, context?.signal);
100
104
  return {
101
105
  path: input.path,
102
- entries,
103
- truncated: rows.length > input.limit,
106
+ ...output,
107
+ results: output.results.map((entry) => ({
108
+ path: entry.path,
109
+ type: entryType(entry),
110
+ })),
104
111
  };
105
112
  },
106
- async glob(input) {
107
- const rows = await workspace.fs.find(input.path, input.pattern, {
108
- limit: input.limit + 1,
109
- });
113
+ async glob(input, context) {
114
+ const output = await visibleSearch(workspace, workspacePath(input.path), input.limit, (options) => workspace.fs.find(workspacePath(input.path), input.pattern, options), (entry) => entry.type === "dir", context?.signal);
110
115
  return {
111
116
  path: input.path,
112
- matches: rows.slice(0, input.limit).map((entry) => entry.path),
113
- truncated: rows.length > input.limit,
117
+ ...output,
118
+ results: output.results.map((row) => ({
119
+ path: row.path,
120
+ type: row.type === "dir" ? "directory" : "file",
121
+ })),
114
122
  };
115
123
  },
116
- async grep(input) {
117
- const rows = await workspace.fs.grep(input.pattern, input.path, {
124
+ async grep(input, context) {
125
+ const options = {
118
126
  regex: input.regex,
119
127
  ignoreCase: input.ignoreCase,
120
128
  limit: input.limit + 1,
121
- });
129
+ };
130
+ if (input.include !== undefined)
131
+ options.include = input.include;
132
+ const output = await visibleSearch(workspace, workspacePath(input.path), input.limit, (page) => workspace.fs.grep(input.pattern, workspacePath(input.path), {
133
+ ...options,
134
+ ...page,
135
+ }), () => false, context?.signal);
122
136
  return {
123
137
  path: input.path,
124
- matches: rows.slice(0, input.limit).map((row) => ({
138
+ ...output,
139
+ results: output.results.map((row) => ({
125
140
  path: row.path,
126
141
  line: row.line,
127
142
  text: row.text,
128
143
  })),
129
- truncated: rows.length > input.limit,
130
144
  };
131
145
  },
132
146
  async replace(input) {
133
147
  return replaceOutputSchema.parse(await owner.replaceFileIfUnchanged(input));
134
148
  },
149
+ };
150
+ return Object.freeze({
151
+ ...port,
152
+ // The Computer REST contract keeps its own path and content searches for
153
+ // Apps and Hub routes; its matches/boolean results are not Agent results.
154
+ rest: Object.freeze({
155
+ list: (input) => listWorkspaceFiles(workspace, input),
156
+ async glob(input) {
157
+ const output = await port.glob(input);
158
+ return {
159
+ path: input.path,
160
+ matches: output.results.map((entry) => entry.path),
161
+ truncated: output.truncated !== null,
162
+ };
163
+ },
164
+ async grep(input) {
165
+ const output = await port.grep(input);
166
+ return {
167
+ path: input.path,
168
+ matches: output.results,
169
+ truncated: output.truncated !== null,
170
+ };
171
+ },
172
+ }),
173
+ });
174
+ }
175
+ function searchWorkspaceDirectory(workspace, input, signal) {
176
+ const path = workspacePath(input.path);
177
+ return visibleSearch(workspace, path, input.limit, async (options) => (await workspace.fs.readdir(path, options)).map((entry) => ({
178
+ ...entry,
179
+ path: childPath(path, entry.name),
180
+ })), (entry) => entry.isDirectory, signal, input.offset);
181
+ }
182
+ /** Shared REST listing: offsets count visible entries, never ignored candidates. */
183
+ export async function listWorkspaceFiles(workspace, input) {
184
+ const output = await searchWorkspaceDirectory(workspace, input);
185
+ // The existing list wire has no scan-reason field. Do not disguise an
186
+ // exhausted scan as an empty finished page or a fabricated next offset.
187
+ if (output.truncated === "scan")
188
+ throw new Error("Files listing reached the search scan bound; narrow path");
189
+ return {
190
+ path: input.path,
191
+ entries: output.results.map((entry) => ({
192
+ name: entry.name,
193
+ size: entry.size,
194
+ modifiedAt: entry.mtime,
195
+ type: entryType(entry),
196
+ })),
197
+ nextOffset: output.truncated === null ? null : input.offset + output.results.length,
198
+ };
199
+ }
200
+ /** Computer wire paths are absolute within its Workspace, even for relative Files inputs. */
201
+ function workspacePath(path) {
202
+ if (path === ".")
203
+ return "/";
204
+ return path.startsWith("/") ? path : `/${path}`;
205
+ }
206
+ /** Storage adapter only; shared Files policy owns visibility and grammar. */
207
+ function workspaceSearchVisibility(workspace) {
208
+ const store = new WorkspaceFileStore(workspace);
209
+ let bytes = 0;
210
+ return new SearchVisibility(async (directory) => {
211
+ const path = posix.join("/", directory, ".gitignore");
212
+ const stat = await store.stat(path);
213
+ if (stat !== null) {
214
+ // Match Local's bounded text reads; never silently omit unreadable rules.
215
+ if (stat.size > 128 * 1_024 || bytes + stat.size > 64 * 1_024 * 1_024)
216
+ throw new Error(`Files ignore rules exceed the search byte bound: ${path}`);
217
+ bytes += stat.size;
218
+ return decodeUtf8(await readBytes(workspace, path, 0, stat.size), path);
219
+ }
220
+ return undefined;
135
221
  });
136
222
  }
223
+ function searchRelativePath(path) {
224
+ return posix.relative("/", posix.resolve("/", path));
225
+ }
226
+ /** Page the SDK's traversal; filtering must not spend the visible-result limit. */
227
+ async function visibleSearch(workspace, root, limit, page, isDirectory, signal, visibleOffset = 0) {
228
+ const rules = workspaceSearchVisibility(workspace);
229
+ const results = [];
230
+ signal?.throwIfAborted();
231
+ const metadata = await workspace.fs.stat(root);
232
+ if (!(await rules.visible(searchRelativePath(root), metadata.isDirectory)))
233
+ return { results, truncated: null };
234
+ const maximumCandidates = 50_000;
235
+ let offset = 0;
236
+ while (offset < maximumCandidates) {
237
+ signal?.throwIfAborted();
238
+ const count = Math.min(offset === 0 ? limit + 1 : Math.max(128, limit + 1), maximumCandidates - offset);
239
+ const rows = await page(offset === 0 ? { limit: count } : { limit: count, offset });
240
+ for (const row of rows) {
241
+ signal?.throwIfAborted();
242
+ if (!(await rules.visible(searchRelativePath(row.path), isDirectory(row))))
243
+ continue;
244
+ if (visibleOffset > 0) {
245
+ visibleOffset--;
246
+ continue;
247
+ }
248
+ if (results.length === limit)
249
+ return { results, truncated: "limit" };
250
+ results.push(row);
251
+ }
252
+ if (rows.length < count)
253
+ return { results, truncated: null };
254
+ offset += rows.length;
255
+ }
256
+ return { results, truncated: "scan" };
257
+ }
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { attachedBackendId, localBackendSchema, } from "../computer-backends.js";
3
3
  import { ComputerBackingError, computerBackingChangeSchema, hostedBackingsSchema, localBackendAttachSchema, localBackendDetachSchema, localBackendDetachRequestSchema, } from "./wire.js";
4
4
  const maximumLocalBackends = 8;
5
- const localRowSchema = z.strictObject({
5
+ const localRowSchema = z.lazy(() => z.strictObject({
6
6
  computer_id: z.string(),
7
7
  device_id: z.string(),
8
8
  actor_id: z.string(),
@@ -10,7 +10,7 @@ const localRowSchema = z.strictObject({
10
10
  host_label: z.string(),
11
11
  folder_label: z.string(),
12
12
  request_id: z.string(),
13
- });
13
+ }));
14
14
  function mintDeviceId() {
15
15
  const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
16
16
  const bytes = crypto.getRandomValues(new Uint8Array(16));
@@ -5,7 +5,7 @@ export { computerActionSchema, computerProvenanceSchema, computerGrantSchema, co
5
5
  export { ComputerRuntimeUsage } from "./runtime-usage.js";
6
6
  export { computerUsageIntervalSchema, type ComputerUsageInterval, } from "./wire.js";
7
7
  export { createFleetInventory } from "./inventory.js";
8
- export { ComputerInventoryError, type FleetActor, type FleetComputer, } from "./inventory-wire.js";
8
+ export { ComputerInventoryError, computerCreateRequestSchema, computerPageSchema, type ComputerCreateRequest, type ComputerPage, type FleetActor, type FleetComputer, } from "./inventory-wire.js";
9
9
  export { createFleetSlots } from "./slots.js";
10
10
  export { FleetLifecycle, FleetGrantRefused, type FleetLifecyclePorts, type FleetLifecycleFact, } from "./lifecycle.js";
11
11
  export { createFleetComputers } from "./computers.js";
@@ -5,7 +5,7 @@ export { computerActionSchema, computerProvenanceSchema, computerGrantSchema, co
5
5
  export { ComputerRuntimeUsage } from "./runtime-usage.js";
6
6
  export { computerUsageIntervalSchema, } from "./wire.js";
7
7
  export { createFleetInventory } from "./inventory.js";
8
- export { ComputerInventoryError, } from "./inventory-wire.js";
8
+ export { ComputerInventoryError, computerCreateRequestSchema, computerPageSchema, } from "./inventory-wire.js";
9
9
  export { createFleetSlots } from "./slots.js";
10
10
  export { FleetLifecycle, FleetGrantRefused, } from "./lifecycle.js";
11
11
  export { createFleetComputers } from "./computers.js";
@@ -12,6 +12,10 @@ export type FleetComputer = Readonly<{
12
12
  updatedAt: number;
13
13
  }>;
14
14
  /** A durable create receipt binds the complete request to its actor. */
15
+ export declare const computerCreateRequestSchema: z.ZodObject<{
16
+ name: z.ZodOptional<z.ZodString>;
17
+ requestId: z.ZodString;
18
+ }, z.core.$strict>;
15
19
  export type ComputerCreateRequest = Readonly<{
16
20
  name?: string | undefined;
17
21
  requestId: string;
@@ -1,4 +1,9 @@
1
1
  import { z } from "zod";
2
+ /** A durable create receipt binds the complete request to its actor. */
3
+ export const computerCreateRequestSchema = z.strictObject({
4
+ name: z.string().min(1).max(256).optional(),
5
+ requestId: z.string().min(1).max(256),
6
+ });
2
7
  /** Inventory pages retain stable ordering and a bounded response. */
3
8
  export const computerPageSchema = z.strictObject({
4
9
  limit: z.number().int().min(1).max(100).default(100),
@@ -7,7 +7,7 @@ const computerIds = defineIdFamily({
7
7
  version: 1,
8
8
  kinds: { computer: { prefix: "computer" } },
9
9
  });
10
- const rowSchema = z.strictObject({
10
+ const rowSchema = z.lazy(() => z.strictObject({
11
11
  computer_id: z.string().min(1),
12
12
  name: z.string().min(1).max(256),
13
13
  actor_kind: z.string().min(1),
@@ -15,15 +15,15 @@ const rowSchema = z.strictObject({
15
15
  active: z.number().int().min(0).max(1),
16
16
  created_at: z.number().int().nonnegative(),
17
17
  updated_at: z.number().int().nonnegative(),
18
- });
19
- const receiptSchema = z.strictObject({
18
+ }));
19
+ const receiptSchema = z.lazy(() => z.strictObject({
20
20
  requested_name: z.string().min(1).max(256).nullable(),
21
21
  request_id: z.string().min(1).max(256),
22
22
  actor_kind: z.string().min(1),
23
23
  actor_id: z.string().min(1).max(256),
24
24
  name: z.string().min(1).max(256),
25
25
  computer_id: z.string().min(1),
26
- });
26
+ }));
27
27
  const selectRow = "SELECT computer_id,name,actor_kind,actor_id,active,created_at,updated_at FROM hub_team_computers";
28
28
  function actorColumns(actor) {
29
29
  return { actorId: actor.id, actorKind: actor.kind };
@@ -53,10 +53,16 @@ export declare class FleetLifecycle {
53
53
  readonly usage: ComputerRuntimeUsage;
54
54
  constructor(ports: FleetLifecyclePorts);
55
55
  get recycleRuntimeId(): string | undefined;
56
+ /** A successful stop cannot race an admitted boot or in-flight operation. */
57
+ get hasPendingOperations(): boolean;
56
58
  takeFacts(): FleetLifecycleFact[];
57
59
  private diagnostic;
58
60
  /** Keep pending starts visible while the host obtains admission and capacity. */
59
- beginHostedStart(): () => void;
61
+ private beginHostedStart;
62
+ /** Start the selected hosted backend under the host's admission and slot policy. */
63
+ startHostedComputer(admitted: (backing: HostedBacking, run: () => Promise<void>) => Promise<void>): Promise<{
64
+ state: "running";
65
+ }>;
60
66
  /** Read liveness without waking a sleeping backend. Queue state wins over booting. */
61
67
  readComputerState(): Promise<{
62
68
  state: "queued";
@@ -6,12 +6,7 @@ import { ComputerRuntimeUsage } from "./runtime-usage.js";
6
6
  import { checkComputerGrant } from "./admission.js";
7
7
  const vmRecycleAfterMs = 23 * 60 * 60 * 1_000;
8
8
  const vmRecycleQuietMs = 30_000;
9
- const runtimeWatchSchema = z.coerce
10
- .number()
11
- .int()
12
- .min(1_000)
13
- .max(3_600_000)
14
- .catch(60_000);
9
+ const runtimeWatchSchema = z.lazy(() => z.coerce.number().int().min(1_000).max(3_600_000).catch(60_000));
15
10
  function runtimeWatchDelay(delivered, running, watchMs) {
16
11
  if (running > 0)
17
12
  return delivered ? watchMs : 1_000;
@@ -45,6 +40,12 @@ export class FleetLifecycle {
45
40
  get recycleRuntimeId() {
46
41
  return this.#recycleRuntimeId;
47
42
  }
43
+ /** A successful stop cannot race an admitted boot or in-flight operation. */
44
+ get hasPendingOperations() {
45
+ return (this.#hostedOperations > 0 ||
46
+ this.#startingHosted > 0 ||
47
+ this.#recycling !== undefined);
48
+ }
48
49
  takeFacts() {
49
50
  const facts = this.#facts;
50
51
  this.#facts = [];
@@ -60,6 +61,22 @@ export class FleetLifecycle {
60
61
  this.#startingHosted--;
61
62
  };
62
63
  }
64
+ /** Start the selected hosted backend under the host's admission and slot policy. */
65
+ async startHostedComputer(admitted) {
66
+ const hosted = this.ports.backend.requireHostedRoute();
67
+ const finish = this.beginHostedStart();
68
+ try {
69
+ await admitted(hosted.backing, async () => {
70
+ await this.ports.backend.workspace().push(hosted.id);
71
+ });
72
+ if (!(await this.ports.backend.containerRunning(hosted.backing)))
73
+ throw new Error("Hosted Computer disconnected while starting");
74
+ return { state: "running" };
75
+ }
76
+ finally {
77
+ finish();
78
+ }
79
+ }
63
80
  /** Read liveness without waking a sleeping backend. Queue state wins over booting. */
64
81
  async readComputerState() {
65
82
  const backends = await this.readAttachedBackends();
@@ -5,6 +5,12 @@ export declare const computerActionSchema: z.ZodEnum<{
5
5
  exec: "exec";
6
6
  start: "start";
7
7
  delete: "delete";
8
+ mkdir: "mkdir";
9
+ backing: "backing";
10
+ bash: "bash";
11
+ exposePort: "exposePort";
12
+ listPorts: "listPorts";
13
+ unexposePort: "unexposePort";
8
14
  "files.read": "files.read";
9
15
  "files.readBytes": "files.readBytes";
10
16
  "files.write": "files.write";
@@ -14,12 +20,6 @@ export declare const computerActionSchema: z.ZodEnum<{
14
20
  "files.grep": "files.grep";
15
21
  "files.edit": "files.edit";
16
22
  "files.patch": "files.patch";
17
- mkdir: "mkdir";
18
- bash: "bash";
19
- backing: "backing";
20
- exposePort: "exposePort";
21
- listPorts: "listPorts";
22
- unexposePort: "unexposePort";
23
23
  terminal: "terminal";
24
24
  stop: "stop";
25
25
  processStart: "processStart";
@@ -40,6 +40,12 @@ export declare const computerGrantSchema: z.ZodObject<{
40
40
  exec: "exec";
41
41
  start: "start";
42
42
  delete: "delete";
43
+ mkdir: "mkdir";
44
+ backing: "backing";
45
+ bash: "bash";
46
+ exposePort: "exposePort";
47
+ listPorts: "listPorts";
48
+ unexposePort: "unexposePort";
43
49
  "files.read": "files.read";
44
50
  "files.readBytes": "files.readBytes";
45
51
  "files.write": "files.write";
@@ -49,12 +55,6 @@ export declare const computerGrantSchema: z.ZodObject<{
49
55
  "files.grep": "files.grep";
50
56
  "files.edit": "files.edit";
51
57
  "files.patch": "files.patch";
52
- mkdir: "mkdir";
53
- bash: "bash";
54
- backing: "backing";
55
- exposePort: "exposePort";
56
- listPorts: "listPorts";
57
- unexposePort: "unexposePort";
58
58
  terminal: "terminal";
59
59
  stop: "stop";
60
60
  processStart: "processStart";
@@ -82,12 +82,12 @@ export declare const computerStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
82
82
  }, z.core.$strict>], "state">;
83
83
  export type ComputerRuntimeState = z.output<typeof computerStateSchema>;
84
84
  /** Hosted fallback order, with the same bound and identity law as the retained owner. */
85
- export declare const hostedBackingsSchema: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
85
+ export declare const hostedBackingsSchema: z.ZodLazy<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
86
86
  kind: z.ZodLiteral<"platform-container">;
87
87
  }, z.core.$strict>, z.ZodObject<{
88
88
  kind: z.ZodLiteral<"on-box-vm">;
89
- hostId: z.ZodString;
90
- }, z.core.$strict>], "kind">>;
89
+ hostId: z.ZodLazy<z.ZodString>;
90
+ }, z.core.$strict>], "kind">>>;
91
91
  /** The host delivers this complete revision to its Computer runtime. */
92
92
  export type ComputerBackendsProjection = Readonly<{
93
93
  computerId: string;
@@ -95,15 +95,15 @@ export type ComputerBackendsProjection = Readonly<{
95
95
  backends: readonly AttachedBackend[];
96
96
  }>;
97
97
  /** An explicit override, or the fleet default when no Computer is selected. */
98
- export declare const computerBackingChangeSchema: z.ZodObject<{
98
+ export declare const computerBackingChangeSchema: z.ZodLazy<z.ZodObject<{
99
99
  computerId: z.ZodOptional<z.ZodString>;
100
- backings: z.ZodNullable<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
100
+ backings: z.ZodNullable<z.ZodLazy<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
101
101
  kind: z.ZodLiteral<"platform-container">;
102
102
  }, z.core.$strict>, z.ZodObject<{
103
103
  kind: z.ZodLiteral<"on-box-vm">;
104
- hostId: z.ZodString;
105
- }, z.core.$strict>], "kind">>>;
106
- }, z.core.$strict>;
104
+ hostId: z.ZodLazy<z.ZodString>;
105
+ }, z.core.$strict>], "kind">>>>;
106
+ }, z.core.$strict>>;
107
107
  export type ComputerBackingChange = Readonly<{
108
108
  computerId?: string | undefined;
109
109
  backings: readonly z.output<typeof hostedBackingSchema>[] | null;
@@ -117,15 +117,15 @@ export declare class ComputerBackingError extends Error {
117
117
  export declare const localBackendAttachSchema: z.ZodObject<{
118
118
  computerId: z.ZodString;
119
119
  publicKey: z.ZodBase64;
120
- hostLabel: z.ZodString;
121
- folderLabel: z.ZodString;
120
+ hostLabel: z.ZodLazy<z.ZodString>;
121
+ folderLabel: z.ZodLazy<z.ZodString>;
122
122
  requestId: z.ZodString;
123
123
  }, z.core.$strict>;
124
124
  export type LocalBackendAttachInput = z.output<typeof localBackendAttachSchema>;
125
- export declare const localBackendDetachSchema: z.ZodObject<{
125
+ export declare const localBackendDetachSchema: z.ZodLazy<z.ZodObject<{
126
126
  computerId: z.ZodString;
127
127
  backendId: z.ZodString;
128
- }, z.core.$strict>;
128
+ }, z.core.$strict>>;
129
129
  /** Recover a lost attach response without retaining the device key. */
130
130
  export declare const localBackendDetachRequestSchema: z.ZodObject<{
131
131
  computerId: z.ZodString;
@@ -139,7 +139,7 @@ export declare const computerUsageIntervalSchema: z.ZodObject<{
139
139
  kind: z.ZodLiteral<"platform-container">;
140
140
  }, z.core.$strict>, z.ZodObject<{
141
141
  kind: z.ZodLiteral<"on-box-vm">;
142
- hostId: z.ZodString;
142
+ hostId: z.ZodLazy<z.ZodString>;
143
143
  }, z.core.$strict>], "kind">;
144
144
  startedAt: z.ZodNumber;
145
145
  observedAt: z.ZodNumber;
@@ -159,7 +159,7 @@ export type ComputerUsageInterval = Readonly<{
159
159
  ended: boolean;
160
160
  }>;
161
161
  /** Observations, not billing decisions. The host owns delivery and settlement. */
162
- export declare const computerFactSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
162
+ export declare const computerFactSchema: z.ZodLazy<z.ZodDiscriminatedUnion<[z.ZodObject<{
163
163
  kind: z.ZodLiteral<"process.exited">;
164
164
  requestId: z.ZodString;
165
165
  exitCode: z.ZodNullable<z.ZodNumber>;
@@ -213,7 +213,7 @@ export declare const computerFactSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
213
213
  kind: z.ZodLiteral<"platform-container">;
214
214
  }, z.core.$strict>, z.ZodObject<{
215
215
  kind: z.ZodLiteral<"on-box-vm">;
216
- hostId: z.ZodString;
216
+ hostId: z.ZodLazy<z.ZodString>;
217
217
  }, z.core.$strict>], "kind">;
218
218
  startedAt: z.ZodNumber;
219
219
  observedAt: z.ZodNumber;
@@ -224,5 +224,5 @@ export declare const computerFactSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
224
224
  actor: z.ZodString;
225
225
  via: z.ZodOptional<z.ZodString>;
226
226
  }, z.core.$strict>;
227
- }, z.core.$strict>], "kind">;
227
+ }, z.core.$strict>], "kind">>;
228
228
  export type ComputerFact = z.output<typeof computerFactSchema>;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { computerOperationSchema, computerValueSchema, pathSchema, } from "../operation-contract.js";
2
+ import { computerOperationSchema, computerValueSchema, pathSchema, } from "@fungi.computer/computer-contract";
3
3
  import { attachedBackendId, hostedBackingSchema, localBackendSchema, } from "../computer-backends.js";
4
4
  /** Operations the host may admit on one Computer. */
5
5
  export const computerActionSchema = z.enum([
@@ -39,15 +39,15 @@ export const computerStateSchema = z.discriminatedUnion("state", [
39
39
  }),
40
40
  ]);
41
41
  /** Hosted fallback order, with the same bound and identity law as the retained owner. */
42
- export const hostedBackingsSchema = z
42
+ export const hostedBackingsSchema = z.lazy(() => z
43
43
  .array(hostedBackingSchema)
44
44
  .max(4)
45
- .refine((backings) => new Set(backings.map(attachedBackendId)).size === backings.length, "hosted backings must be distinct");
45
+ .refine((backings) => new Set(backings.map(attachedBackendId)).size === backings.length, "hosted backings must be distinct"));
46
46
  /** An explicit override, or the fleet default when no Computer is selected. */
47
- export const computerBackingChangeSchema = z.strictObject({
47
+ export const computerBackingChangeSchema = z.lazy(() => z.strictObject({
48
48
  computerId: z.string().min(1).max(256).optional(),
49
49
  backings: hostedBackingsSchema.nullable(),
50
- });
50
+ }));
51
51
  /** A backing operation was refused without changing the stored decision. */
52
52
  export class ComputerBackingError extends Error {
53
53
  code;
@@ -65,10 +65,10 @@ export const localBackendAttachSchema = z.strictObject({
65
65
  folderLabel: localBackendSchema.shape.folderLabel,
66
66
  requestId: z.string().min(1).max(256),
67
67
  });
68
- export const localBackendDetachSchema = z.strictObject({
68
+ export const localBackendDetachSchema = z.lazy(() => z.strictObject({
69
69
  computerId: z.string().min(1).max(256),
70
70
  backendId: z.string().regex(/^local-[a-z2-7]{16}$/),
71
- });
71
+ }));
72
72
  /** Recover a lost attach response without retaining the device key. */
73
73
  export const localBackendDetachRequestSchema = z.strictObject({
74
74
  computerId: localBackendAttachSchema.shape.computerId,
@@ -89,7 +89,7 @@ export const computerUsageIntervalSchema = z.strictObject({
89
89
  });
90
90
  const timestamp = z.number().int().nonnegative();
91
91
  /** Observations, not billing decisions. The host owns delivery and settlement. */
92
- export const computerFactSchema = z.discriminatedUnion("kind", [
92
+ export const computerFactSchema = z.lazy(() => z.discriminatedUnion("kind", [
93
93
  z.strictObject({
94
94
  ...factIdentity,
95
95
  kind: z.literal("process.exited"),
@@ -124,4 +124,4 @@ export const computerFactSchema = z.discriminatedUnion("kind", [
124
124
  kind: z.literal("interval"),
125
125
  ...computerUsageIntervalSchema.shape,
126
126
  }),
127
- ]);
127
+ ]));