- 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)
- five stories status: done; 00-story records the one-run landing
- spec History: three implementation amendments (Signal record not
scalar, caller-owned stdio fds, handler-latch instead of signalfd)
- board NEXT PLAN entry with measured findings (zero transport code
added; the tty-across-the-socket handover proven; the double-raw
refusal restoring the terminal — the "bug" that was the design
working); section rows flipped; graph nodes green
- CODE-LOGIC.md: the runtime-v2 section
- full belt quoted on the board: suites 0 fail both flavors (test_proc
193/0, test_term 60/0), woc 557/0, subprocess 12/0, site 23/0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit bc1b4f070693eb755ad6a9fd0c853fb3e2bda347)
- sendmsg/recvmsg with one SCM_RIGHTS fd and a sentinel byte; EAGAIN
parks in the net mould; the received fd arrives nonblocking as a plain
Int every fd verb accepts
- SO_DOMAIN gate: send_fd on anything but a unix socket refuses by name;
plain bytes deliver nil from recv_fd
- net.connect_unix carried here (iteration 38 still pending)
- legs (single-fiber: unix connect completes while the listener holds
the handshake): a pipe's read end crosses and still reads "ping"; a
tty crosses, term.raw works on the RECEIVED copy and destroy restores
it; refusal and nil legs verbatim. test_term 60/0
- full belt: all suites 0 fail both flavors, woc 557/0,
subprocess-accept 12/0, site-accept 23/0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 1d689027920d6814f87b97c216b0cb42f7eba3e9)
- two verbs on any tty fd; saved termios in a per-shard 8-entry table;
double-raw and restore-without-save refuse by name
- restore is a RUNTIME obligation: vm_unwind at depth 0 (uncaught trap,
fiber reap) restores the dying fiber's entries newest-first, and
wo_vm_destroy sweeps the rest — proven twice in the legs: a DIV0
while raw restores, and even the double-raw REFUSAL (itself a trap)
restores the first raw
- test_term 39/0 against a real PTY pair made by the test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit b439387dbf4f653e034c721bc3f08b83616c5e24)
- mechanics amendment to the spec (recorded at close-out): no signalfd —
the stop-latch pattern generalized. An async-signal-safe handler
latches the number, bumps a sequence and pokes shard 0's wake eventfd;
wo_io_wait's loop head drains latches into fresh Signal{sig} records
delivered via runtime_notify (exported as wo_actor_notify)
- payloads must be heap objects (vm.c drops them unconditionally) — the
Signal record exists exactly for that; class id rides the call as the
appended record operand (sm_record drives it even with no return)
- offerable: WINCH/CHLD/HUP/USR1/USR2; SIGTERM/SIGINT refused naming the
stop latch; shard-0-only registration; coalescing disclosed
- stdlib_modules gains `signal` (and `term`, next task)
- test_term: a real child kills the test process with USR1; the actor's
multi holds one coalesced delivery; refusal leg verbatim. 14/0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 14e03a6a4a343b97c9b47fab1a5e3c4bb69d8201)
- posix_openpt/grantpt/unlockpt/ptsname_r (plain libc, no -lutil); child
setsid + opens the slave as its controlling terminal, initial
TIOCSWINSZ from the call
- Child.stdin == Child.stdout = the master (caller's copy); the slot
keeps a private dup so resize survives the caller closing theirs
- proc.resize -> TIOCSWINSZ; refuses by name on a pipe child
- legs: test -t proves a real tty; stty size reads "24 80" then "40 120"
after a mid-sleep resize; refusal asserted; test_proc 193/0 ASan clean
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 9836c9cd5197153517c054b243cab3b453d130a0)
- a child is fds: Child {id, stdin, stdout, stderr}, driven by the
existing net verbs (echo leg proves cat round-trip through write_dl/
read_dl); caller owns the fds, the runtime owns pid + pidfd
- wait_dl parks on the pidfd: code on exit, nil at the deadline with the
child untouched; one waiter per id, a second refuses by name; stale
ids refused via a generation counter in the handle
- proc.signal through pidfd_send_signal; actor_die kills the streaming
children the dying actor owns; dead fibers cannot linger as waiters
- ids 97-107 registered wholesale (wob.h, loader arities, dispatch
bound); Child + Signal predeclared records in types.ml; unimplemented
ids trap at the default case until their task lands
- test_proc 168/0 (echo, wait trio, one-waiter refusal, 200-round churn
fd-flat), suite ASan clean, woc-test green
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 9be87f159f1bf9cdd509ceed160e7ea518fde46c)