@themoltnet/sandbox-gondolin 0.1.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.
@@ -0,0 +1,364 @@
1
+ import { MemoryProvider } from '@earendil-works/gondolin';
2
+ import { VirtualFileHandle } from '@earendil-works/gondolin';
3
+ import { VM } from '@earendil-works/gondolin';
4
+
5
+ export declare function abortableResource<T>(opts: AbortableResourceOptions<T>): Promise<T>;
6
+
7
+ declare interface AbortableResourceOptions<T> {
8
+ promise: PromiseLike<T>;
9
+ signal: AbortSignal | undefined;
10
+ label: string;
11
+ cleanup: (resource: T) => Promise<void> | void;
12
+ onCleanupError?: (err: unknown) => void;
13
+ }
14
+
15
+ /**
16
+ * Apply agent env vars to the host process, mirroring `moltnet start`.
17
+ * Resolves relative paths (e.g. GIT_CONFIG_GLOBAL) against the repo root.
18
+ */
19
+ export declare function activateAgentEnv(agentEnv: Record<string, string | undefined>, repoRoot: string): void;
20
+
21
+ export declare function assertGuestEnvironmentBoundary(options: {
22
+ guestCredentialMode: GuestCredentialMode;
23
+ forwardEnv?: readonly string[];
24
+ sandboxEnv?: Readonly<Record<string, string>>;
25
+ }): void;
26
+
27
+ /** @deprecated Prefer assertGuestEnvironmentBoundary for mode-aware checks. */
28
+ export declare function assertHostAuthenticatedGuestEnvironment(options: {
29
+ forwardEnv?: readonly string[];
30
+ sandboxEnv?: Readonly<Record<string, string>>;
31
+ }): void;
32
+
33
+ export declare class AutoParentMemoryProvider extends MemoryProvider {
34
+ private ensureParentDir;
35
+ mkdir(pathname: string, options?: object): Promise<void | string>;
36
+ mkdirSync(pathname: string, options?: object): void | string;
37
+ open(pathname: string, flags: string, mode?: number): Promise<VirtualFileHandle>;
38
+ openSync(pathname: string, flags: string, mode?: number): VirtualFileHandle;
39
+ }
40
+
41
+ export declare function delay(ms: number, signal: AbortSignal | undefined, label: string): Promise<void>;
42
+
43
+ /**
44
+ * Ensure a cached snapshot exists, building one if needed.
45
+ * Returns the absolute path to the qcow2 checkpoint file.
46
+ */
47
+ export declare function ensureSnapshot(options?: EnsureSnapshotOptions): Promise<string>;
48
+
49
+ export declare interface EnsureSnapshotOptions {
50
+ config?: SnapshotConfig;
51
+ onProgress?: (message: string) => void;
52
+ /** Max number of old snapshots to keep (default 1). */
53
+ maxCached?: number;
54
+ }
55
+
56
+ /**
57
+ * Resolve the main worktree root (where .moltnet/ lives — it's untracked,
58
+ * only exists in the main worktree, not in git worktrees).
59
+ */
60
+ export declare function findMainWorktree(startPath?: string): string;
61
+
62
+ /** Commands guaranteed by the base Gondolin snapshot. */
63
+ export declare const GONDOLIN_BASE_EXECUTABLES: readonly string[];
64
+
65
+ /**
66
+ * Memory-backed VFS mount used by the daemon to inject task context
67
+ * (#943 slice 1.5). This is a separate top-level mount because Gondolin
68
+ * mounts can't nest. The agent's Gondolin-bound Read tool accepts paths
69
+ * under this prefix (see toGuestPath in tool-operations.ts).
70
+ *
71
+ * Why MemoryProvider rather than a path under the workspace mount:
72
+ * - Injected task context is ephemeral by intent: per-task-attempt input
73
+ * scoped to the VM lifetime. MemoryProvider models that exactly —
74
+ * in-memory, per-VM-instance, zero host artefacts, automatic
75
+ * cleanup on VM close.
76
+ * - Writing under the workspace mount fails in worktrees because we symlink
77
+ * `.moltnet/` to the main repo (so credentials are reachable from
78
+ * worktrees), and Gondolin's RealFSProvider correctly refuses to
79
+ * create paths whose ancestors' realpath escapes the mount root.
80
+ * That refusal is a deliberate sandbox-escape protection, not a
81
+ * bug. See diary semantic entry cd27d9d3-efdc-4aec-ac0d-5fd8ce258d1f
82
+ * and episodic 7affbfeb-18a2-4963-aeac-c177eb2afa2d for the full
83
+ * investigation and the alternatives we rejected.
84
+ */
85
+ export declare const GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
86
+
87
+ /** @deprecated Use GUEST_TASK_CONTEXT_MOUNT. */
88
+ export declare const GUEST_TASK_SKILLS_MOUNT = "/moltnet-task-context";
89
+
90
+ export declare type GuestCredentialMode = 'guest-config' | 'host-authenticated';
91
+
92
+ export declare class GuestEnvironmentBoundaryError extends Error {
93
+ readonly refusedNames: readonly string[];
94
+ constructor(refusedNames: readonly string[]);
95
+ }
96
+
97
+ /**
98
+ * Check containment for already-resolved lexical or real paths.
99
+ *
100
+ * Callers that accept untrusted paths must resolve/realpath at their I/O
101
+ * boundary first; keeping the platform-specific relative-path rule here avoids
102
+ * subtly different `..` and absolute-path handling across runtime cleanup,
103
+ * session sync, and artifact staging.
104
+ */
105
+ export declare function isResolvedPathInsideRoot(path: string, root: string): boolean;
106
+
107
+ export declare function loadCredentials(agentDir: string, mode?: GuestCredentialMode, onDiagnostic?: (diagnostic: VmDiagnostic) => void, providerAuth?: ProviderAuthSource): VmCredentials;
108
+
109
+ export declare interface ManagedVm {
110
+ vm: VM;
111
+ credentials: VmCredentials;
112
+ mountPath: string;
113
+ guestWorkspace: string;
114
+ agentDir: string;
115
+ }
116
+
117
+ export declare interface ProviderAuthSource {
118
+ /** Read the auth blob from the host, or return null when absent. */
119
+ load(): string | null;
120
+ /** Absolute guest path to write the blob to (mode 0600). */
121
+ guestPath: string;
122
+ }
123
+
124
+ export declare function resolveVfsShadowConfig(config: SandboxConfig | undefined): {
125
+ mode: 'none';
126
+ patterns: [];
127
+ } | {
128
+ mode: 'deny' | 'tmpfs';
129
+ patterns: string[];
130
+ };
131
+
132
+ export declare function resolveVmAgentDir(config: {
133
+ agentName: string;
134
+ agentRootDir?: string;
135
+ }): string;
136
+
137
+ export declare interface ResumeCommand {
138
+ /** Shell command, same semantics as the string form. */
139
+ run: string;
140
+ /** Optional generic runtime predicate for whether this step should run. */
141
+ when?: ResumeCommandWhen;
142
+ /** Additional attempts on non-zero exit. Default 0. */
143
+ retries?: number;
144
+ /** Linear backoff between attempts in ms. Delay before attempt N+1 is
145
+ * `(N + 1) * retryBackoffMs` (so 2s, 4s, … with the default). */
146
+ retryBackoffMs?: number;
147
+ }
148
+
149
+ /** Structured form of a resume command with optional retry policy. */
150
+ export declare interface ResumeCommandWhen {
151
+ /**
152
+ * Effective workspace mode(s) that should run this command.
153
+ * Evaluated by the runtime from the mounted workspace shape rather than
154
+ * from task type semantics.
155
+ */
156
+ workspaceMode?: ('shared_mount' | 'dedicated_worktree' | 'scratch_mount')[];
157
+ }
158
+
159
+ /**
160
+ * Resume a VM from a checkpoint, inject credentials, configure egress +
161
+ * TLS. Returns the managed VM handle.
162
+ */
163
+ export declare function resumeVm(config: VmConfig): Promise<ManagedVm>;
164
+
165
+ /**
166
+ * Rewrite host-absolute paths inside an agent gitconfig to VM-local
167
+ * equivalents before injecting it into the guest.
168
+ *
169
+ * Two rewrites:
170
+ * - `signingKey = <host path>` → `<vmSshDir>/id_ed25519`
171
+ * - `... credential-helper --credentials <host moltnet.json>`
172
+ * → `<vmAgentDir>/moltnet.json`
173
+ *
174
+ * The credential-helper line is generated host-side by `moltnet github setup`
175
+ * with a host-absolute `--credentials` path; inside the guest that path is
176
+ * invalid, so it must point at the VM-side moltnet.json. The `insteadOf`
177
+ * rewrite rule and every other line are workspace-independent and pass through
178
+ * unchanged. A gitconfig without a credential helper is rewritten only for
179
+ * `signingKey`.
180
+ *
181
+ * This is the single source of truth for git push auth in the guest: the
182
+ * injected gitconfig carries the tokenless mint-on-demand helper, so the VM
183
+ * no longer hand-rolls a credential-helper script or runs an imperative
184
+ * `git config --global ... insteadOf` against the guest $HOME.
185
+ */
186
+ export declare function rewriteGitconfigPaths(gitconfig: string, vmSshDir: string, vmAgentDir: string): string;
187
+
188
+ /**
189
+ * Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
190
+ *
191
+ * Fields rewritten:
192
+ * ssh.private_key_path → <vmSshDir>/<basename of original>
193
+ * ssh.public_key_path → <vmSshDir>/<basename of original>
194
+ * git.config_path → <vmAgentDir>/gitconfig
195
+ * github.private_key_path → <vmAgentDir>/<pemFilename> (if present)
196
+ *
197
+ * All other fields are passed through unchanged.
198
+ * Throws if moltnetJson is not valid JSON — callers must not inject a broken
199
+ * moltnet.json into the guest.
200
+ */
201
+ export declare function rewriteMoltnetJsonPaths(moltnetJson: string, vmAgentDir: string, vmSshDir: string, githubAppPemFilename: string | null): string;
202
+
203
+ export declare interface SandboxConfig {
204
+ /**
205
+ * Operator-owned snapshot build settings. Runtime profiles must never
206
+ * populate this field.
207
+ */
208
+ snapshot?: {
209
+ /** Shell commands to run after the base setup. */
210
+ setupCommands?: string[];
211
+ /** Additional hosts to allow network access during build. */
212
+ allowedHosts?: string[];
213
+ /** Overlay disk size (default '3G'). */
214
+ overlaySize?: string;
215
+ };
216
+ /** Runtime network egress policy. Separate from snapshot build access. */
217
+ network?: {
218
+ /** Additional host patterns allowed while the VM is running.
219
+ * Internal and private address resolution remains blocked. */
220
+ allowedHosts?: string[];
221
+ /** Host patterns explicitly allowed to resolve to internal/private IPs. */
222
+ allowedInternalHosts?: string[];
223
+ };
224
+ /** Operator-owned shell commands to run every VM resume, after platform setup
225
+ * (TLS, DNS, git safe.directory, tmpfs node_modules) and before
226
+ * the agent session starts. Use for per-session bootstrap that
227
+ * doesn't belong baked into the snapshot.
228
+ *
229
+ * Not included in the snapshot cache key — changes here apply on
230
+ * every resume without triggering a snapshot rebuild. Each command
231
+ * runs in a fresh shell with `set -eu` and `set -o pipefail`; a
232
+ * non-zero exit (including from any segment of a pipeline) aborts
233
+ * resume with the failing command's stderr/stdout tail.
234
+ *
235
+ * Each entry is either a raw string (no retries) or an object
236
+ * `{ run, when?, retries?, retryBackoffMs? }`. `when` gates the
237
+ * command on generic runtime properties such as effective
238
+ * `workspaceMode`; this keeps sandbox policy decoupled from task
239
+ * types. `retries` is the number of ADDITIONAL attempts after the
240
+ * first failure (default 0 = no retry). Use for steps that hit the
241
+ * network and may legitimately race DHCP/registry availability on a
242
+ * fresh resume (e.g. `pnpm install`). The wrapped command must be
243
+ * idempotent. */
244
+ resumeCommands?: (string | ResumeCommand)[];
245
+ /** VFS shadow settings — hide host paths from the guest. */
246
+ vfs?: {
247
+ /** Paths (relative to workspace root) to shadow from the host mount. */
248
+ shadow?: string[];
249
+ /** What to do with writes to shadowed paths: 'deny' or 'tmpfs' (default 'tmpfs'). */
250
+ shadowMode?: 'deny' | 'tmpfs';
251
+ };
252
+ /** Environment variable overrides for the guest VM (applied on top of defaults). */
253
+ env?: Record<string, string>;
254
+ /** Host-side escape hatch policy. Applies only to `moltnet_host_exec`. */
255
+ hostExec?: {
256
+ /**
257
+ * `true` auto-approves every allowed executable. An array auto-approves
258
+ * only commands matching one of the executable/argument rules.
259
+ */
260
+ autoApprove?: boolean | {
261
+ executable: string;
262
+ argsPrefix?: string[];
263
+ argsContains?: string[];
264
+ argsExcludes?: string[];
265
+ }[];
266
+ };
267
+ /** VM resource allocation. */
268
+ resources?: {
269
+ /** Memory size in qemu syntax (default '1G'). */
270
+ memory?: string;
271
+ /** CPU count (default 2). */
272
+ cpus?: number;
273
+ };
274
+ }
275
+
276
+ export declare function shouldRunResumeCommand(entry: string | ResumeCommand, ctx: {
277
+ workspaceMode: 'shared_mount' | 'dedicated_worktree' | 'scratch_mount';
278
+ }): boolean;
279
+
280
+ export declare function shouldShadowNodeModulesPath(pathname: string): boolean;
281
+
282
+ /** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
283
+ export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
284
+
285
+ export declare function throwIfAborted(signal: AbortSignal | undefined, label: string): void;
286
+
287
+ export declare interface VmConfig {
288
+ /** Absolute path to the qcow2 checkpoint. */
289
+ checkpointPath: string;
290
+ /** MoltNet agent name (used to resolve credentials). */
291
+ agentName: string;
292
+ /**
293
+ * Trust boundary for guest credentials. `guest-config` injects the complete
294
+ * legacy agent directory. `host-authenticated` never reads or injects it and
295
+ * relies exclusively on the supplied host-side Agent for MoltNet operations.
296
+ */
297
+ guestCredentialMode?: GuestCredentialMode;
298
+ /**
299
+ * Host root that owns `.moltnet/<agentName>/`.
300
+ *
301
+ * Defaults to the main git worktree for backwards compatibility. Daemon
302
+ * callers pass the sandbox root so non-git scratch/shared tasks can boot.
303
+ */
304
+ agentRootDir?: string;
305
+ /** Host directory to mount into the VM. */
306
+ mountPath: string;
307
+ /** Effective workspace shape selected by the caller. */
308
+ workspaceMode?: 'shared_mount' | 'dedicated_worktree' | 'scratch_mount';
309
+ /** Additional hosts to allow in egress policy. */
310
+ extraAllowedHosts?: string[];
311
+ /** Full sandbox config (vfs shadows, env overrides). */
312
+ sandboxConfig?: SandboxConfig;
313
+ /**
314
+ * Host environment variable names to copy into the VM process.
315
+ *
316
+ * Runtime profiles use this for provider API keys: `requiredEnv` proves the
317
+ * daemon host has the secret, and this allowlist forwards only those names
318
+ * into the guest without storing secret values in the profile.
319
+ */
320
+ forwardEnv?: string[];
321
+ /** Structured credential-boundary diagnostics for daemon loggers. */
322
+ onDiagnostic?: (diagnostic: VmDiagnostic) => void;
323
+ /** Abort resume/setup work, closing any live VM owned by resumeVm. */
324
+ signal?: AbortSignal;
325
+ /**
326
+ * Coding-agent provider authentication to place in the guest (for example
327
+ * Pi's `auth.json`). The sandbox knows nothing about the provider: the
328
+ * runtime above it supplies the loader and the guest path. When omitted,
329
+ * nothing is written and the guest relies on forwarded env providers.
330
+ */
331
+ providerAuth?: ProviderAuthSource;
332
+ }
333
+
334
+ export declare interface VmCredentials {
335
+ /** Empty in host-authenticated mode; retained as strings for API stability. */
336
+ moltnetJson: string;
337
+ /** Empty in host-authenticated mode; retained as strings for API stability. */
338
+ agentEnvRaw: string;
339
+ /**
340
+ * Provider auth blob supplied by `VmConfig.providerAuth`, or null when the
341
+ * runtime supplied none or the host file is absent — the guest then relies
342
+ * on env-var providers (`ANTHROPIC_API_KEY`, etc.) carried via `agentEnv`
343
+ * and the forwarded host environment instead. CI uses this path.
344
+ */
345
+ providerAuthJson: string | null;
346
+ agentEnv: Record<string, string | undefined>;
347
+ gitconfig: string | null;
348
+ sshPrivateKey: string | null;
349
+ sshPublicKey: string | null;
350
+ allowedSigners: string | null;
351
+ /** Raw PEM content of the GitHub App private key, or null if not configured. */
352
+ githubAppPem: string | null;
353
+ /** VM-local filename for the GitHub App PEM (basename of host path), or null. */
354
+ githubAppPemFilename: string | null;
355
+ }
356
+
357
+ export declare interface VmDiagnostic {
358
+ event: 'vm.credentials.mode' | 'vm.credentials.github_key_missing';
359
+ level: 'info' | 'warning';
360
+ message: string;
361
+ credentialMode: GuestCredentialMode;
362
+ }
363
+
364
+ export { }