From 2fc197c5d3f43bd2536cebe508bb46e532dc4d6c Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Mon, 8 Jun 2026 00:02:07 +0400 Subject: [PATCH 1/2] docs(process): un-stub implemented id-keyed write-stdin/close-stdin The persistent-tier registry landed in unicity-astrid/astrid#867: the id-keyed write-stdin/close-stdin are implemented (pipe retained host-side, survives pooled-instance reset, 1 MiB cap, backpressure, ownership-checked, audited). Drops the stale (NOT YET IMPLEMENTED) tags on those two funcs and corrects the two PERSISTENT TIER banners that claimed the whole tier was stubbed 'until the registry lands'. The ProcessHandle (ephemeral) write-stdin/close-stdin, attach, and watch/unwatch remain genuinely deferred and keep their notes. --- host/process@1.0.0.wit | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/host/process@1.0.0.wit b/host/process@1.0.0.wit index cbc27ee..d8828f1 100644 --- a/host/process@1.0.0.wit +++ b/host/process@1.0.0.wit @@ -257,8 +257,8 @@ interface host { } // ================================================================ - // PERSISTENT TIER value types. (NOT YET IMPLEMENTED — the host stubs - // the persistent functions below; the SHAPES are fixed now.) + // PERSISTENT TIER value types. The persistent registry has landed; these + // shapes are in use. The SHAPES are frozen. // ================================================================ /// Opaque, unforgeable, principal-scoped identity for a PERSISTENT @@ -449,9 +449,11 @@ interface host { spawn-background: func(request: spawn-request) -> result; // ================================================================ - // PERSISTENT TIER functions. (NOT YET IMPLEMENTED — the host stubs - // these, returning `persist-unsupported` / `no-such-process` / `unknown` - // until the registry lands. The SHAPES are fixed now.) + // PERSISTENT TIER functions. The host registry has LANDED — these are + // implemented and audited. Still deferred (the host returns a stub + // `unknown`): `attach` (resource-handle materialisation — the id-keyed ops + // below ARE the documented `attach(id)?.method()` equivalent) and `watch` / + // `unwatch` (the lifecycle-event channel, RFC-open). The SHAPES are frozen. // // Every id-keyed function re-resolves the live calling principal + // capsule and checks them against the recorded creator BEFORE touching @@ -504,10 +506,14 @@ interface host { read-since: func(id: process-id, which-stream: log-stream, cursor: log-cursor, max-bytes: u32) -> result; /// `attach(id)?.write-stdin(data)`. Requires `keep-stdin-open = true`. - /// (NOT YET IMPLEMENTED — stdin pipe capture pending.) + /// Caps at 1 MiB/call (`too-large` above that); returns bytes written. The + /// retained pipe lives in the host registry, so a later instance that + /// re-attached keeps writing to the same child across pooled-instance + /// resets. A non-reading child backpressures the write (all-or-nothing per + /// call, so framed stdin is never torn). write-stdin: func(id: process-id, data: list) -> result; - /// `attach(id)?.close-stdin()`. (NOT YET IMPLEMENTED.) + /// `attach(id)?.close-stdin()`. Child observes EOF; idempotent. close-stdin: func(id: process-id) -> result<_, error-code>; /// `attach(id)?.signal(sig)`. Fire-and-forget signal delivery. From de89b1454f1aa31c1165d9399da2e0f61b87a45e Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Mon, 8 Jun 2026 14:45:49 +0400 Subject: [PATCH 2/2] docs(process): correct write-stdin atomicity wording The un-stub text claimed write-stdin is "all-or-nothing per call, so framed stdin is never torn". The host writes via write_all over a pipe, which can deliver a prefix before an I/O error and then marks the stream closed, so a failed write is NOT atomic and CAN tear a frame. Pipe writes are atomic only up to PIPE_BUF for a single write(), never for a 1 MiB write_all, so the contract cannot promise this. State the real semantics: a successful return wrote every byte; an error may have delivered a prefix and closes the stream. --- host/process@1.0.0.wit | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/host/process@1.0.0.wit b/host/process@1.0.0.wit index d8828f1..34aaf4d 100644 --- a/host/process@1.0.0.wit +++ b/host/process@1.0.0.wit @@ -506,11 +506,15 @@ interface host { read-since: func(id: process-id, which-stream: log-stream, cursor: log-cursor, max-bytes: u32) -> result; /// `attach(id)?.write-stdin(data)`. Requires `keep-stdin-open = true`. - /// Caps at 1 MiB/call (`too-large` above that); returns bytes written. The - /// retained pipe lives in the host registry, so a later instance that - /// re-attached keeps writing to the same child across pooled-instance - /// resets. A non-reading child backpressures the write (all-or-nothing per - /// call, so framed stdin is never torn). + /// Caps at 1 MiB/call (`too-large` above that). The retained pipe lives in + /// the host registry, so a later instance that re-attached keeps writing to + /// the same child across pooled-instance resets. A non-reading child + /// backpressures the call until the OS pipe buffer drains. A successful + /// return wrote every byte of `data`, in order, and yields `len(data)`. An + /// error is NOT all-or-nothing: a prefix may already have reached the child, + /// and the stream is then marked closed (`closed` thereafter) — a caller + /// that needs intact framing must treat a failed write as a torn frame, not + /// a no-op. write-stdin: func(id: process-id, data: list) -> result; /// `attach(id)?.close-stdin()`. Child observes EOF; idempotent.