network: Unix domain sockets #119
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Local Postgres and Redis sockets, Docker's API. Listed among what
stdis missing for the first-party packages(
torb/postgreslists "Unix sockets" under "Not built"). Today they go over TCP tolocalhost.Source
docs/design/NETWORK.mdsection 10: "A new one - Unix domain sockets ... calls one of the checks there, or refuseswhile a sandbox is open: the grant fails closed, never open."
Design (decided by the loop)
This is an epic of two steps plus one later slice. The change needs new natives, and the seed that builds the compiler
compiles
std/network(compiler/src/debugger/adapter.trbimports it): the two-commit rule of CLAUDE.md applies, andthe second commit cannot bootstrap until the published seed knows the natives.
std/network, a new filesrc/unix.trb, exported fromsrc/lib.trb. Stream sockets only(
SOCK_STREAM); datagram Unix sockets, abstract names, peer credentials and descriptor passing stay later additions.String(sostd/networkgains no dependency on
std/path):public shared type UnixListener with Close:static fn listen(path: String, backlog: Int = 128): Result<UnixListener, NetworkError>,fn path(): String,fn accept(): Task<Result<UnixStream, NetworkError>>.Closing removes the socket file it bound.
public shared type UnixStream with Close:static fn connect(path: String): Task<Result<UnixStream, NetworkError>>,fn receive(maximum: Int = 65536): Task<Result<Bytes?, NetworkError>>,fn send(bytes: Bytes): Task<Result<Void, NetworkError>>,fn shutdown(): Result<Void, NetworkError>,fn source(chunk: Int = 65536): UnixSource,fn sink(): UnixSink;UnixSourceandUnixSinkareSource<Bytes, NetworkError>andSink<Bytes, NetworkError>likeTcpSourceandTcpSink(end()is a half close).NetworkError(a new kind is never a breaking change); no new error type.Streamtrait: no generic transport abstraction exists yet (FIRST-PARTY.md gap17 is its own item);
torb/postgresandtorb/redischoose their transport behind their own private type, andtwo small mirrors are cheaper to undo than a premature trait.
networkListenUnix(path, backlog)andnetworkConnectUnix(path); accept, receive, send,shutdown and close are the existing natives on the new handles.
(
network connect: "unix:/run/app.sock") are a later slice, filed only when a sandboxed script needs a Unix socket.AF_UNIXover IOCP) written, asUDP was.
Steps
feat(runtime): Unix domain socket natives): #428std/networkUnixListener,UnixStream,UnixSource,UnixSink, tests instd/network/testsover a socketfile in a temp directory (VM and native), docs (
docs/standard-library/network.md, NETWORK.md section 4 andslice table, the generated
skills/), FIRST-PARTY.md gap 21 marked done and the "Unix sockets" mention oftorb/postgresleft to that package. Commitfeat(network): UnixListener and UnixStream. Filed as #546(step 1 landed, the published seed knows the natives). Landed as
d4157f4153(PR #548).unix:pattern for thenetworkgrant instd/sandboxandruntime/sandbox.c.Acceptance criteria (of the epic)
UnixStream.connectto it, bytes go bothways,
shutdown()gives the peer the end of the stream, connecting to an unbound path fails withisConnectionRefused()or the unreachable kind, listening on an existing path fails withisAddressInUse(),the socket file is gone after the listener closed; natively and in the VM
Dependencies
None known; the steps depend on each other as listed. #120 (socket options) is implemented in parallel and edits the
same generated natives files; that is a rebase note for whichever lands second, not a dependency.
Moved from the planning lists of the docs; the design record stays the specification.
[loop:torbscript] intake: in scope (author orion, label loop), source FIRST-PARTY.md gap 21, not a duplicate. No dependencies. next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z
[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T15:16Z
[loop:torbscript] decided by the loop: #119 is an epic, not one PR, and stays without
ready. Why: it needs two new natives inruntime/io.c(listen on and connect to a path; accept, receive, send and close are the existing natives), per-system code inposix_io.candiocp.c(sockaddr_un, a thirdAcceptEx/ConnectExslot), a sandbox check, and then thestd/networktypes with tests and docs. The natives touch the seed (CLAUDE.md "Seed and breaking changes"), so the two-commit rule splits the work: the second commit cannot bootstrap until the published seed knows the natives.Design recorded in the body: new file
std/network/src/unix.trb;UnixListenerandUnixStream(plusUnixSourceandUnixSink) mirror the TCP types, with methods and no settings object; the path is aStringsostd/networktakes no dependency onstd/path; stream sockets only; no shared stream trait, because gap 17 owns the transport question and two small mirrors are cheap to undo; the listener's close removes its socket file; errors are the existingNetworkError. Sandbox: both natives refuse while any sandbox is open (NETWORK.md section 10: "the grant fails closed, never open"); aunix:grant pattern is a later slice, filed only when a sandboxed script needs it. Names followTcpListener/TcpStreamand the "methods over free functions" convention (#146).Filed the first step as #428 (
runtime: Unix domain socket natives, touches the seed: yes, first commit of the rule, labelsloopandready; no open duplicate found by keyword search for unix, socket, domain socket, AF_UNIX, docker). Linked in the epic checklist; step 2 is filed once step 1 has landed and the published seed knows the natives.Note, non-blocking: #120 step 1 (#426, socket option natives) is implemented in parallel and edits the same files (
runtime/io.c,torb_network.h,natives.trb, the NATIVES.md table, the generatedtorb_natives.handmachine_natives.c). Whichever lands second rebases and regenerates the two generated files. Not a dependency.refine: read FIRST-PARTY.md gap 21 and section 8/10, NETWORK.md sections 4, 10 and 11, NATIVES.md network table, CLAUDE.md, CONTRIBUTING; claim released with this comment. Worker usage: about 90k tokens (sonnet).
next: waiting (epic waits on its steps)
[loop:torbscript] claim refine by session:loop-torbscript-20261028 until 2026-10-28T16:22:28Z
[loop:torbscript] decided by the loop: next step of #119 is step 2, filed as #546 (
std/networkUnixListener,UnixStream,UnixSource,UnixSink; labelsloopandready; linked in the checklist, step 1 ticked). Why now: step 1 (#428, PR #430,5dd2b9a4) landed on 2026-10-23; the natives are in the published seeds (c4a8443532aaof 2026-10-24 and every later one, e.g.142293fd98fc, are built from commits after5dd2b9a4), so the second commit of the two-commit rule can bootstrap in CI. Seed: no further seed change and no new native in #546 (a purestd/commit); the seed refresh stays with the merge gate and Torben. Size: one PR, one commitfeat(network): UnixListener and UnixStream, ~250 lines ofstd/mirroringTcpListener/TcpStreamwithout options, address and acceptor, one test file over a socket file made withFile.createTemporaryDirectory, three doc edits and regeneratedskills/. Two small decisions beyond the recorded design: noacceptor()for Unix listeners (a later addition when a server needs every core), and the existing natives the Unix types need are madepublic native fnintcp.trblikenetworkClose(internal, not exported by the package). Dependencies: none open; #473 (step 2 of #120, PR #477) edits the same files, a rebase note for whichever lands second. Duplicate check: keyword search of open issues for unix and socket found no other issue than the epic. Step 3 (unix:grant pattern) stays unfiled until a sandboxed script needs it.[loop:torbscript] refine: verified against
main(step 1 landed, the published seed knows the natives), filed step 2 as #546 (ready) and linked it in the checklist; the epic stays open withoutready, next: waiting (epic waits on #546)[loop:torbscript] claim refine by session:loop-torbscript-20261029 until 2026-10-29T01:13:11Z
[loop:torbscript] decided by the loop: closing the epic. Both steps landed on
main: step 1 (#428,5dd2b9a4, natives and sandbox refusal) and step 2 (#546, PR #548,d4157f4153feat(network): UnixListener and UnixStream, followed by two docs fixesb1cda15cacand38d3edd4ab). FIRST-PARTY.md gap 21 is marked done onmain.mainCI is green on the head38d3edd4ab(gates, tier A, tier B, bootstrap on linux-x64, pdisc, commit messages all success). All three epic acceptance criteria are ticked in the body. Step 3 (aunix:pattern for thenetworkgrant) was never part of the acceptance criteria and is deliberately unfiled; it becomes its ownloopissue when a sandboxed script needs a Unix socket. Landed commit:d4157f4153[loop:torbscript] refine: verified against
mainthat all steps (1 and 2) landed, ticked the checklist and criteria in the epic body, closed #119 per LOOP.md (work landed,mainCI green, commit link above); no new step issue needed. Claim released. Worker usage: about 45k tokens (sonnet), next: done (closed)std/networkaddresses and network interfaces (OS slice 5) #129