Skip to content

Commit 463eb06

Browse files
NathanFlurryclaude
andcommitted
feat: kernel consolidation, Node.js conformance tests, and bridge improvements
- Implement kernel socket table with TCP, UDP, Unix socket, and socketpair support - Add loopback routing, server sockets, shutdown/half-close, socket options/flags - Add network permissions, external routing via host adapter, Node.js HostNetworkAdapter - Add DNS cache, timer table, inode table, wait queue, signal handler registry - Migrate Node.js bridge to kernel: FD table, net.connect, http.createServer, SSRF, child process - Route WasmVM sockets through kernel, add bind/listen/accept/sendto/recvfrom WASI extensions - Add C sysroot patches and test programs for server/UDP/Unix/signal handling - Remove legacy networking Maps from Node.js driver and bridge - Fix CI crossterm build, wire TimerTable and handle tracking to bridge - Implement SA_RESTART, SA_RESETHAND, O_NONBLOCK, O_EXCL/O_TRUNC, backlog limits - Wire InodeTable into VFS for deferred unlink and real nlink/ino - Add readdir "." and "..", /proc filesystem population, poll timeout -1 - Implement blocking flock and pipe write with WaitQueue - Add getLocalAddr/getRemoteAddr, WasmVM getsockname/getpeername, setsockopt routing - Fix ESM parity: native V8 ESM mode, top-level await, dynamic import failures - Add Node.js conformance test runner (19.9% genuine pass rate, 704/3532 tests) - Add conformance report generator script and docs page - Add adversarial review of kernel consolidation PRD Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 493b0da commit 463eb06

140 files changed

Lines changed: 32803 additions & 14057 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agent/contracts/kernel.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ The kernel VFS SHALL provide a POSIX-like filesystem interface with consistent e
2828
- **WHEN** a caller invokes `removeFile(path)` on an existing regular file
2929
- **THEN** the file MUST be deleted and subsequent `exists(path)` MUST return false
3030

31+
#### Scenario: removeFile defers inode data deletion while FDs remain open
32+
- **WHEN** the last directory entry for a file is removed while one or more existing FDs still reference that inode
33+
- **THEN** the pathname MUST disappear from directory listings and `exists(path)` MUST return false, but reads and writes through the already-open FDs MUST continue to operate until the last reference closes
34+
3135
#### Scenario: removeDir deletes a directory
3236
- **WHEN** a caller invokes `removeDir(path)` on an existing empty directory
3337
- **THEN** the directory MUST be deleted
@@ -52,10 +56,18 @@ The kernel VFS SHALL provide a POSIX-like filesystem interface with consistent e
5256
- **WHEN** a caller invokes `link(oldPath, newPath)`
5357
- **THEN** both paths MUST reference the same content, and `stat` for both MUST report `nlink >= 2`
5458

59+
#### Scenario: hard links share a stable inode number
60+
- **WHEN** two directory entries refer to the same file through `link(oldPath, newPath)`
61+
- **THEN** `stat(oldPath).ino` and `stat(newPath).ino` MUST be identical until the inode is deleted
62+
5563
#### Scenario: readDirWithTypes returns entries with type information
5664
- **WHEN** a caller invokes `readDirWithTypes(path)` on a directory containing files and subdirectories
5765
- **THEN** the VFS MUST return `VirtualDirEntry[]` where each entry has `name`, `isDirectory`, and `isSymbolicLink` fields
5866

67+
#### Scenario: InMemoryFileSystem directory listings include self and parent entries
68+
- **WHEN** a caller invokes `readDir(path)` or `readDirWithTypes(path)` against an `InMemoryFileSystem` directory
69+
- **THEN** the listing MUST begin with `.` and `..`, and for `/` the `..` entry MUST refer back to the root directory
70+
5971
#### Scenario: chmod updates file permissions
6072
- **WHEN** a caller invokes `chmod(path, mode)` on an existing file
6173
- **THEN** subsequent `stat(path)` MUST reflect the updated `mode`
@@ -71,6 +83,18 @@ The kernel FD table SHALL manage per-process file descriptor allocation with ref
7183
- **WHEN** a process opens a file via `fdOpen(pid, path, flags)`
7284
- **THEN** the FD table MUST allocate and return the lowest available file descriptor number
7385

86+
#### Scenario: Open with O_CREAT|O_EXCL rejects existing paths
87+
- **WHEN** a process opens an already-existing path with `O_CREAT | O_EXCL`
88+
- **THEN** `fdOpen` MUST fail with `EEXIST` before allocating a new FD
89+
90+
#### Scenario: Open with O_TRUNC truncates at open time
91+
- **WHEN** a process opens an existing regular file with `O_TRUNC`
92+
- **THEN** the file contents MUST be truncated to zero bytes before subsequent reads or writes through the returned FD
93+
94+
#### Scenario: Open with O_TRUNC|O_CREAT materializes an empty file
95+
- **WHEN** a process opens a missing path with `O_TRUNC | O_CREAT`
96+
- **THEN** the kernel MUST create an empty regular file during `fdOpen`
97+
7498
#### Scenario: Close decrements reference count and releases FD
7599
- **WHEN** a process closes an FD via `fdClose(pid, fd)`
76100
- **THEN** the FD entry MUST be removed from the process table and the underlying FileDescription's `refCount` MUST be decremented
@@ -79,6 +103,10 @@ The kernel FD table SHALL manage per-process file descriptor allocation with ref
79103
- **WHEN** the last FD referencing a FileDescription is closed (refCount reaches 0)
80104
- **THEN** the FileDescription MUST be eligible for cleanup
81105

106+
#### Scenario: Close last reference releases deferred-unlink inode data
107+
- **WHEN** the last FD referencing an already-unlinked inode is closed
108+
- **THEN** the kernel MUST release the inode's retained file data so no hidden data remains after the final close
109+
82110
#### Scenario: Dup creates a new FD sharing the same FileDescription
83111
- **WHEN** a process duplicates an FD via `fdDup(pid, fd)`
84112
- **THEN** a new FD MUST be allocated pointing to the same FileDescription, and the FileDescription's `refCount` MUST be incremented
@@ -107,6 +135,25 @@ The kernel FD table SHALL manage per-process file descriptor allocation with ref
107135
- **WHEN** a process exits and `closeAll()` is invoked on its FD table
108136
- **THEN** all FDs MUST be closed and all FileDescription refCounts MUST be decremented
109137

138+
### Requirement: Advisory flock Semantics
139+
The kernel SHALL provide advisory `flock()` semantics per file description, including blocking waits and cleanup on last close.
140+
141+
#### Scenario: Exclusive flock blocks until the prior holder unlocks
142+
- **WHEN** process A holds `LOCK_EX` on a file and process B calls `flock(fd, LOCK_EX)` on the same file without `LOCK_NB`
143+
- **THEN** process B MUST remain blocked until process A releases the lock, after which process B acquires it
144+
145+
#### Scenario: Non-blocking flock returns EAGAIN on conflict
146+
- **WHEN** a conflicting advisory lock is already held and a caller uses `LOCK_NB`
147+
- **THEN** `flock()` MUST fail immediately with `EAGAIN`
148+
149+
#### Scenario: flock waiters are served in FIFO order
150+
- **WHEN** multiple callers are queued waiting for the same file lock
151+
- **THEN** unlock MUST wake the next waiter in FIFO order so lock ownership advances predictably
152+
153+
#### Scenario: Last file description close releases flock state
154+
- **WHEN** the final FD referencing a locked file description is closed or the owning process exits
155+
- **THEN** the lock MUST be released and the next queued waiter MUST be eligible to acquire it
156+
110157
### Requirement: Process Table Register/Waitpid/Kill/Zombie Cleanup
111158
The kernel process table SHALL manage process lifecycle with atomic PID allocation, signal delivery, and time-bounded zombie cleanup.
112159

@@ -154,6 +201,17 @@ The kernel process table SHALL manage process lifecycle with atomic PID allocati
154201
- **WHEN** `listProcesses()` is invoked
155202
- **THEN** it MUST return a Map of PID to ProcessInfo containing `pid`, `ppid`, `driver`, `command`, `status`, and `exitCode` for every registered process
156203

204+
### Requirement: Kernel TimerTable Ownership And Process Cleanup
205+
The kernel SHALL expose a shared timer table so runtimes can enforce per-process timer budgets and clear timer ownership on process exit.
206+
207+
#### Scenario: TimerTable is exposed to runtimes
208+
- **WHEN** a runtime receives a kernel interface in a kernel-mediated environment
209+
- **THEN** it MUST be able to access the shared `timerTable` for per-process timer allocation and cleanup
210+
211+
#### Scenario: Process exit clears kernel-owned timers
212+
- **WHEN** a process exits through the kernel process lifecycle
213+
- **THEN** any timers owned by that PID MUST be removed from the kernel `TimerTable`
214+
157215
### Requirement: Device Layer Intercepts and EPERM Rules
158216
The kernel device layer SHALL transparently intercept `/dev/*` paths with fixed device semantics, pass non-device paths through to the underlying VFS, and deny mutation operations on devices.
159217

@@ -197,6 +255,37 @@ The kernel device layer SHALL transparently intercept `/dev/*` paths with fixed
197255
- **WHEN** any filesystem operation targets a path outside `/dev/`
198256
- **THEN** the device layer MUST delegate the operation to the underlying VFS without interception
199257

258+
### Requirement: Proc Filesystem Introspection
259+
The kernel SHALL expose a read-only `/proc` pseudo-filesystem backed by live process and FD table state so runtimes can inspect `/proc/<pid>` consistently, while process-scoped runtime adapters resolve `/proc/self` to the caller PID.
260+
261+
#### Scenario: /proc root lists self and running PIDs
262+
- **WHEN** a caller invokes `readDir("/proc")`
263+
- **THEN** the listing MUST include a `self` entry and directory entries for every PID currently tracked by the kernel process table
264+
265+
#### Scenario: /proc/<pid>/fd lists live file descriptors
266+
- **WHEN** a caller invokes `readDir("/proc/<pid>/fd")` for a live process
267+
- **THEN** the listing MUST contain the process's currently open FD numbers from the kernel FD table
268+
269+
#### Scenario: /proc/<pid>/fd/<n> resolves to the underlying description path
270+
- **WHEN** a caller invokes `readlink("/proc/<pid>/fd/<n>")` for an open FD
271+
- **THEN** the kernel MUST return the backing file description path for that FD
272+
273+
#### Scenario: /proc/<pid>/cwd and exe expose process metadata
274+
- **WHEN** a caller reads `/proc/<pid>/cwd` or `/proc/<pid>/exe`
275+
- **THEN** the kernel MUST expose the process working directory and executable path for that PID
276+
277+
#### Scenario: /proc/<pid>/environ exposes NUL-delimited environment entries
278+
- **WHEN** a caller reads `/proc/<pid>/environ`
279+
- **THEN** the kernel MUST return the process environment as `KEY=value` entries delimited by `\0`, or an empty file when the process environment is empty
280+
281+
#### Scenario: /proc paths are read-only
282+
- **WHEN** a caller invokes a mutating filesystem operation against `/proc` or any `/proc/...` path
283+
- **THEN** the kernel MUST reject the operation with `EPERM`
284+
285+
#### Scenario: Process-scoped runtimes resolve /proc/self to the caller PID
286+
- **WHEN** sandboxed code in a process-scoped runtime accesses `/proc/self/...`
287+
- **THEN** the runtime-facing VFS MUST resolve that path as `/proc/<current_pid>/...` before delegating into the shared kernel proc filesystem
288+
200289
### Requirement: Pipe Manager Blocking Read/EOF/Drain
201290
The kernel pipe manager SHALL provide buffered unidirectional pipes with blocking read semantics and proper EOF signaling on write-end closure.
202291

@@ -220,6 +309,22 @@ The kernel pipe manager SHALL provide buffered unidirectional pipes with blockin
220309
- **WHEN** a read is performed on a pipe's read end with an empty buffer and the write end is still open
221310
- **THEN** the read MUST block (return a pending Promise) until data is written or the write end is closed
222311

312+
#### Scenario: Blocking write waits when the pipe buffer is full
313+
- **WHEN** a blocking write reaches `MAX_PIPE_BUFFER_BYTES` buffered data while the read end remains open
314+
- **THEN** the write MUST suspend until a reader drains capacity or the pipe closes, rather than growing the buffer without bound
315+
316+
#### Scenario: Pipe reads wake one blocked writer after draining capacity
317+
- **WHEN** a read consumes buffered pipe data while one or more writers are blocked on buffer capacity
318+
- **THEN** the pipe manager MUST wake the next blocked writer so it can continue writing in FIFO order
319+
320+
#### Scenario: Non-blocking pipe write returns EAGAIN on a full buffer
321+
- **WHEN** a pipe write end has `O_NONBLOCK` set and a write finds no remaining buffer capacity
322+
- **THEN** the write MUST fail immediately with `EAGAIN`
323+
324+
#### Scenario: Blocking pipe writes preserve partial progress
325+
- **WHEN** only part of a blocking write fits before the pipe buffer becomes full
326+
- **THEN** the pipe manager MUST commit the bytes that fit, then block for the remainder until more capacity is available
327+
223328
#### Scenario: Read returns null (EOF) when write end is closed and buffer is empty
224329
- **WHEN** a read is performed on a pipe's read end after the write end has been closed and the buffer is drained
225330
- **THEN** the read MUST return `null` signaling EOF
@@ -228,6 +333,10 @@ The kernel pipe manager SHALL provide buffered unidirectional pipes with blockin
228333
- **WHEN** the write end of a pipe is closed and readers are blocked waiting for data
229334
- **THEN** all blocked readers MUST be notified with `null` (EOF)
230335

336+
#### Scenario: Closing the read end wakes blocked writers with EPIPE
337+
- **WHEN** writers are blocked waiting for pipe capacity and the read end is closed
338+
- **THEN** those writers MUST wake and fail with `EPIPE`
339+
231340
#### Scenario: Pipes work across runtime drivers
232341
- **WHEN** a pipe connects a process in one runtime driver (e.g., WasmVM) to a process in another (e.g., Node)
233342
- **THEN** data MUST flow through the kernel pipe manager transparently, with the same blocking/EOF semantics
@@ -236,6 +345,51 @@ The kernel pipe manager SHALL provide buffered unidirectional pipes with blockin
236345
- **WHEN** `createPipeFDs(fdTable)` is invoked
237346
- **THEN** the pipe manager MUST create a pipe and install both read and write FileDescriptions as FDs in the specified FD table, returning `{ readFd, writeFd }`
238347

348+
### Requirement: Socket Blocking Waits Respect Signal Handlers
349+
The kernel socket table SHALL allow blocking accept/recv waits to observe delivered signals so POSIX-style syscall interruption semantics can be enforced.
350+
351+
#### Scenario: SA_RESETHAND resets a caught handler after first delivery
352+
- **WHEN** a process delivers a caught signal whose registered handler includes `SA_RESETHAND`
353+
- **THEN** the kernel MUST invoke that handler once and reset the disposition to `SIG_DFL` before any subsequent delivery of the same signal
354+
355+
#### Scenario: recv interrupted without SA_RESTART returns EINTR
356+
- **WHEN** a process is blocked in a socket `recv` wait and a caught signal is delivered whose handler does not include `SA_RESTART`
357+
- **THEN** the wait MUST reject with `EINTR`
358+
359+
#### Scenario: recv interrupted with SA_RESTART resumes waiting
360+
- **WHEN** a process is blocked in a socket `recv` wait and a caught signal is delivered whose handler includes `SA_RESTART`
361+
- **THEN** the wait MUST resume transparently until data arrives or EOF occurs
362+
363+
### Requirement: Non-blocking Socket Operations Return Immediate Status
364+
The kernel socket table SHALL respect per-socket non-blocking mode for read, accept, and external connect operations.
365+
366+
#### Scenario: recv on a non-blocking socket returns EAGAIN when empty
367+
- **WHEN** `recv` is called on a socket whose `nonBlocking` flag is set and no data or EOF is available
368+
- **THEN** the call MUST fail immediately with `EAGAIN`
369+
370+
#### Scenario: accept on a non-blocking listening socket returns EAGAIN when backlog is empty
371+
- **WHEN** `accept` is called on a listening socket whose `nonBlocking` flag is set and there are no queued connections
372+
- **THEN** the call MUST fail immediately with `EAGAIN`
373+
374+
#### Scenario: external connect on a non-blocking socket returns EINPROGRESS
375+
- **WHEN** `connect` is called on a non-blocking socket for an external address routed through the host adapter
376+
- **THEN** the call MUST fail immediately with `EINPROGRESS` while the host-side connection continues asynchronously
377+
378+
#### Scenario: accept interrupted with SA_RESTART resumes waiting
379+
- **WHEN** a process is blocked in a socket `accept` wait and a caught signal is delivered whose handler includes `SA_RESTART`
380+
- **THEN** the wait MUST resume transparently until a connection is available
381+
382+
### Requirement: Socket Bind and Listen Preserve Bounded Listener State
383+
The kernel socket table SHALL reserve listener ports deterministically for loopback routing while keeping pending connection queues bounded.
384+
385+
#### Scenario: bind with port 0 assigns a kernel ephemeral port
386+
- **WHEN** an internet-domain socket is bound with `port: 0` for kernel-managed routing
387+
- **THEN** the socket MUST be assigned a free port in the ephemeral range and `localAddr.port` MUST reflect that assigned value instead of `0`
388+
389+
#### Scenario: loopback connect refuses when listener backlog is full
390+
- **WHEN** a loopback `connect()` targets a listening socket whose pending backlog already reached the configured `listen(backlog)` capacity
391+
- **THEN** the connection MUST fail with `ECONNREFUSED` instead of growing the backlog without bound
392+
239393
### Requirement: Command Registry Resolution and /bin Population
240394
The kernel command registry SHALL map command names to runtime drivers and populate `/bin` stubs for shell PATH-based resolution.
241395

.agent/contracts/node-bridge.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,6 @@ The bridge global key registry consumed by host runtime setup, bridge modules, a
157157
- **WHEN** contributors add a new bridge global used by host/isolate boundary wiring
158158
- **THEN** that global MUST be added to the canonical shared key registry and corresponding shared contract typing in the same change
159159

160+
#### Scenario: Native V8 bridge registries stay aligned with async and sync lifecycle hooks
161+
- **WHEN** bridge modules depend on a host bridge global via async `.apply(..., { result: { promise: true } })` or sync `.applySync(...)` semantics
162+
- **THEN** the native V8 bridge function registries MUST expose a matching callable shape for that global (or an equivalent tested shim), and automated verification MUST cover the registry alignment

0 commit comments

Comments
 (0)