@fungi.computer/computer 0.2.1 → 0.3.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.
@@ -10,6 +10,8 @@
10
10
  // container behind. Any other host builds its own target natively through
11
11
  // rustup. One immutable artifact per pin, wrapper and target is cached under
12
12
  // ${XDG_CACHE_HOME:-~/.cache}/fungi/native-patch and restored on later calls.
13
+ // Turbo's strict environment lends only tool homes, so under Turbo the cache
14
+ // follows HOME; a caller that wants a private cache sets a private HOME.
13
15
  import { spawn } from "node:child_process";
14
16
  import { homedir } from "node:os";
15
17
  import { resolve } from "node:path";
@@ -50,7 +50,7 @@ async function digest(path) {
50
50
  .digest("hex");
51
51
  }
52
52
 
53
- /** One immutable artifact per Codex pin/target, shared by every checkout and updater. */
53
+ /** One immutable artifact per Codex pin/target/wrapper, shared by every checkout and updater. */
54
54
  export async function ensureNativePatchArtifact({
55
55
  cacheRoot,
56
56
  pin: inputPin,
@@ -62,7 +62,7 @@ export async function ensureNativePatchArtifact({
62
62
  const pin = pinSchema.parse(inputPin);
63
63
  const target = targetSchema.parse(inputTarget);
64
64
  const wrapperSha256 = await digest(wrapper);
65
- const key = join(cacheRoot, pin.version, target);
65
+ const key = join(cacheRoot, pin.version, target, wrapperSha256);
66
66
  const artifact = join(key, "artifact");
67
67
  await mkdir(key, { recursive: true, mode: 0o700 });
68
68
  async function published() {
@@ -82,7 +82,7 @@ export async function ensureNativePatchArtifact({
82
82
  receipt.wrapperSha256 !== wrapperSha256
83
83
  )
84
84
  throw new Error(
85
- `Cached patch artifact does not match this pin or wrapper: ${artifact}. Use a new pin before rebuilding.`,
85
+ `Cached patch artifact does not match this pin or wrapper: ${artifact}. The artifact was not used or rebuilt.`,
86
86
  );
87
87
  if (
88
88
  (await digest(join(artifact, "fungi-apply-patch"))) !==
@@ -114,7 +114,11 @@ const nativePathFlags = [
114
114
  .map((flag) => JSON.stringify(flag))
115
115
  .join(" ");
116
116
  ensureNativeHostToolchain({ toolchain: codexPatchPin.rustToolchain });
117
- const { command: cargoCommand, args: cargoArgs } = selectCargoInvocation({
117
+ const {
118
+ command: cargoCommand,
119
+ args: cargoArgs,
120
+ env: cargoEnv,
121
+ } = selectCargoInvocation({
118
122
  toolchain: codexPatchPin.rustToolchain,
119
123
  cargoArgs: [
120
124
  crossMac ? "zigbuild" : "build",
@@ -139,6 +143,7 @@ const cargo = spawnSync(cargoCommand, cargoArgs, {
139
143
  maxBuffer: 64 * 1024 * 1024,
140
144
  env: {
141
145
  ...process.env,
146
+ ...cargoEnv,
142
147
  CARGO_TARGET_DIR: resolve(scratch, "target"),
143
148
  CARGO_ENCODED_RUSTFLAGS: [
144
149
  `--remap-path-prefix=${scratch}=/build`,
@@ -242,6 +242,20 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
242
242
  }
243
243
  }
244
244
  }
245
+ // Inspection uses the same upstream parser and admission as application.
246
+ // The host uses these NUL-delimited paths to enforce protected mounts
247
+ // before offering an approval and again immediately before dispatch.
248
+ if std::env::args().nth(1).as_deref() == Some("--inspect") {
249
+ use std::io::Write;
250
+ let mut output = io::stdout().lock();
251
+ let mut paths: Vec<_> = paths.into_iter().collect();
252
+ paths.sort();
253
+ for path in paths {
254
+ output.write_all(path.as_os_str().as_encoded_bytes())?;
255
+ output.write_all(&[0])?;
256
+ }
257
+ return Ok(());
258
+ }
245
259
  let cwd = PathUri::from_abs_path(&AbsolutePathBuf::try_from(PathBuf::from(&root))?);
246
260
  let mode = ApplyPatchFileUpdateMode::PreserveLineEndings;
247
261
  let diagnostic_hunks = parsed.hunks.clone();
@@ -4,16 +4,36 @@
4
4
  // image's builder stage) installs Rust through rustup. A non-rustup `cargo`
5
5
  // earlier on `PATH` (for example Homebrew's) rejects the `+toolchain`
6
6
  // directive, so the build asks rustup's ordinary interface for the pinned
7
- // toolchain's `cargo` explicitly.
7
+ // toolchain's `cargo` explicitly. Cargo itself still resolves `rustc` from
8
+ // `PATH`, so a Homebrew `rustc` listed first would compile with the wrong
9
+ // compiler; the invocation pins `RUSTC` to the toolchain's own compiler too.
8
10
  import { spawnSync } from "node:child_process";
9
11
 
10
12
  /**
11
- * Builds the `{ command, args }` cargo invocation for the pinned toolchain.
13
+ * Builds the `{ command, args, env }` cargo invocation for the pinned
14
+ * toolchain. `env` carries `RUSTC` and must be merged into the child's
15
+ * environment.
12
16
  *
13
17
  * @param {{ toolchain: string, cargoArgs: string[] }} options
14
18
  */
15
19
  export function selectCargoInvocation({ toolchain, cargoArgs }) {
16
- return { command: "rustup", args: ["run", toolchain, "cargo", ...cargoArgs] };
20
+ const which = spawnSync(
21
+ "rustup",
22
+ ["which", "--toolchain", toolchain, "rustc"],
23
+ {
24
+ encoding: "utf8",
25
+ },
26
+ );
27
+ if (which.error) throw which.error;
28
+ if (which.status !== 0)
29
+ throw new Error(
30
+ `rustup could not locate the Rust ${toolchain} toolchain's rustc: ${which.stderr.trim()}`,
31
+ );
32
+ return {
33
+ command: "rustup",
34
+ args: ["run", toolchain, "cargo", ...cargoArgs],
35
+ env: { RUSTC: which.stdout.trim() },
36
+ };
17
37
  }
18
38
 
19
39
  /**
@@ -8,9 +8,6 @@ export declare const computerActionSchema: z.ZodEnum<{
8
8
  mkdir: "mkdir";
9
9
  backing: "backing";
10
10
  bash: "bash";
11
- exposePort: "exposePort";
12
- listPorts: "listPorts";
13
- unexposePort: "unexposePort";
14
11
  "files.read": "files.read";
15
12
  "files.readBytes": "files.readBytes";
16
13
  "files.write": "files.write";
@@ -25,6 +22,9 @@ export declare const computerActionSchema: z.ZodEnum<{
25
22
  processStart: "processStart";
26
23
  processList: "processList";
27
24
  processKill: "processKill";
25
+ exposePort: "exposePort";
26
+ unexposePort: "unexposePort";
27
+ listPorts: "listPorts";
28
28
  }>;
29
29
  export type ComputerAction = z.output<typeof computerActionSchema>;
30
30
  /** Opaque host identities; the fleet does not derive authority from their text. */
@@ -43,9 +43,6 @@ export declare const computerGrantSchema: z.ZodObject<{
43
43
  mkdir: "mkdir";
44
44
  backing: "backing";
45
45
  bash: "bash";
46
- exposePort: "exposePort";
47
- listPorts: "listPorts";
48
- unexposePort: "unexposePort";
49
46
  "files.read": "files.read";
50
47
  "files.readBytes": "files.readBytes";
51
48
  "files.write": "files.write";
@@ -60,6 +57,9 @@ export declare const computerGrantSchema: z.ZodObject<{
60
57
  processStart: "processStart";
61
58
  processList: "processList";
62
59
  processKill: "processKill";
60
+ exposePort: "exposePort";
61
+ unexposePort: "unexposePort";
62
+ listPorts: "listPorts";
63
63
  }>>>;
64
64
  expiresAt: z.ZodNumber;
65
65
  egress: z.ZodOptional<z.ZodJSONSchema>;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createLocalBackendReplayStore } from "./local-backend-replay.js";
2
- import { Workspace, WorkspaceProxy, WorkspaceServiceProxy, type WorkspaceStubHost } from "@fungi.computer/cloudflare-computer";
2
+ import { Workspace, WorkspaceProxy, WorkspaceServiceProxy, type WorkspaceStub } from "@fungi.computer/cloudflare-computer";
3
3
  import { type WorkerShellBackendOptions } from "@fungi.computer/cloudflare-computer/backends/worker-shell";
4
4
  import { type IWorkspaceContainerAPI } from "@fungi.computer/cloudflare-computer/backends/container";
5
5
  import { DurableObject } from "cloudflare:workers";
@@ -10,8 +10,13 @@ import { createWorkspaceFilesPort } from "./files.js";
10
10
  import { OnBoxVmContainerHost, type OnBoxVmEnv, type OnBoxVmPlacement } from "./on-box-vm.js";
11
11
  import { type AttachedBackend, type AttachedBackendView, type BackendNeed, type BackendRoute, type ContainerRuntimeStart, type HostedBacking } from "./computer-backends.js";
12
12
  import { LocalBackendConnections } from "./local-backend.js";
13
+ /** Files needs only the native Workspace lease and its atomic replacement door. */
14
+ export type ComputerFilesRpc = Readonly<{
15
+ __getWorkspaceStub(): Promise<WorkspaceStub>;
16
+ replaceFileIfUnchanged(input: ComputerFileReplacement): Promise<ComputerFileReplacementResult>;
17
+ }>;
13
18
  /** The exact RPC surface of one Computer Durable Object. */
14
- export type ComputerRpc = WorkspaceStubHost & Readonly<{
19
+ export type ComputerRpc = ComputerFilesRpc & Readonly<{
15
20
  getCapabilities: ComputerDO["getCapabilities"];
16
21
  call: (capabilityId: ComputerValue, input: ComputerValue) => Promise<ComputerValue>;
17
22
  readFile: (input: ComputerFileReadInput) => Promise<ReadableStream<Uint8Array>>;
@@ -31,7 +36,6 @@ export type ComputerRpc = WorkspaceStubHost & Readonly<{
31
36
  port: number;
32
37
  removed: boolean;
33
38
  }>;
34
- replaceFileIfUnchanged: (input: ComputerFileReplacement) => Promise<ComputerFileReplacementResult>;
35
39
  }>;
36
40
  /** The Cloudflare binding projection needed to select one named Computer. */
37
41
  export type ComputerNamespace = Readonly<{
@@ -120,7 +124,7 @@ declare class ContainerComputer extends ContainerComputerBase {
120
124
  */
121
125
  protected readyWorkspace(): Promise<Workspace>;
122
126
  /** `getWorkspace(stub)` reaches the current generation through this door. */
123
- __getWorkspaceStub(): Promise<import("@fungi.computer/cloudflare-computer").WorkspaceStub>;
127
+ __getWorkspaceStub(): Promise<WorkspaceStub>;
124
128
  /**
125
129
  * Backend doors. An attached machine dials in on its public connect path.
126
130
  * A container backend's `/ws` upgrade and egress: the platform container's
@@ -130,7 +134,7 @@ declare class ContainerComputer extends ContainerComputerBase {
130
134
  fetch(request: Request): Promise<Response>;
131
135
  }
132
136
  /** One upstream Cloudflare Workspace that advertises and runs its capabilities. */
133
- export declare class ComputerDO extends ContainerComputer {
137
+ export declare class ComputerDO extends ContainerComputer implements ComputerFilesRpc {
134
138
  #private;
135
139
  constructor(ctx: DurableObjectState, env: ComputerEnv);
136
140
  /** Host-admitted settings write; egress reads the current durable value. */
@@ -294,7 +298,7 @@ export { computerContainerSleepAfterMs } from "./container-policy.js";
294
298
  export * from "./computer-backends.js";
295
299
  export { LocalBackendOfflineError, localBackendAudience, localBackendDialPath, verifyLocalBackendDial, } from "./local-backend.js";
296
300
  /** Keep each native Workspace lease inside the operation that uses it. */
297
- export declare function createComputerFilesPort(computer: ComputerRpc): FilesPort;
301
+ export declare function createComputerFilesPort(computer: ComputerFilesRpc | Rpc.Provider<ComputerFilesRpc>): FilesPort;
298
302
  export { createWorkspaceFilesPort, type WorkspaceFilesOwner } from "./files.js";
299
303
  export { ComputerOperationConflictError, ComputerOperationLimitError, ComputerRequestInvalidError, } from "./operation-record.js";
300
304
  export type { ContainerComputer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/computer",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Cloudflare Computer host for one durable Fungi Computer",
6
6
  "type": "module",
@@ -35,6 +35,10 @@
35
35
  "types": "./dist/fleet/wire.d.ts",
36
36
  "import": "./dist/fleet/wire.js"
37
37
  },
38
+ "./fleet/inventory-wire": {
39
+ "types": "./dist/fleet/inventory-wire.d.ts",
40
+ "import": "./dist/fleet/inventory-wire.js"
41
+ },
38
42
  "./fleet": {
39
43
  "types": "./dist/fleet/index.d.ts",
40
44
  "import": "./dist/fleet/index.js"
@@ -54,12 +58,12 @@
54
58
  "dependencies": {
55
59
  "@fungi.computer/cairn": "0.1.0",
56
60
  "@fungi.computer/capnweb": "0.12.0",
57
- "@fungi.computer/cloudflare-computer": "0.2.1-fungi.1",
58
- "@fungi.computer/cordyceps": "0.3.1",
59
- "@fungi.computer/knapsack": "0.2.0",
60
- "@fungi.computer/seam": "0.1.0",
61
+ "@fungi.computer/cloudflare-computer": "0.2.1-fungi.2",
62
+ "@fungi.computer/cordyceps": "0.4.0",
63
+ "@fungi.computer/knapsack": "0.3.0",
64
+ "@fungi.computer/seam": "0.2.0",
61
65
  "@fungi.computer/watchdog": "0.1.0",
62
- "@fungi.computer/computer-contract": "0.0.1",
66
+ "@fungi.computer/computer-contract": "0.0.3",
63
67
  "effect": "4.0.2",
64
68
  "just-bash": "3.4.2",
65
69
  "zod": "4.4.3"
@@ -107,7 +111,7 @@
107
111
  "build": "pnpm run clean && tsc -p tsconfig.build.json && node scripts/copy-daemon.mjs",
108
112
  "build:native": "fungi-apply-patch-build --out native/bin",
109
113
  "clean": "node --input-type=module --eval \"import { rm } from 'node:fs/promises'; await rm('dist', { recursive: true, force: true });\"",
110
- "test": "pnpm run test:unit && pnpm run test:fleet-native && node test/port-proof.mjs && node test/terminal-container-proof.mjs && node test/fuse-startup-proof.mjs && node test/container-resource-proof.mjs && node test/runtime-proof.mjs && node test/process-schema-proof.mjs && node test/process-proof.mjs && node test/terminal-proof.mjs && pnpm run test:app-method-execution && pnpm run test:execution-lifecycle",
114
+ "test": "pnpm run test:unit && pnpm run test:fleet-native && node test/files-rpc.native.mjs && node test/port-proof.mjs && node test/terminal-container-proof.mjs && node test/fuse-startup-proof.mjs && node test/container-resource-proof.mjs && node test/runtime-proof.mjs && node test/process-schema-proof.mjs && node test/process-proof.mjs && node test/terminal-proof.mjs && pnpm run test:app-method-execution && pnpm run test:execution-lifecycle",
111
115
  "format:check": "node ../../tooling/repository/scripts/format-package-check.mjs",
112
116
  "lint:anti-slop": "node ../../tooling/repository/scripts/lint-package-anti-slop.mjs",
113
117
  "test:merge": "pnpm run test",