- §G written to READY (forks auto-approved, review_pending): the inbound
server rung. Grounds what's reused (record layer, role-symmetric key
schedule, X.509, slot table + data plane) vs new (server FSM, signing,
key parsing, accept surface)
- six locked decisions: (1) constant-time private-key ops — the built
modexp/scalar-mult are verify-only, not constant-time, so G adds a
constant-time fixed-window modexp + Montgomery-ladder scalar mult;
(2) both RSA-PSS + ECDSA-P256 server keys; (3) deterministic RFC 6979
ECDSA nonce; (4) net.accept_tls(listener,cert,key) w/ per-path shard
identity cache; (5) full 1-RTT server-auth only (no mTLS/resumption/HRR);
(6) sans-io wo_tls_server FSM
- sub-phases G1 signing+key-parse, G2 server FSM (loopback KAT), G3
net.accept_tls + live gate (openssl s_client); acceptance + out-of-scope
- ladder G row -> READY; may become its own runtime-v2 iteration
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9fcb4a96a137a897f0ce2be868c18e63a979c997)
- new "Hand-rolled TLS 1.3 client" section: the crypto ladder in crypto.c,
the tls.c layers (record / key schedule / messages / sans-io driver /
chain validation / PEM), and the net.*_tls builtins in sysio.c —
per-shard no-lock slot table, deadline-bounded blocking handshake then a
parked data plane, getrandom ephemeral (the runtime's first RNG),
WO_CA_BUNDLE trust store, loud WO_T_IO failures, the just tls gate
- files table: crypto.c entry updated, tls.c/.h added
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 5670304d8a7a54e291b81e99e27aa16e29aa1d3e)
- rv2 9 §F3c-net marked LANDED + live-gated; phase-F row COMPLETE (client);
frontmatter review_pending updated (client complete, remaining = G server
+ deferred park-handshake/TlsConn/pooling + doctrine-doc corrections)
- jarvis 00-story + 01: the outbound-TLS blocker is cleared
(net.connect_tls landed) — jarvis 1 (chat loop) is now buildable
- status board: rv2 9 row + NEXT PLAN rewritten to the completed client;
next step is jarvis 1 or rv2 9 G
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 732c2216b501dd226d306f9abcbd1ddd846809dc)
- docs/examples/tls-client/main.wo: an outbound HTTPS client in .wo —
net.connect_tls, write_tls a request, read_tls to EOF, print; connect
failure caught with try/catch and reported (never a silent downgrade)
- scripts/tls-accept.sh + `just tls`: dials a local TLS 1.3 stub (python
ssl, TLS1.3-only) with a generated test CA — proves the hand-rolled
handshake + chain/host validation + an app round-trip end to end from
.wo through the compiler, and refuses the untrusted-chain and
hostname-mismatch negatives. No live network; log /tmp/tls.log
- gate: 5 checks, 0 failures
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 3d8bb140ec478167a4e660adf2caa964ff410ff1)
The outbound TLS 1.3 client wired into the VM (ids 115-117, WO_B_MAX->117):
- net.connect_tls(host,port)->Int: DNS + non-blocking connect+poll bounded
by WO_TLS_HANDSHAKE_MS (decision 5), then a blocking, SO_*TIMEO-bounded
hand-rolled handshake over the sans-io driver, then wo_tls_verify_chain
(chain + host + validity + basicConstraints/EKU) against the shard's
lazily-loaded read-only CA bundle (decision 4). Any failure traps WO_T_IO
loudly (decision 3). Returns the fd.
- net.read_tls / net.write_tls: application data over the parked data plane
(decision 1) — O_NONBLOCK + park on POLLIN/POLLOUT like net.read/write,
with record reassembly + leftover-plaintext + in-flight-record buffers in
the per-fd slot so a park/retry never re-seals or loses progress.
- per-shard wo_tls_conn slot table keyed by fd, no locks (one thread per
shard, the wo_child pattern; decision 2); net.close frees the slot;
wo_vm_destroy reaps all slots + the CA bundle. getrandom ephemeral.
- driver keeps the whole Certificate message + wo_tls_client_chain() so the
trust walk sees the full chain, not just the leaf.
- wiring: wob.h, loader.c arities, builtin.c dispatch (second net range),
types.ml (net.connect_tls/read_tls/write_tls), sysio.c impl.
Builds; full runtime suite 0 fail; woc builds. Live behaviour is the
Phase-4 gate (next commit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9a922b3245eaa9134efb60bfd46521952ed12a39)
- wo_tls_pem_to_ders: scan a PEM bundle for CERTIFICATE blocks, base64-decode
each into a caller arena, record DER spans as trust anchors for
wo_tls_verify_chain. Pure (caller reads the file + owns the arena) so it is
offline-testable; the file read + per-shard cache land with the builtin
- b64_decode helper (standard alphabet, skips whitespace/newlines)
- KAT: decode the real /etc/ssl/certs/ca-certificates.crt (>100 anchors,
each parses, first is a CA), garbage PEM -> 0 with no over-read,
skip-if-absent for CI. test_tls 107 pass, ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 6445d55aa83dbed831d84fd3cca3a74fe4609a00)
- crypto.c: x509_find_ext (generic extension walker) + wo_x509_basic_constraints
(cA / pathLenConstraint, absent => not a CA) + wo_x509_eku_serverauth_ok
(EKU absent, serverAuth, or anyEKU => usable; else not)
- wo_tls_verify_chain enforces decision 6: the leaf must be server-usable
(EKU), every server-sent issuer and the signing anchor must be a CA
(basicConstraints CA:TRUE) with a pathLenConstraint covering the
intermediates below it — stops a leaf masquerading as a CA
- gen_x509.py extended (folds in the wildcard leaf, adds EKU clientAuth-only,
EKU serverAuth, a non-CA intermediate + a leaf issued under it); vectors
regenerated
- KATs: extractors (test_crypto 104) + chain enforcement (test_tls 103) —
EKU serverAuth accepted, clientAuth-only rejected, leaf-under-non-CA
rejected though every signature verifies; existing chains still pass.
ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 3811418014c2c8d9bc3a9464256f52104261b1b9)
A reusable named Workflow (.claude/workflows/) that runs the
brainstorm-to-ready groundwork this repo does before any feature code:
- Understand: read the target story (or find the NEXT-PLAN target) +
scout relevant .dev/reference projects for the concern
- Analyze: one agent per reference project — how it handles the concern,
gaps vs our planned approach, recommendations (the fiber/Go step,
generalized)
- Audit: story-format/frontmatter/plans-no-raw-code + dependency-graph /
status-board consistency
- Consolidate: settle open forks (KISS defaults), fold reference gaps as
locked requirements, acceptance-criteria gaps, go/no-go on readiness
Parameterized via args {story?, concern?, references?}; grounds every
agent in on-disk files. Does the parallelizable research half; the
fork-settling stays an interactive brainstorm. Invoke:
Workflow({name:'prebuild-feature', args:{...}}) or /workflows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 2bfbb0ca5ca17cb2d1ead39f93aa42aeb50b1639)
Compared §F3c-net's forks against gofiber v3's client (fasthttp + Go
crypto/tls/x509, .dev/reference/fiber). Two gaps my defaults had vs Go,
now locked as decisions 5 and 6:
- (5) bounded handshake deadline: the blocking model would let a stalled
server hang the shard's one thread indefinitely (the DoS DoTimeout
closes). connect_tls now bounds connect+handshake via non-blocking
connect+poll + SO_RCVTIMEO/SNDTIMEO, default WO_TLS_HANDSHAKE_MS
(10s); expiry traps WO_T_IO. _dl variant + park handshake stay follow-ups
- (6) chain hardening: signatures+validity+SAN alone let a leaf act as a
CA. Now every non-leaf must assert basicConstraints CA:TRUE (+pathLen)
and the leaf must carry EKU serverAuth — what Go's crypto/x509 enforces
- acceptance criteria added (stalled-server timeout; leaf-as-CA + no-EKU
rejected); connect_tls bullet, frontmatter review_pending, status NEXT
PLAN updated to six locked forks
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9662cd8b040f417b4886dae9ce97b70083e24e40)
- rv2 9 §F3c-net: the remaining live-gated slice with auto-approved
defaults — getrandom ephemeral, system CA-bundle loader, net.connect_tls
builtin returning the TCP fd (fd-keyed side table, blocking model like
net.connect) driving the sans-io driver, then wo_tls_verify_chain; plus
net.read_tls/write_tls and a live gate
- status board NEXT PLAN: the TLS client security engine landed this
session (E-F3c minus socket glue), F3c-net is the next rung, then jarvis
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit ad87974fc722268e278f957f1f3d607f5dafa50d)
- wo_tls_verify_chain: leaf-first DER chain — each cert signed by the
next, the top trusted (equal to, or signed by, a trust anchor), the leaf
SAN matching host, every cert temporally valid. Any failure rejects;
no partial trust. Pure over the phase-D/E verifiers, so offline-testable
- KAT with the phase-E RSA + EC chains: leaf trusted via its issuing CA
anchor; wrong-anchor / wrong-host / expired / broken-link / no-anchor
all rejected; two-cert chain with a byte-equal root anchor; NULL host
skips the SAN check. test_tls 100 pass, ASan/UBSan clean
- remaining F3c-net (live-gated): CA-bundle PEM loader, random ephemeral,
the net.connect_tls builtin driving the sans-io driver over a real fd
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9d40055108d301be32df0e1d4140c23446baa7c6)
- rv2 9 phase-F row + review_pending: F3c-core sans-io driver + SAN/host
landed (KAT'd vs RFC 8448 record trace); remaining F3c-net = system CA
trust-anchor walk + net.connect_tls VM plumbing (live-gated), then G
- jarvis 00-story + 01 blocker tables: crypto/handshake engine landed;
jarvis now waits only on net.connect_tls (the socket glue)
- status board rv2 9 row updated to the full ladder state
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4fdf07196d01c32d0ab12d32d36fa4285ff34654)
- wo_x509_check_host: match a hostname against the cert subjectAltName
dNSNames (RFC 6125) — case-insensitive, single left-most wildcard that
covers exactly one label; no SAN => refused; no legacy CN fallback.
Completes the phase-E deferred hostname check (walks the [3] extensions)
- wo_tls_client_set_host + driver enforcement: with a host set, a leaf
whose SAN does not match is refused at the Certificate step (MITM
defense); unset skips the check (offline testing only, documented unsafe)
- KAT: exact/case-insensitive/mismatch, no-SAN refused, wildcard one-label
(not zero, not sub-label) via a wildcard-SAN cert; driver refuses the
RFC 8448 leaf (no SAN) once a host is set. test_crypto 95, test_tls 91,
ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 319ce8bfcf608030f958b36ab2c8f62fd76e1740)
- wo_tls_client: a pure state machine (no sockets). Caller frames
records; driver runs ClientHello->ServerHello->flight->Finished and
hands back bytes to send. Keeps all I/O out of the security-critical FSM
- start_with (inject CH + ephemeral priv), push_record, take_output,
encrypt/decrypt (application traffic keys). Handshake-message reassembly
across records; per-message transcript timing (CertVerify signs CH..Cert,
Finished MACs CH..CertVerify); constant-time Finished compare; every
failure lands in FAILED (no warn-and-continue)
- verifies server CertificateVerify (phase E+D) + server Finished, emits
the client Finished, switches to application keys
- KAT: whole handshake driven offline against the RFC 8448 record trace —
client Finished record byte-for-byte, first client app record
byte-for-byte, NewSessionTicket + server app data decrypt to plaintext,
tampered flight -> FAILED. test_tls 90 pass, ASan/UBSan clean
- SECURITY TODO before live use (documented in tls.h + story): chain walk
to a trust anchor + SAN/hostname match; random ephemeral for production
start; the net.connect_tls socket glue
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 74c332d7efdb8bbfdbe90bd2fcc3defa2fe8da00)
- phase-F row: F1 record layer, F2 key schedule, F3a message layer,
F3b offline handshake verification all landed + KAT'd (RFC 8448 /
real certs); F3c socket FSM + net.connect_tls plumbing remaining
- review_pending updated to the current ladder state
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit d49bc3866b6b620d00a8a57137ba211da25cd05b)
- wo_tls_verify_cert_verify: verifies a server CertificateVerify
(RFC 8446 §4.4.3) — builds the 64-space || context || 0x00 ||
transcript-hash content, parses the leaf SPKI (phase E) and dispatches
to phase-D RSA-PSS / RSA-PKCS1 / ECDSA-P256; the scheme must match the
leaf key type. ECDSA sig r/s pulled from its DER SEQ
- reuses wo_tls_finished_verify (phase F2) for server + client Finished
- KAT: the whole handshake crypto driven offline from the RFC 8448 §3
recorded messages — CertificateVerify (RSA-PSS) VALID, wrong-transcript
/ tampered-sig / mismatched-scheme rejected, server Finished byte-exact,
and the client Finished we would send byte-exact. test_tls 78 pass,
ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit afd9f23508c648322712df91329aa117c975ccab)
- new tls.c/tls.h on the crypto ladder: wo_tls_record_seal/open
(RFC 8446 §5.2) — TLSInnerPlaintext (content||type, no padding),
5-byte header as AEAD additional-data, per-record nonce = iv XOR
seq big-endian (§5.3)
- suite dispatch: TLS_AES_128_GCM_SHA256 (mandatory) +
TLS_CHACHA20_POLY1305_SHA256 (AES-NI-less fallback), over phase-A AEAD
- open() strips trailing zero padding to recover the inner content type;
rejects a length-field lie before the AEAD, and auth failure after
- KAT vs python AEAD oracle (test/gen_tls_record.py): sealed record
byte-for-byte both suites, open() recovers it, 5-seq round-trip,
tamper + wrong-seq + bad-suite rejected. test_tls 51 pass, ASan/UBSan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 5021a99f8359f78a642bb0cc2b28ab66f6b624e2)
- defensive ASN.1/DER reader: every length/bound checked; malformation
is rejection, never over-read (truncated input KAT-gated)
- x509_parse: tbsCertificate span, sig-alg OID, signature,
SubjectPublicKeyInfo (RSA n/e or EC P-256 x/y), validity dates
- wo_x509_verify_one: one chain link's signature, dispatching to
phase-D RSA-PKCS1/PSS + ECDSA-P256 by the issuer key type
- wo_x509_parse_spki + wo_x509_check_validity (caller supplies time)
- KAT against real python-generated chains (test/gen_x509.py):
RSA CA+leaf (SHA256withRSA), EC P-256 CA+leaf (ecdsa-with-SHA256);
leaf-vs-CA, self-signed CA, wrong-issuer/tampered/truncated reject,
validity window, SPKI extraction. test_crypto 84 pass, ASan/UBSan clean
- deferred to phase F: SAN/hostname match + multi-cert chain walk to a
system CA bundle (both need the target host / trust store, known at
handshake time)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4ec1c75f889df3612e31b9a1a77c4c927fb546c1)
- jarvis 1 (chat loop) brainstormed to ready with forks AUTO-APPROVED for
autonomous execution and flagged in `review_pending` frontmatter for the
developer's second review: Anthropic Messages API backend, env-var API key,
actor-per-conversation SSE relay, durable @table history, session-gated routes
- jarvis 2 (tool use) + 3 (retrieval/RAG) created at refine with forks named
- 00-story iterations table linked to the new files
- blocked until rv2 9 TLS reaches phase F; pure .wo on porch 2/3/6/7 + the seam
(cherry picked from commit 8e160c3fcf9c50a05af073c036c693b192c9e595)
- wo_ecdsa_p256_sha256_verify: NIST P-256 signature verify for EC-cert chains
and TLS 1.3 CertificateVerify
- Jacobian point arithmetic (double a=-3, general add with the H==0 special
cases), double-and-add scalar mult; field/scalar arithmetic reuses the
bignum Montgomery multiply and modexp (Fermat inverses mod p and mod n)
- validates r,s in [1,n-1] and that Q is on the curve (invalid-curve guard)
- verify-only public data -> not constant-time by design
- renamed the P-256 field mul to fpmul to avoid the clash with X25519's fmul
- VERIFIED against a python ECDSA-P256 vector; tamper + wrong-hash rejected;
test_crypto 69/0; ASan/UBSan clean; battery green
- phase D COMPLETE (RSA PKCS1+PSS + ECDSA-P256). Next E: ASN.1/X.509
(cherry picked from commit 92c996ba96d785d098c173d4ac6ace9052ef6a2f)
- wo_rsa_pkcs1_sha256_verify + wo_rsa_pss_sha256_verify (SHA-256), for the
server cert chain and TLS 1.3 CertificateVerify
- bignum: Montgomery multiply (CIOS, 64-bit limbs, __int128), modexp with the
public exponent (R^2 via 128k modular doublings, no division); MGF1-SHA256
- verification is public data only -> NOT constant-time by design (correct and
much simpler than a private-key op)
- assumes a full-length modulus for PSS emBits (standard RSA-2048/3072/4096)
- VERIFIED against python cryptography RSA-2048 vectors (PKCS#1 v1.5 + PSS,
salt 32); tamper + wrong-hash rejected; test_crypto 66/0; ASan/UBSan clean;
battery green
- internal C, no builtin/compiler change. Remaining in D: ECDSA-P256 (D2)
(cherry picked from commit 9118177fbfd03eff9757defea6931afdd68830c4)
- wo_x25519: constant-time Montgomery ladder + mask-based conditional swap,
radix-2^51 field arithmetic with __int128 products (curve25519-donna-c64,
public domain); scalar clamped, u-coord high bit masked per RFC 7748
- internal C (consumer is the TLS ECDHE handshake); no builtin/compiler change
- KAT-gated in test_crypto: RFC 7748 §5.2 both direct vectors AND the
1000-iteration base-point test; test_crypto 61/0; ASan/UBSan clean; battery green
- fixed one transcription bug found via the KAT: crecip needs 5 final squarings
(p-2 = 2^255-21 = (2^250-1)*2^5 + 11), not 3
- rv2 9 ladder: A (AEAD) + B (HKDF) + C (X25519) done; next D signatures/RSA
(cherry picked from commit f41b1c5f56caa841d1904382830baff0f75525d9)
- wo_hkdf_sha256_extract/expand (RFC 5869) + expand_label (RFC 8446 §7.1),
internal C over the existing hmac_sha256; SHA-256 (mandatory-suite hash;
SHA-384 a later add for the AES-256 suite)
- no builtin, no compiler change -- no .wo consumer yet (the TLS handshake
is the consumer); exposed for the C unit test
- KAT-gated in test_crypto: RFC 5869 Test Case 1 (PRK + 42-byte OKM) and
three HKDF-Expand-Label vectors (key/iv/derived-secret shape); 57/0,
ASan/UBSan clean; runtime battery green
- rv2 9 ladder: A (AEAD, = rv2 8) and B (HKDF) now done; next C X25519
(cherry picked from commit c8d27b6c89a80cd97a996ff7d8b64ff4895e4b26)
- portable constant-time AES-GCM software path; AES-GCM now on any CPU
(hw-or-sw dispatch), NIST-KAT-gated both paths (48/0). Remaining D/E;
ARMv8 hw path deferred. Board synced
(cherry picked from commit 138de17988e6bd274abe5e1e2d444ff2689747cd)
- no-intrinsics AES: S-box = GF(2^8) inverse via a fixed-exponent power ladder
(constant-time in the input, no tables), constant-time gf8_mul, byte-oriented
ShiftRows/MixColumns/key-expansion (AES-128 and AES-256)
- constant-time GHASH: bit-by-bit GF(2^128) multiply (mask-driven, no tables)
- aes_gcm_seal/open now dispatch: AES-NI path when present (and not forced
software), else this portable fallback -> AES-GCM works on ANY CPU, so the
phase-B no-AES-NI trap is retired
- wo_aes_force_software test hook; both hw and sw paths verified against NIST
SP 800-38D cases 4 (AES-128) and 16 (AES-256) byte-for-byte; test_crypto 48/0;
ASan/UBSan clean; full runtime battery green
- ARMv8 crypto-extension hardware path deferred (untestable on x86-64 host)
(cherry picked from commit dccf650899798401a9adac8489f34c85ed9304af)
- phases A + B done; B = AES-128/256-GCM via AES-NI/PCLMULQDQ, NIST-KAT-gated,
ASan clean, portable binary (CPUID-gated). Phase C now owns the software
fallback AND the ARMv8 hardware path (deferred, untestable on x86-64 host)
(cherry picked from commit 249b1dbd72122ed6c05f4f0aadb7e1a5e87f844c)
- a second consumer (rv2 9 TLS phase A) reshaped the forks since the draft
- locked: BOTH AES-GCM (128/256, TLS-mandatory per RFC 8446) AND
ChaCha20-Poly1305 (RFC 8439, easy constant-time, cookie default)
- AES constant-time via AES-NI/ARMv8 hardware + bitsliced software fallback
(compiler intrinsics, zero external dep)
- caller-supplied nonce (TLS builds its own per-record nonce); random-nonce
is a cookie WRAPPER (phase D) not the primitive. shape:
seal(key,nonce,aad,pt)->Bytes / open->?Bytes; AES variant by key length
- raw key + length check; hand-rolled (matches rv2 9); ids from 111
- phases A ChaCha -> B hardware AES-GCM -> C software AES -> D cookie wrapper
-> E gate (RFC 8439 + NIST GCM vectors, ASan, reference cross-check)
- risk/test: constant-time mandatory, KAT-gated, reused-nonce documented
- retires the stale "TLS proxy-terminated" OOS line (rv2 9 overturned it)
(cherry picked from commit c8a5a31c39a5d14952056cd0af1b8c1e42d4ed2d)
- decision: HAND-ROLL TLS 1.3 (no vendored lib) per developer call; keeps the
zero-external-dep single binary, and raises risk rather than lowering it —
recorded, owned, with mandatory mitigations
- 1.3-only; RSA-PSS/PKCS1 + ECDSA-P256 + full ASN.1/X.509 chain validation +
trust store + hostname (the scope needed to reach real LLM APIs)
- decomposed into a bottom-up phase ladder: A AEAD (=rv2 8, forces AES-GCM
there) -> B HKDF -> C X25519 -> D signatures/RSA -> E X.509 -> F record+FSM
client -> G inbound server; C/D/E may each split into own iterations
- risk + test strategy section: constant-time, reference-tested (openssl +
RFC 8448 vectors), negative tests first-class, no partial-trust states
- deps: rv2 8 (AEAD), lang 34 (SHA/HMAC), net.connect (110, landed). Board synced
(cherry picked from commit f1881cca8cd0cdd58b1ac844e3e3ea9c234bb99c)
- jarvis (00-story): 6th track, 2nd software built with writeonce — an AI
assistant; direct-HTTPS design; blockers named (net.connect + TLS)
- runtime-v2 7 observability + 8 symmetric cipher: moved from the language
track (were 30/43); 9 in-process TLS: created from the gap jarvis surfaces,
RETIRES the "TLS is the proxy's job" doctrine (both directions)
- language 41 (arena hang): fix design to ready — marshal cross-shard
messages (root), align the shard_id % nshards route/compare + assert bound;
poison-on-free + minimal fixture as follow-ups
- fiber scope-gap analysis (plan/exploration/fiber/01): porch vs fiber, what
porch lacks, would developers prefer porch
- board + dependency-graph synced (porch 2-8 ready; rv2 table; §5/§5a graphs)
(cherry picked from commit 203470ceb2a151fe3584931cd4237af3f96a9f29)
- new builtin net.connect(host, port) -> Int: the outbound-socket gap
language 38 named and jarvis surfaced; the client half of the net verbs
- getaddrinfo for DNS (v4/v6, numeric or hostname), blocking connect with
the same EINTR/stop handling as net.connect_unix, then O_NONBLOCK for the
park plane; returns the same fd-scalar accept yields
- wob.h enum + WO_B_MAX 110; types.ml registration; loader.c arity;
builtin.c sysio dispatch range extended to WO_B_NET_CONNECT; sysio.c impl
- verified: numeric IP + hostname (DNS) connect to a local listener, closed
port traps cleanly; ASan-clean; runtime battery 0 fail
- deferred (next slice): net.connect_dl deadline/park variant (no shard
stall during handshake), on the accept_dl pattern
(cherry picked from commit 13c6f124428243b4956fbb4eceb1e7d0206d45f2)
- whole porch track (2-8) now brainstormed and locked (all ready)
- four decisions: three hooks (on-listen/on-shutdown/on-route-registered);
healthcheck ships BOTH /livez + /readyz; directory listing off-by-default,
documented; Last-Modified via a new small time.utc(ms)->TimeParts builtin
- language enhancement: YES, one small builtin -- time.utc, a gmtime sibling
of time.local (time.local is local-tz, time.iso is UTC-but-ISO); IMS by
string-equality, no date parser. The track's third + smallest language touch
- byte ranges/large files via fs.read_at + iteration 6 writer; not lang-41-exposed
- track language bill now explicit: random_bytes (2), deflate+crc32 (7),
time.utc (8) -- each a builtin with a named consumer, none decoration
- validated against .dev/reference/fiber. Board: whole track marked ready
(cherry picked from commit 9801fceade799e25718606177f09e4306a579e98)
- five decisions: refuse incoherent heartbeat/idle_ms pair at construction;
codec = two C builtins deflate+crc32 (perf over pure-.wo; hand-rolled, no
zlib dep; gzip framing in .wo); ETag over uncompressed bytes + Vary;
Last-Event-ID explicitly unsupported (not silently ignored); Vary via
comma-join
- language enhancement: YES, two builtins -- the track's SECOND language
dependency after iteration 2's random_bytes. CRC32 finally gets its
consumer; inflate deliberately not built (request-body decompression OOS)
- corrected stale dependency: Vary uses iteration 5's comma-join, so story 7
depends on 6 + 5, NOT 2; codec is pure compute, not lang-41-exposed
- confirmed CRC32 absent + iteration 36 bit operators landed (pure-.wo was
viable, traded for hot-path speed)
- validated against .dev/reference/fiber. Board synced
(cherry picked from commit 07f53574dd90f502235b79d4920eab3d684c8b77)
- re-scoped to OUTBOUND streaming only
- three decisions: separate StreamHandler/BodyProducer parallel path (Resp
path untouched -> existing responses byte-identical); streaming routes opt
out of the after-chain, framework refuses at registration to combine with
header-mutating middleware (loud, never silent), security_headers() helper
lets handlers stamp them; chunked REQUEST bodies split into their own future
iteration (parse.wo refusal stays, smuggling cases enumerated for later)
- no language enhancement (net.write framing, fs.read_at/actor source,
interfaces for producer); rides the fiber loop not the actor pool, so not
lang-41-exposed
- fixed title inconsistency: "three iterations wait on" -> "two" (7 and 8)
- validated against .dev/reference/fiber + the app.wo/serve.wo pipeline. Board synced
(cherry picked from commit 15205408e03c3c02a38e00e5d2017a8a11f62f28)
- five decisions: head auto-registers with opt-out (+ patch/options/all);
request ids mirror limiter trust model with a NON-crypto source; per-route
body_limit is a SECOND check after routing (global BODY_MAX stays the
pre-routing ceiling, over-limit = 413); Route fields are corpus-free;
Vary accumulates by comma-join
- key finding: story 5 has NO upstream dependency, not even iteration 2 --
request ids are not secrets, so a non-crypto source (time.ticks+counter)
keeps it startable today; the one porch slice buildable right now
- three story assumptions corrected: per-route limit cannot replace the
global (body read before routing); the container-owned-move corpus fixture
has its OWN Route (adding fields is free); Vary needs no iteration 2
- validated against .dev/reference/fiber; zero language enhancement. Board synced
(cherry picked from commit 0589a13db1f3b7c220d9d9fdc76142af6a63c390)
sessions (3):
- six decisions: pure-auth-primitive row (no payload bag); wall-clock
time.now not monotonic time.ticks (restart durability); login always
mints a fresh id (fixation, no anon-session model); throttled last_seen
touch at idle/20 (not a WAL write per request); Session writes
req.principal; config refuses absolute < idle
- finding: no per-key actor pool, so NOT blocked on lang-41 (plain @table
CRUD, same path storefront uses); the no-bag rule closes the one place
fiber's Set(key,any)+msgp+RegisterType would have hit principle 13
csrf (4):
- five decisions: fiber's hybrid transport (session-stored CsrfToken
@table + double-submit cookie, both must pass; no CSRF for sessionless
apps); opt-in single-use (checkout example); double-click -> distinct
SPENT refusal, NOT coupled to lang-41-blocked idempotency; trusted
origin/referer/Sec-Fetch-Site second layer; refusal classes distinct in
logs, opaque in body
- no actor pool, not blocked on lang-41
both validated against .dev/reference/fiber (v3, 3ca9a9d); exactly ZERO
language enhancement needed beyond iteration 2's random_bytes. Board synced.
(cherry picked from commit 3a4fb4215b23d2516362e2dd0acc5bec6c9aebc0)
- five forks locked: cookies: multi SetCookie beside unchanged headers
map; bare-name random_bytes(n)->Bytes; structural-400 in parse_request
+ on-demand cookie() helper; base64(value).base64(mac) signing;
app-supplied key, no middleware (that is iteration 3)
- validated against .dev/reference/fiber (v3, 3ca9a9d): exactly ONE
language enhancement needed (the CSPRNG); repeated Set-Cookie, cookie
attributes, parsing and signing all map to existing primitives
- corrects phase A registry: random_bytes joins the crypto-family
bare-name table (emit.ml b_* + types.ml), NOT wob.h's module enum;
next free id 84/90, not 110
- board: story 2 marked ready, porch-2 row rewritten off the stale
wob.h/110 claim
(cherry picked from commit 4d31d5359436496aed40cb25611abc7ccd4d7875)
- wo_schema_diff poisons a class (fmap=NULL, new_cid=NONE) when a
referenced/nested type changed or a field type is incompatible — the
field-level map does not apply and wo_wal_migrate transcodes it instead.
- main.c's pre-migration "migrating `X`: +/-fields" print loop dereferenced
fmap unconditionally, so a poisoned-but-field-added class (e.g. a wmux
Window whose nested Vte gained fields) was a NULL read → SIGSEGV at boot,
before the migrate call could refuse or transcode.
- guard the field detail on fmap != NULL; for a poisoned class print
"(a referenced type changed — cannot migrate in place)" and let
wo_wal_migrate proceed. It then transcodes cleanly when no record blocks
it — so an additive nested change (Vte +oscbuf +title) now migrates and
the session replays, instead of crashing serve.
- test_wal 5966/0; verified against the real WAL that crashed (recovers
session `main`); wmux gate 52/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 35efa214d20dd7b055910ae66e4bfcc6201821c9)