import { V as Vfs, C as Cred, a as VirtualTcpNetwork } from './contracts-BHo4LdY2.cjs'; export { m as ROOT_CRED, v as makeCred } from './contracts-BHo4LdY2.cjs'; export { M as MemoryVolume } from './memory-volume-e-uJFRnG.cjs'; /** * Open-file descriptions: the thing a descriptor points *at*. * * POSIX has three levels and collapsing any two of them produces bugs that look * like something else entirely: * * - a **pathname** is a directory entry, and may be renamed or removed; * - an **inode** is the file's identity and content, and outlives its names; * - an **open-file description** holds the offset and the status flags. * * `dup()` makes two descriptors share one description — so they share an offset. * Two separate `open()` calls on the same path make two descriptions, so their * offsets are independent. A file stays readable through an open description * after its last name is unlinked. None of that is expressible if a descriptor * is just a path and a number, which is why this layer exists rather than the * path-based {@link Vfs} calls being used directly. * * The identity used here is the volume's inode number, so a rename moves a name * without disturbing anything already open. */ /** A failure carrying the errno the guest's libc should see. */ declare class PosixError extends Error { readonly errno: number; constructor(errno: number, message?: string); } declare const O_RDONLY = 0; declare const O_WRONLY = 1; declare const O_RDWR = 2; declare const O_CREAT = 64; declare const O_EXCL = 128; declare const O_TRUNC = 512; declare const O_APPEND = 1024; declare const O_NONBLOCK = 2048; declare const O_DIRECTORY = 65536; declare const O_CLOEXEC = 524288; /** Access mode alone, with the flag bits masked off. */ declare function accessMode(flags: number): number; interface DescriptionStat { ino: number; mode: number; size: number; uid: number; gid: number; nlink: number; atimeMs: number; mtimeMs: number; ctimeMs: number; } /** * What every kind of open thing can do. * * `read` and `write` are permitted to move fewer bytes than asked for: that is * ordinary POSIX behaviour, and a guest that assumes otherwise is already * broken on Linux. */ interface OpenFileDescription { readonly kind: "file" | "dir" | "pipe" | "stream" | "socket"; flags: number; read(length: number): Uint8Array; write(data: Uint8Array): number; pread?(length: number, offset: number): Uint8Array; pwrite?(data: Uint8Array, offset: number): number; seek?(offset: number, whence: number): number; stat(): DescriptionStat; truncate?(length: number): void; /** Ready to read now, or at end of input (which is also "ready"). */ readable(): boolean; /** Ready to accept at least one byte. */ writable(): boolean; /** Resolves when readiness may have changed; used only to park a poll. */ whenReady(): Promise; /** Last reference dropped. */ close(): void; } declare const SEEK_SET = 0; declare const SEEK_CUR = 1; declare const SEEK_END = 2; /** * The bytes behind an open regular file. * * Content lives in the volume while a name still points at it, and moves into * `detached` when the last name goes away with the file still open. That is the * only way to honour "an unlinked file stays readable until the last close" on * top of a path-addressed volume. * * A name removed by something that does not go through this service — the * shell, or the Node side — cannot be intercepted, and the description will * then fail with ENOENT rather than reading stale bytes. Documented in * docs/python/abi.md; correctness holds for everything using the host ABI. */ declare class Inode { readonly ino: number; path: string; private readonly vfs; private readonly cred; detached: Uint8Array | null; refs: number; constructor(ino: number, path: string, vfs: Vfs, cred: Cred); /** Read the whole file, wherever it currently lives. */ contents(): Uint8Array; replace(data: Uint8Array): void; /** Called when the last name is about to be removed. */ detach(): void; size(): number; stat(): DescriptionStat; } /** One `open()`: an offset and status flags over an {@link Inode}. */ declare class FileDescription implements OpenFileDescription { readonly inode: Inode; flags: number; readonly kind = "file"; offset: number; constructor(inode: Inode, flags: number); read(length: number): Uint8Array; pread(length: number, offset: number): Uint8Array; write(data: Uint8Array): number; pwrite(data: Uint8Array, offset: number): number; private writeAt; seek(offset: number, whence: number): number; truncate(length: number): void; stat(): DescriptionStat; readable(): boolean; writable(): boolean; whenReady(): Promise; close(): void; } /** An open directory: `getdents` reads from a snapshot at the description's offset. */ declare class DirectoryDescription implements OpenFileDescription { readonly path: string; flags: number; private readonly vfs; private readonly cred; readonly kind = "dir"; private entries; private index; constructor(path: string, flags: number, vfs: Vfs, cred: Cred); /** Entry names, NUL-terminated, as many as fit in `length`. */ read(length: number): Uint8Array; write(): number; seek(offset: number, whence: number): number; stat(): DescriptionStat; readable(): boolean; writable(): boolean; whenReady(): Promise; close(): void; } /** The shared buffer between a pipe's two ends. */ declare class PipeBuffer { private chunks; private queued; readers: number; writers: number; private waiters; get available(): number; get space(): number; push(data: Uint8Array): void; take(length: number): Uint8Array; /** Anything that may have changed readiness for either end. */ wake(): void; whenReady(): Promise; } declare class PipeReadEnd implements OpenFileDescription { private readonly buffer; flags: number; readonly kind = "pipe"; constructor(buffer: PipeBuffer, flags: number); read(length: number): Uint8Array; write(): number; stat(): DescriptionStat; readable(): boolean; writable(): boolean; whenReady(): Promise; close(): void; } declare class PipeWriteEnd implements OpenFileDescription { private readonly buffer; flags: number; readonly kind = "pipe"; constructor(buffer: PipeBuffer, flags: number); read(): Uint8Array; write(data: Uint8Array): number; stat(): DescriptionStat; readable(): boolean; writable(): boolean; whenReady(): Promise; close(): void; } declare function createPipe(flags?: number): [PipeReadEnd, PipeWriteEnd]; /** * A process's standard streams. * * Bytes, not text: the terminal boundary is where multi-byte characters get cut * in half, so nothing here decodes. Input arrives from the host asynchronously * and is queued; a blocked reader parks on {@link whenReady} rather than * spinning. */ declare class StreamDescription implements OpenFileDescription { flags: number; private readonly sink; readonly isTty: boolean; readonly kind = "stream"; private queue; private queued; private ended; private waiters; constructor(flags: number, sink: ((data: Uint8Array) => void) | null, isTty?: boolean); /** Host side: give the guest more input. */ push(data: Uint8Array): void; /** Host side: no more input will arrive. */ end(): void; read(length: number): Uint8Array; write(data: Uint8Array): number; stat(): DescriptionStat; readable(): boolean; writable(): boolean; whenReady(): Promise; close(): void; private wake; } /** Translate a volume error into the errno a guest expects. */ declare function toPosixError(error: unknown): PosixError; /** Protocol version carried in every frame. */ declare const SBX_HOST_ABI_VERSION = 1; declare const SBX_REQUEST_HEADER_BYTES = 16; declare const SBX_RESPONSE_HEADER_BYTES = 20; /** Operation codes. Families are 256 apart so a family stays contiguous. */ declare const Op: { readonly handshake: 0; readonly openat: 1; readonly close: 2; readonly read: 3; readonly write: 4; readonly pread: 5; readonly pwrite: 6; readonly lseek: 7; readonly fstat: 8; readonly statat: 9; readonly ftruncate: 10; readonly renameat: 11; readonly unlinkat: 12; readonly mkdirat: 13; readonly readlinkat: 14; readonly symlinkat: 15; readonly getdents: 16; readonly fsync: 17; readonly dup: 256; readonly dup2: 257; readonly get_flags: 258; readonly set_flags: 259; readonly pipe: 512; readonly poll: 768; readonly clock_gettime: 1024; readonly sleep: 1025; readonly getpid: 1280; readonly getcwd: 1281; readonly chdir: 1282; readonly environ: 1283; readonly getrandom: 1536; readonly socket: 1792; readonly bind: 1793; readonly listen: 1794; readonly accept: 1795; readonly connect: 1796; readonly send: 1797; readonly recv: 1798; readonly shutdown: 1799; readonly getsockname: 1800; readonly getpeername: 1801; readonly setsockopt: 1802; readonly getsockopt: 1803; readonly socket_close: 1804; readonly resolve: 1805; readonly spawn: 2048; readonly waitpid: 2049; readonly kill: 2050; }; type OpCode = (typeof Op)[keyof typeof Op]; /** Canonical (Linux/musl) errno values. A negative status is `-Errno.X`. */ declare const Errno: { readonly EPERM: 1; readonly ENOENT: 2; readonly ESRCH: 3; readonly EINTR: 4; readonly EIO: 5; readonly ENXIO: 6; readonly E2BIG: 7; readonly EBADF: 9; readonly ECHILD: 10; readonly EAGAIN: 11; readonly ENOMEM: 12; readonly EACCES: 13; readonly EFAULT: 14; readonly EBUSY: 16; readonly EEXIST: 17; readonly EXDEV: 18; readonly ENODEV: 19; readonly ENOTDIR: 20; readonly EISDIR: 21; readonly EINVAL: 22; readonly ENFILE: 23; readonly EMFILE: 24; readonly ENOTTY: 25; readonly EFBIG: 27; readonly ENOSPC: 28; readonly ESPIPE: 29; readonly EROFS: 30; readonly EMLINK: 31; readonly EPIPE: 32; readonly ERANGE: 34; readonly ENAMETOOLONG: 36; readonly ENOSYS: 38; readonly ENOTEMPTY: 39; readonly ELOOP: 40; readonly ENODATA: 61; readonly ENOTSOCK: 88; readonly EPROTONOSUPPORT: 93; readonly EAFNOSUPPORT: 97; readonly EADDRINUSE: 98; readonly EADDRNOTAVAIL: 99; readonly ENETUNREACH: 101; readonly ECONNRESET: 104; readonly EISCONN: 106; readonly ENOTCONN: 107; readonly ECONNREFUSED: 111; readonly EPROTO: 71; readonly EOVERFLOW: 75; readonly ETIMEDOUT: 110; readonly ECANCELED: 125; readonly EDQUOT: 122; }; type ErrnoName = keyof typeof Errno; /** Capability names a host may advertise in its handshake. */ declare const CAPABILITIES: readonly ["files", "descriptors", "pipes", "readiness", "time", "identity", "entropy", "sockets", "processes", "signals", "storage", "services", "threads"]; type Capability = (typeof CAPABILITIES)[number]; /** Names by code, for diagnostics. Never used for dispatch. */ declare const OP_NAMES: Record; declare const ERRNO_NAMES: Record; /** * Framing for the host ABI. * * Every field is fixed-width and little-endian, because the two sides are * compiled by different toolchains and a shared struct layout is the one thing * they cannot negotiate at runtime. Text is UTF-8; file content and network * traffic are raw bytes and are never decoded in transit. * * `request_id` and `process_generation` exist so a late answer can be * recognised as late. A worker that exits and is replaced must not have its * successor accept a completion addressed to the process before it, and the * failure mode for a mismatched frame is an error — never an empty success. */ interface RequestHeader { version: number; op: number; requestId: number; generation: number; } interface ResponseFrame { header: RequestHeader; status: number; payload: Uint8Array; } declare class ProtocolError extends Error { readonly code = "ERR_SBX_ABI_PROTOCOL"; } declare function encodeRequest(header: RequestHeader, payload: Uint8Array): Uint8Array; declare function decodeRequest(frame: Uint8Array): { header: RequestHeader; payload: Uint8Array; }; declare function encodeResponse(header: RequestHeader, status: number, payload: Uint8Array): Uint8Array; declare function decodeResponse(frame: Uint8Array, expected?: RequestHeader): ResponseFrame; /** Little-endian reader over a payload. Bounds-checked: the guest owns this memory. */ declare class Reader { private at; private readonly view; private readonly bytes; constructor(bytes: Uint8Array); private need; i32(): number; u32(): number; i64(): number; u64(): number; bytes32(): Uint8Array; string(): string; get remaining(): number; } /** Little-endian writer. Grows as needed; payload sizes are bounded by the caller. */ declare class Writer { private parts; i32(value: number): this; u32(value: number): this; i64(value: number): this; u64(value: number): this; bytes32(value: Uint8Array): this; string(value: string): this; finish(): Uint8Array; } /** A status the guest reads as `-errno`. */ declare function failure(errno: number): number; /** * The transport under the host ABI: one blocking client, one non-blocking host. * * This is deliberately not `runtime/sync-channel.ts`. That channel predates the * ABI and carries no request identity, so a late or duplicated answer is * indistinguishable from the right one; worse, its server answers a failed * handler with a zero-length frame, which a caller can only read as a * successful empty result. Both are acceptable for `spawnSync`, where the * payload is self-describing JSON, and neither is acceptable for `read()`, * where an empty result means end of input. * * So the framing here always carries a full response header, a transport * failure is always an error, and the client verifies that the answer it got * belongs to the question it asked. * * Only the client blocks, and the client never owns shared state. The host * services requests from its ordinary event loop and may take as long as it * likes — which is what lets a guest's synchronous `read()` wait for a * keystroke that has not been typed yet. */ interface HostTransportBuffers { control: SharedArrayBuffer; data: SharedArrayBuffer; } declare function createHostTransportBuffers(windowBytes?: number): HostTransportBuffers; declare class HostTransportError extends Error { code: string; } /** * The guest side. Lives in the worker with the compiled program and blocks. * * `wake` nudges the host's event loop: the host cannot poll shared memory * without spinning, so each window is announced with an ordinary message. */ declare class HostCallClient { private control; private data; private capacity; private wake; private closed; constructor(buffers: HostTransportBuffers, wake: () => void); /** Send a frame and block until the whole answer is back. Never returns empty. */ call(request: Uint8Array): Uint8Array; private send; private receive; private publish; private waitWhile; } /** * The host side. Lives wherever the volume and the process table do, and never * blocks; `handle` is free to be asynchronous. */ declare class HostCallServer { private control; private data; private capacity; private handle; private onFault; private incoming; private outgoing; private sent; private closed; constructor(buffers: HostTransportBuffers, handle: (request: Uint8Array) => Promise | Uint8Array, onFault?: (error: unknown) => void); /** Call on each wake message from the client. */ pump(): Promise; /** Release a blocked client — container teardown, or the worker being killed. */ close(): void; private sendWindow; private publish; } /** * Per-process descriptor tables, and the path operations that have to know * about them. * * A descriptor is a small integer plus a close-on-exec bit; everything else * lives in the {@link OpenFileDescription} it names. Several descriptors — in * one process or across a spawn — may name the same description, and that is * how `dup2(fd, 1)` redirects a child's output without copying anything. * * `unlink` and `rename` live here rather than being left to {@link Vfs} * because they are the two operations whose correctness depends on what is * currently open: a removed name must not take an open file's contents with * it, and a renamed one must not strand it. */ /** Descriptors are handed out lowest-free-first, as POSIX requires. */ declare class DescriptorTable { private readonly slots; /** Descriptions this table shares with others, so close counts correctly. */ private static readonly refs; /** Install at a chosen number, closing whatever was there. */ set(fd: number, description: OpenFileDescription, cloexec?: boolean): number; /** Install at the lowest free number at or above `from`. */ add(description: OpenFileDescription, cloexec?: boolean, from?: number): number; get(fd: number): OpenFileDescription; has(fd: number): boolean; cloexec(fd: number): boolean; setCloexec(fd: number, value: boolean): void; /** `dup`: a second name for one description, so the offset is shared. */ dup(fd: number, from?: number): number; dup2(fd: number, target: number): number; close(fd: number): void; /** Everything a child inherits: a spawn keeps all but the close-on-exec ones. */ inherit(): DescriptorTable; /** Drop every descriptor. Every resource has an owner and a cleanup path. */ closeAll(): void; /** The numbers this table currently holds, in ascending order. */ fds(): number[]; get openCount(): number; private release; } /** * Path operations bound to one container's volume and one process's credentials. * * Open inodes are tracked per volume so that two processes opening the same * file agree about its identity — which is what makes "unlink it in one, keep * reading it in the other" behave. */ declare class FileService { private readonly vfs; private readonly cred; private readonly openInodes; constructor(vfs: Vfs, cred: Cred); open(path: string, flags: number, mode?: number): OpenFileDescription; /** * Remove a name. * * If something still has the file open, its contents move into the open * description first: POSIX guarantees a reader keeps reading, and a volume * that only knows about paths cannot provide that on its own. */ unlink(path: string): void; /** Rename a name. Anything already open follows the file to its new name. */ rename(from: string, to: string): void; /** One {@link Inode} per file identity, shared by every open description. */ private inodeFor; } /** * The kernel side of the host ABI: turn frames into real operations. * * This runs on whichever thread owns the volume and the process table, and it * never blocks. A guest that asks to read a terminal with nothing typed yet * parks in `Atomics.wait` inside its own worker; the handler here simply * awaits {@link OpenFileDescription.whenReady} and answers when input arrives. * That asymmetry is the whole reason a synchronous `read()` can exist at all * without freezing the host — and it is why keyboard input must be delivered to * *this* side rather than to the worker that is blocked waiting for it. */ /** Everything one guest process is allowed to see. */ interface HostAbiProcess { readonly pid: number; readonly ppid: number; readonly generation: number; readonly vfs: Vfs; readonly cred: Cred; readonly table: DescriptorTable; readonly env: Record; cwd: string; /** Aborts when the process is killed; wakes anything parked in a call. */ readonly signal: AbortSignal; readonly sockets?: VirtualTcpNetwork; /** * How this process makes another one. * * There is no fork in WebAssembly, so process creation cannot be a libc * detail the guest performs on its own — it is a service, like the * filesystem. A host that does not supply this reports ENOSYS to every * spawn, which is the honest answer and not a crash: a container with no * process table genuinely cannot start a child. */ readonly processes?: ProcessService; } /** The descriptors a child is to be given, by the numbers it will see. */ interface SpawnRequest { readonly argv: string[]; readonly env: Record; readonly cwd: string; /** `child fd` → the parent's open file description behind it. */ readonly files: Map; } interface ProcessService { spawn(request: SpawnRequest): Promise; /** * Reap one child. Returns the pid and its wait status, or a pid of 0 when * `WNOHANG` was asked for and nothing has exited yet. */ wait(pid: number, options: number): Promise<{ pid: number; status: number; }>; kill(pid: number, signal: number): void; } declare function createHostAbiServer(proc: HostAbiProcess): (frame: Uint8Array) => Promise; /** * The guest side of the host ABI, as the compiled code sees it. * * Compiled programs reach this through `sbx_host_call` in * `python-runtime/native/js/library_sbx.js`, which copies a frame out of the * module's memory, calls {@link HostAbiClient.callRaw}, and copies the answer * back. The typed methods below are for the JavaScript side of the worker — * loaders, the probe harness, and tests — which need the same operations * without going through Wasm memory. */ declare class HostAbiError extends Error { errno: number; code: string; constructor(errno: number, op: number); } interface HostAbiResult { status: number; payload: Uint8Array; } /** * One process's connection to the kernel. * * `generation` is stamped into every frame and checked on the way back, so a * completion belonging to a previous incarnation of this PID is rejected rather * than applied to the wrong descriptor table. */ declare class HostAbiClient { private transport; private generation; private nextRequestId; constructor(transport: HostCallClient, generation: number); /** Send one frame, blocking. Returns the status and payload verbatim. */ callRaw(op: number, payload: Uint8Array): HostAbiResult; /** As {@link callRaw}, but a negative status becomes an exception. */ private call; handshake(): { version: number; capabilities: Record; }; open(path: string, flags: number, mode?: number): number; close(fd: number): void; read(fd: number, length: number): Uint8Array; write(fd: number, data: Uint8Array): number; /** Write every byte, looping over short writes the way a guest's libc must. */ writeAll(fd: number, data: Uint8Array): void; pread(fd: number, length: number, offset: number): Uint8Array; pwrite(fd: number, data: Uint8Array, offset: number): number; seek(fd: number, offset: number, whence: number): number; fstat(fd: number): HostStat; stat(path: string, followLinks?: boolean): HostStat; ftruncate(fd: number, length: number): void; rename(from: string, to: string): void; unlink(path: string, removeDirectory?: boolean): void; mkdir(path: string, mode?: number): void; readlink(path: string): string; symlink(target: string, path: string): void; readdir(fd: number, bufferSize?: number): string[]; dup(fd: number, from?: number): number; dup2(fd: number, target: number): number; getFlags(fd: number): { flags: number; cloexec: boolean; }; setFlags(fd: number, flags: number, cloexec: boolean): void; pipe(flags?: number): [number, number]; poll(entries: { fd: number; events: number; }[], timeoutMs: number): { fd: number; revents: number; }[]; clockGettime(monotonic: boolean): { seconds: number; nanos: number; }; sleep(nanos: number): void; identity(): { pid: number; ppid: number; uid: number; gid: number; umask: number; }; getcwd(): string; chdir(path: string): void; environ(): Record; getrandom(length: number): Uint8Array; socket(family: number, type: number, protocol?: number): number; bind(fd: number, address: string, port: number): void; listen(fd: number, backlog?: number): void; accept(fd: number): { fd: number; address: string; port: number; }; connect(fd: number, address: string, port: number): void; send(fd: number, data: Uint8Array): number; recv(fd: number, length: number): Uint8Array; shutdown(fd: number, how: number): void; socketName(fd: number, peer?: boolean): { address: string; port: number; }; } interface HostStat { ino: number; mode: number; size: number; uid: number; gid: number; nlink: number; atimeNs: number; mtimeNs: number; ctimeNs: number; } export { CAPABILITIES, type Capability, Cred, type DescriptionStat, DescriptorTable, DirectoryDescription, ERRNO_NAMES, Errno, type ErrnoName, FileDescription, FileService, HostAbiClient, HostAbiError, type HostAbiProcess, HostCallClient, HostCallServer, type HostStat, type HostTransportBuffers, HostTransportError, Inode, OP_NAMES, O_APPEND, O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL, O_NONBLOCK, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, Op, type OpCode, type OpenFileDescription, PipeReadEnd, PipeWriteEnd, PosixError, ProtocolError, Reader, type RequestHeader, type ResponseFrame, SBX_HOST_ABI_VERSION, SBX_REQUEST_HEADER_BYTES, SBX_RESPONSE_HEADER_BYTES, SEEK_CUR, SEEK_END, SEEK_SET, StreamDescription, Vfs, Writer, accessMode, createHostAbiServer, createHostTransportBuffers, createPipe, decodeRequest, decodeResponse, encodeRequest, encodeResponse, failure, toPosixError };