- cherry-pick table row + a per-prefix `dev` → `master` sub-table (39 prefixes,
regenerable from the `-x` trailers) + what the pick taught: "already on
master" is the mapping table, never prose (`79e6da4` had never been
picked); earlier picks had dropped hunks; excluded tracks leave dangling
links; the equality proof (master + the 70 excluded commits == dev in code)
- registry: statuses for every prefix picked or deliberately left (wmux,
lang-18, porch2-rng), rows for the unregistered ones (tls/crypto/rv2-*/net,
docs-only prefixes, one-off fixes)
- obligations for the wmux pick: the `justfile` recipe and wmux-accept.sh's
WO_EPHEMERAL edits
- board: standup entry for the landing — what moved, what stayed and why,
every gate count on master, the known fibers TSan red, what is next
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- the leg copies the porch package, whose store declares RateLimitCounter
default-durable, and ran the check program with no WO_DATA: since 6a that
is a startup refusal, so `just web-app` read 56 checks, 1 failure on dev
and on master alike — the 6a blast-radius pass missed this leg
- RAM-only opt-in on that one run, boot notice filtered from the byte-exact
compare (the db-actor / wmux pattern); web-app 56/0
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 1ce195da25f527eb1fed3e9e6b8843e00315f25e)
- `woc build <dir> -o <example>/target/<name>` wrote its temp file beside the
output and failed with "No such file or directory" when target/ was absent;
target/ is gitignored, so every fresh checkout hit it — the db-actor and
db-bench gates went red on a clean master worktree while passing on dev,
where the directories exist from history
- the driver now creates the output's parent (mkdir -p shape) before the
temp write; project-mode builds and existing directories are unchanged
- single-binary-smoke.sh gains `build-into-missing-dir` (4 checks, was 3),
red on the old driver, green on this one; woc-test clean
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit e9213bb949e1c26cb3a29d6f9babf2322a82d839)
- mode 100644 -> 100755, matching every other scripts/*-accept.sh
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ec797d9646acc9519d21358dc28f5443a88940cd)
- idempotency built, reviewed, then reverted WHOLE to the tag
archive/porch-idempotency. Not a design failure: it passed its gates.
It provokes a C-runtime SIGSEGV in wo_arena_alloc/wo_str_new under
concurrent call()-parked callers
- the evidence for that attribution: over ten gate runs every failure
was an idempotency leg and none was the limiter's, which drives the
same pool through the same call/park machinery. The begin arm has 5x
the allocation sites inside receive and moves a whole Req plus a
Handler through the mailbox
- before the split the suite reported 0 to 6 failures run to run; after
it, five consecutive runs at 56 checks, 0 failures
- PoolMsg loses digest/req/handler, and NullHandler/dummy_req/fresh_req
go with them — every rate-limit count used to allocate a throwaway
Req it never read
- IdempotencyKey is KEPT and commented: the schema is settled and the
digest-as-column decision cost a review round to get right
- the limiter's saturation 503 has no leg of its own now (§19 drove
Idempotent). Stated in the README rather than papered over — a
deterministic leg needs a slow actor, and only the reverted arm was
- new: porch 9 (idempotency, on hold) and language 41 (the arena crash,
with the reproduction harness and the evidence that localises it)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 79e6da4465133dc555e913c960d544ef1c7bedd8)
- the agent rename database-developer → codd (and its guide) happened inside
lang-18's `aab4878` on dev, which stays there; the `docs(agents)` pick
brought codd.md in beside the old file — remove the old name and its guide
- docs/stories/porch/09-idempotent-replay.md: added on dev by `79e6da4`, whose
earlier pick onto master (`refactor(porch-store)`) landed without it — the
board, porch 1 and porch 4 link to it
- docs/stories/porch/10-memory-features-over-table.md: the refine stub
language 18's docs commit created on dev; the board and porch 00-story link
to it, the code it waits on is not on master
- docs/examples/skill-catalog/README.md: the story link fix from `b3d8c40` that its
earlier pick (`8311330`) dropped in conflict resolution — dev's version taken
- `just linkcheck` on master: every remaining broken link is a wmux story or
spec (track not picked) or a developer-local `.dev/reference` symlink
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- board: language 18 row (hold lifted 2026-09-11, split — 18 keeps
transaction { }, cache/flags/jobs to porch 10); In-progress rows for
databasev2 4 part B / 5 / language 18 and the Active slice; databasev2
rows 2 (CLOSED, 6a), 4 (part B re-brainstormed), 5 (ready), 7 (CLOSED),
13 (new); the held list drops 18
- dependency graph: new §8 databasev2 (nodes 1–13, edges, states table);
graph 1's 23/32 nodes turn done and their edge becomes undirected (they
compose; neither needs the other); language 18 / porch 10 nodes and
edges across the porch and language graphs; wmux gains the databasev2 2
edge (DB2W) the prose already named
- databasev2 00-story: sequence rows 1/2/4/5/7/8/11/12/13/14, the ASCII
graph (2 no longer needs 1; 2 → 11, 12) and the order rationale
- 01: the budget finding redirected to 5; 06: the Needs line marked
superseded, task 7's 2026-08-30 measurement quoted; 09: the report's
group-by is still refused, schema-sharing is language-track work; 10: an
in-tree signing answer exists (rv2 9), Ed25519-vs-reuse still open
- porch 00-story: row 10 (memory features over @table, refine stub) and
the "not porch's" table updated for the split
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 423b3c187b626f69da1ddb942c3c7849a3ee5a73)
- inserted 2026-09-12 from a developer question ("would I build an
e-commerce site on writeonce?"): the load is a non-question — a hundred
orders a minute is under two durable writes a second against an engine
that group-commits thousands; what the developer hits is the SHAPE of the
query and schema surface, measured against PostgreSQL habits
- goals, each its own future slice: range queries and ordering through an
ordered index (today's indexes are equality-only hash buckets); `skip`
beside `take` (specced 2026-08-15, never built); group-by aggregation for
the reports (parser accepts, types.ml refuses — owner: language track);
composite unique, check rules, on-delete policy beside FK restrict;
export/import and a read-only attach (databasev2 9)
- Given/When/Then per page or report of docs/examples/shop; out of scope;
the forks left open for a brainstorm before any slice starts; progress
and history empty — `status: pending`, `readiness: refine`
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit f33ae982c954d5478d549a6dc5f15463de43ab1b)
- `readiness: ready`, `review_pending` (forks 1–12 settled 2026-09-10 by
codd-shoney under autonomy; look first at the cuts — fork 4 no
back-pressure, no cross-table shedding, no warn threshold; fork 2 the
per-table bound is rows only; forks 3/7 the default budget)
- two independent questions: RAM for all tables is one process budget in
bytes (`WO_DB_MB`; unset = the default, never "no budget"), breached by
refusal — the crossing insert traps WO_T_OOM, one stderr line names the
largest table, the budget and its source; during replay the crossing is
exit 2. Capacity of one table is `@table(max_rows: N)` with `on_full:
refuse | drop_oldest`; drop_oldest legal on `durable: false` only,
oldest = smallest live id; eviction never touches a durable table
- the default is the kernel's limit minus what the process holds at boot:
cgroup v2 memory.high/max up the ancestry, else MemAvailable, minus VmRSS
before replay — no fraction, no invented reserve; headroom is MEASURED
(A4 re-runs iteration 1's 64 MiB swap-off leg, refusal must precede kill)
- estimate = what the engine asked the allocator for, chunk-rounded; keys
tables count what is resident; observed on the WO_WAL_STATS exit line
- phases A–F, progress A1–A4 / B1–B3 / D1–D2 / F1 / P1 with owners; `.wob`
v9 carries max_rows + policy; `status: pending`, Phase A startable now
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 579199af01581ebad3d2fa5f7e208745b210e9f9)
- retitled "group commit, and the async barrier"; the 2026-08-15/20/28
banners compressed into a trail; `readiness: refine`, `review_pending`
(forks 1–5 and 8–10 decided under autonomy 2026-09-10 by codd-shoney;
6 and 7 keep readiness at refine)
- what part B is FOR: the read tail on shard 0 while a barrier blocks — and
only that; mechanism: the drain pwrites as today, then submits ONE bare
IORING_OP_FSYNC and keeps working; held replies released by the
completion; the epoll fallback is part A unchanged; ordering with
compaction and the deferred drops/re-points; the inline durable write on
shard 0 unified through its own inbox while a barrier is in flight;
completion delivery on a busy shard 0; shutdown reaps an in-flight
barrier before wo_wal_close
- fork 6 (kernel floor and raw-syscall shape) goes to lintor; fork 7
(go/no-go) is settled by one measurement — tmpfs vs ext4 `mixread.p99` —
then one developer answer; both answers already sit in
.dev/zack/databasev2-4b.md, the fold into this story is pending
- progress table B1–B9 with sizes and owners (cyril B1/B8, lintor B2, the
runtime agent B3, codd + pm B9); no new knob, no new dependency
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 7ceb7b8805da41e67661744746bf2d0b7879cc50)
- symptom: `seed` of docs/examples/residency on a FRESH log, rc 139, in both
WO_DATA forms — found smoke-testing databasev2 7, independent of it
- root cause, two defects composing: wo_wal_fold_row_at wrote `*msg`
unguarded while wo_idx_probe borrows with msg = NULL; and the databasev2 12
schema head was staged lazily AFTER db.c captured the first keys-resident
row's offset (`koff`), so that offset pointed at the schema record
- fix (already on dev, prefix db2-keys): wo_wal_next_offset stages the
pending head before returning an offset; the fold tolerates a NULL msg;
both failing-first, and a control build with wal.c reverted reproduces
the trace
- third finding: residency-accept.sh never checked seed's rc, so 20/0 was
green over a crash — `e274f4a`; the gate is 32/0 since
- `residency.keys.fit` rc 74 confirmed a SEPARATE defect (compaction/replay
of keys-resident offsets), still open under codd.md "Next bugs"
- `status: done`, `readiness: ready`; counts test_wal 6629 → 6660,
make -C runtime test 8462/0 at the time of the fix
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit b5b1da795917f0446a1e4a0317d9136c33acb262)
- frontmatter `status: done`, `readiness: ready`, `review_pending` (the
nonexistent-path rule settled under autonomy 2026-09-10)
- every criterion met with its evidence: task 1 smokes A–D (`b31bd40`),
residency section 8 checks i–vii (`aaea6b2`), db-bench `--wo-data-file`
181 checks / 5 failures — the same 5 as the directory form
(`residency.keys.fit` rc 74, databasev2 13's sibling, not this defect),
temps-beside-the-log test plus its mutation control (`ccee2d0`)
- the fork, settled: existing dir or trailing `/` → <dir>/shard-0.wal,
byte-identical; otherwise the path IS the log, created behind an existing
parent; a missing parent or a non-regular non-directory path refuses,
exit 2, naming path and parent — never a silent mkdir -p
- `WO_DATA` stays a path: ephemerality is WO_EPHEMERAL=1 (databasev2 2 task
6a), so the file-vs-directory parse carries no `:memory:` sentinel
- progress table with the four hashes; history
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 38f4f1e5832e79b9834a93fd6c3273fefe5eeadc)
- database-developer becomes `codd`: scope is the whole embedded DB (engine,
runtime seams, the compiler's @table/query surface); doctrine rewritten
from what landed (fatal commit, group commit per drain, checkpoint by
rename, delta fold, schema head, v8 table bit, no-WO_DATA refusal); file
map with anchors; state as of 2026-09-11; architect only — no gates, no
tests, names the checks for cyril and the tasks for zack
- one four-role pattern shared by three tracks: `<architect>` brainstorms
and owns contracts, `-zack` implements ONE ready iteration with a
resume-safe ledger under .dev/zack/, `-cyril` owns every test above unit
level and the gate ladder, `-pm` keeps stories, board and graph truthful
(`model: sonnet`); families codd (database), fielding (porch), ada (jarvis)
- `codd-shoney` is the developer's proxy: brainstorms `refine` stories to
`ready`, reviews `review_pending` forks; `lintor` the kernel consultant
over .dev/reference/linux
- README: roster (reads, gates), the families rule, proposed agents not yet
written and the order to add them
- docs/guides/codd-subagent.md, 00-doc-audit.md, 08-project-structure.md
follow the rename
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 830bbb16d5dd990478149678c642857bb65466f4)
- test_oracle_all_vs_keys_same_update_sequence continues the shared
sequence 3×WO_DELTA_MAX_HOPS steps, alternating scalar and Text, and
asserts the `resident: all` and `resident: keys` rows equal after EVERY
step; the fold's hop count proves the chain terminated at least twice and
never exceeded K; then the keys log replays into a fresh store and is
compared against the oracle once more — the criterion as written, which
the story carried as ⚠ "an expected value, not an oracle table"
- wal.h: wo_wal_append_row_image's comment claimed the flattened image is
written as WO_WAL_INSERT; it is WO_WAL_UPDATE — an INSERT would replay as
a duplicate id; compaction alone writes INSERT, into a FRESH log — as 11
landed it and its story recorded
- story 11: the criterion flips to ✅ naming the test; the sequencing note
and out-of-scope bullet record task 7's 2026-08-30 measurement (16× vs
105× collapse under a cap, 1.53× faster than swapping) — the work stands
- test_wal 6295 → 6880 pass, 0 fail; 21 runtime suites 0 fail
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit d841390f3087a0c2542ddf23f25f15037a7d4d71)
- story 02: `status: done`, `review_pending` (forks 1–7 auto-approved for
autonomy); progress rows 6a ✅, 6b ➡ databasev2 5 Phase A, 7 `a310496`;
5c/5d rows cite the `dev` hashes (the pre-merge ones were unreachable);
task 6a's Given/When/Then met; Info records the seven forks (sentinel over
`:memory:`, its rules, the refusal contract, startup-only, the budget
leaves for 5, library-owned tables bind consumers, the v8 table bit);
History keeps the first cut that refused every class-bearing program
- database/src/CODE-LOGIC.md: "Startup refusal + WO_EPHEMERAL" — contract,
hatch, table bit, measured blast radius, deferred items, proof; the
dispatcher paragraph no longer says a failed commit un-applies the row
(fatal since databasev2 4 part A; WO_T_IO unreachable from a write path)
- residency spec + plan: task 6 items annotated with the 2026-09-09
decisions; the byte budget marked moved to databasev2 5
- README, seven example READMEs and four guides carry the one-line rule
(durable default refuses without WO_DATA; WO_EPHEMERAL=1; durable:
false); shop's RAM-only command sets the sentinel
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 2c3531998124042fe736388e8b926abda3841194)
- residency-accept.sh section 7, six checks: the refusal names the class
and all three ways forward (exit 2); WO_EPHEMERAL=1 runs from RAM with
the boot notice and a write round-trips; WO_EPHEMERAL with WO_DATA
refuses; WO_EPHEMERAL=2 refuses naming the accepted value; keys-resident
still refuses under the hatch; a plain class (the corpus `methods`
fixture) runs with no WO_DATA, rc 0, nothing on stderr
- blast radius measured gate by gate — each run without the export first,
kept only where the program refused: oop-e2e (fixtures declare tables);
db-bench.py's ram/msgrate/growth/randread legs (the durable legs drop it,
so a WO_DATA in the caller's shell now refuses loudly instead of silently
turning a RAM leg durable); db-actor per run (its restart pair sets
WO_DATA); chat (porch's store declares RateLimitCounter default-durable —
a library's table binds the consumer); wmux client legs (same image as
the server, no WO_DATA; servers and the WO_DATA-carrying r11cli `env -u`)
- byte-exact compares (db-actor single-shard, wmux client) drop the one
notice line; fibers, subprocess, log-watcher declare no table — untouched
- db-bench.py ceiling note: the checked refusal is databasev2 5's now
- residency 32/0, oop-e2e 131/0 with this tree
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 4553ca15da235c155b7ad31bbb077c3ad8e88fee)
- main.c, startup only: WO_DATA unset and a class carrying WO_CLASSF_TABLE
without WO_CLASSF_VOLATILE (`durable: true`, the default) refuses — exit 2,
one stderr line naming the class and the three ways forward (WO_DATA=<dir
or file>, WO_EPHEMERAL=1, @table(durable: false)); before, every write
was silently dropped at exit — the one outcome `durable: true` forbids
- WO_EPHEMERAL=1 (exact value) is the whole-program escape: one boot notice,
rc 0, the RAM path byte-for-byte the old one (db.c untouched); any other
value refuses; set alongside WO_DATA refuses regardless of tables; the
`resident: keys` loop still wins and is not rescued
- .wob v8: WO_CLASSF_TABLE 0x08 (WO_CLASSF_ALL 0x0f), set from emit.ml's
cr_is_table — the first cut keyed on !VOLATILE and refused every
class-bearing program (fibers' Tick, subprocess's ConnMsg), because v7
spelled `durable: true` as the mere absence of a bit
- loader refuses VOLATILE/RESIDENT_KEYS without the table bit ("storage
flags on a class that is not a @table"); a v7 image is refused by the
exact-match version check, as v7 refused v6; disasm prints `table`;
runner.ml's independent validator carries both rules; obj.h comment
- test_loader: test_storage_flags_need_table (forged flags word: both
refusals, and the same bits WITH the table bit load); no golden moved
- contract: docs/plan/oop-vm/00-wob-format.md "v8: the table bit"
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 863692a590d426da0047831ae315142ef5b24416)
- registry rows for the prefixes this landing uses, claimed before their
first commit as the file requires: `db2-ephemeral` (databasev2 2 task 6a),
`db2-4b` (part B re-brainstorm), `db2-5` and `db2-14` (story docs),
`agents` (the persona roster), `status` (cross-track board/graph sweeps)
- `db2-7` and `lang-18` registered after the fact — both already have
commits on `dev` (`b31bd40`, `6b4b960`) and had no row
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit d0e658f06df8f3390d56841bdb4093cb8d509fb8)
- Second half of the fresh-log seed SEGV: every `*msg = ...` in the fold
was unguarded, and `wo_idx_probe` (table.c:373) borrows with `msg == NULL`
because a candidate that does not fold is simply not a hit; a malformed
record under an index probe was therefore a zero-page write.
- Guard: `const char *sink; if (!msg) msg = &sink;` at the top of the fold;
wal.h documents [msg] as optional. A future malformed record refuses the
candidate by name instead of segfaulting.
- Failing test first: `test_fold_row_at_tolerates_null_msg` (test_wal.c) —
head-only log, fold at offset 0 (schema record) and past the tail with
`msg == NULL` -> -1 both; with a real `msg` the names "record header is
malformed" / "no intact record at that offset" still arrive. Pre-guard:
ASan SEGV `wo_wal_fold_row_at wal.c:1886` from the test.
- Gates: test_wal 6660/0 (was 6650); `make -C runtime test` 21 suites
8462/0 (was 8452); wovm-asan clean; residency `seed` fresh dir + fresh
app.db rc 0 under wovm_asan.
- CODE-LOGIC §Schema migrations bullet extended with the guard + test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 1b6750d78db991af464994c2188d219519dfe16f)
- `seed` of docs/examples/residency (`resident: keys`) on a FRESH WO_DATA
segfaulted rc 139 in both the dir and the file form; pre-existing.
- Root cause: the databasev2 12 head record was staged lazily INSIDE the
first `wo_wal_append_*` (wal.c `stage()`), after db.c:78/293 had read
`koff = wo_wal_next_offset(w)`; the first keys-resident row was re-pointed
at the schema record and its first read folded "record header is
malformed"; `wo_idx_probe` borrows with `msg == NULL` -> zero-page write.
- Fix: one helper `stage_schema_head` shared by `stage()`,
`wo_wal_ensure_schema` and `wo_wal_next_offset` (no longer a pure inline):
the head is staged before any caller observes `off + len`. Still lazy,
never for a log that stays empty; head-stage OOM is `wo_wal_stage_fatal`.
db.c untouched; compaction/migrate stage the head explicitly, unaffected.
- Failing test first: `test_keys_resident_fresh_log_first_row` (test_wal.c),
the db.c:78 sequence call for call, then read-by-id, `wo_idx_probe`,
replay. Pre-fix: `koff != 0` FAIL, `wo_row_read` -1 "record header is
malformed", ASan SEGV `wo_wal_fold_row_at wal.c:1871` via `table.c:373`.
- Gates: test_wal 6650/0 (was 6629); `make -C runtime test` 21 suites
8452/0 (was 8431); wovm-asan clean; residency `seed` + restart `order`
under wovm_asan rc 0 on a fresh dir AND a fresh app.db; a control build
with wal.c/wal.h reverted reproduces the SEGV.
- CODE-LOGIC §Schema migrations: "Head before any offset capture" bullet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 631007839451fb970e9dec83338d19c09b3043ab)
- residency-accept.sh section 8 (11 checks): file-form seed -> restart prints
the directory form's line; `find -mindepth 1` shows exactly app.db; missing
parent exits 2 naming path + parent, no mkdir -p; a fifo exits 2 "neither a
regular file nor a directory"; `d/` still writes d/shard-0.wal; `nodir/`
keeps the pre-7 "cannot open .../nodir//shard-0.wal" bytes; WO_EPHEMERAL=1
with the file exits 2 on the 6a conflict
- kill -9 battery against app.db: stdbuf -oL vehicle, asserts the kill landed
(rc 137) before verifying every acked row replays; forced compaction
(WO_CHECKPOINT_BYTES=1, WO_WAL_STATS proves >= 1 ran) leaves app.db the only
artifact and every row replays
- failing-first on the pre-7 wovm: 9 of 12 new checks red ("cannot open
.../app.db/shard-0.wal"); the two trailing-slash pins and the 6a conflict
pass by construction — they pin what must stay byte-identical
- db-bench.py --wo-data-file: restart proof + crash battery against
<tmp>/app.db, legs tagged .file, file form also asserts app.db is the only
artifact; no metric, bench/baseline.json untouched; quick run unchanged
without the flag (181 checks / 5 failures both ways, all five the known
residency.keys.fit rc 74)
- READMEs: db-bench env-knob row for WO_DATA=<path>.db + the driver flag;
residency run instructions name the file form
- gates: just residency 32/1 (the seed rc, pre-existing), make -C runtime
test 21 suites 8452/0, just oop-e2e 129/0
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit aaea6b2c0f818efd0cdf472ccbd9bdd09eac5554)
- scripts/residency-accept.sh:155 ran docs/examples/residency `seed` with its
rc unchecked; the inserts commit before the crash, so the restart legs passed
on the log a dead seed left behind and the gate read 20/0 while seed died 139
- new check "example: seed exits 0" — its FAIL names the rc (139 = SIGSEGV)
and the log to read
- failing-first on today's binary: `FAIL example seed -- rc=139`; the SEGV is
the pre-existing keys-resident fresh-log defect (wo_wal_fold_row_at, HEAD
wal.c:1837), zack's fix in flight — this check stays red until it lands
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit e274f4a932688bccf8310581199fa0d26f737eea)
- docs/plan/oop-vm/04-db-binding.md, WAL section, "Where the log lives":
WO_DATA is always a path; directory form (existing dir or trailing `/`
→ `<dir>/shard-0.wal`, pre-7 bytes incl. the `//`), file form (the path
IS the log, created only under an existing parent), the two refusal
lines verbatim, too-long refused not truncated, one file at any core
count, compaction/migration temps + parent fsync derived from the log
path never from WO_DATA, the two pinning tests named.
- database/src/CODE-LOGIC.md, `wal.c — durability`: the resolver's three
codes and main.c's wording, why no mkdir -p, trailing slash on a missing
dir kept as the pre-7 `cannot open` on purpose, `parent_dir_of` shared
by the boot check and the post-rename fsync.
- Both paragraphs sit in regions untouched by the uncommitted 6a/12 doc
work in the same files; no other docs touched (story/board are pm's).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit f1985bae5d110ed773159393bd82c32b73bfb672)
- test_file_form_temps_beside_log (runtime/test/test_wal.c): the log is
`<dir>/app.db` — an operator's name, not shard-0.wal — with a sibling
directory `app.db.d/` as the decoy nothing may land in.
- Proof by blocker: a DIRECTORY planted at exactly `<file>.compact` makes
`wo_wal_compact` and `wo_wal_migrate` each return -1 with the log
untouched (record count unchanged, blocker still an empty dir); a temp
anywhere else would have let them succeed.
- Blocker removed: compaction 10 → 2 records, migration n,t → n,t,extra
succeeds, `<file>.compact` gone after each rename, the directory holds
exactly {app.db, app.db.d}, the decoy is empty, the migrated file
replays into the new shape (slots[0] == 107, slots[2] == 0).
- The parent fsync'd after a rename is `parent_dir_of(<file>)`, the helper
the resolver shares (task 1), so its derivation is pinned there; fsync
itself is not observable from a test.
- Green on first run (57 assertions) as a pin must be; teeth shown by a
mutation control — compaction's temp redirected into the decoy turned
14 assertions red (`wo_wal_compact(&w, &db) == 0, want -1`, …).
- `make -C runtime test`: 21 suites, 8431 pass / 0 fail (was 8374/0).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ccee2d04fddfb96c7dfab1c17f235e3a9bbc69fb)
- `wo_wal_resolve_data_path` (database/src/wal.c|h): an existing directory
or a trailing `/` → `<dir>/shard-0.wal` byte for byte (the `//` after a
trailing slash included); otherwise the path IS the log — opened if a
regular file, created by `wo_wal_open` if absent under an existing parent.
- Refusals as codes for main.c: WO_WAL_PATH_NO_PARENT (out = the parent, so
the line names it), WO_WAL_PATH_NOT_A_FILE (fifo/socket/device),
WO_WAL_PATH_TOO_LONG (today's snprintf truncated silently).
- `parent_dir_of` shared by the resolver and `sync_parent_dir`: the parent
checked at boot IS the parent fsync'd after a compaction/migration rename.
- runtime/src/main.c: the resolver replaces the unconditional
`"%s/shard-0.wal"`; each refusal is one stderr line, exit 2 through the
6a destroy sequence; never mkdir -p. The 6a block is untouched.
- Failing first: test_resolve_data_path — 4× -Werror (implicit declaration
+ three undeclared codes); green after: 21 assertions (dir, trailing
slash, absent file, regular file, bare name, missing parent, parent is a
file, fifo, two too-long).
- `make -C runtime test`: 21 suites, 8374 pass / 0 fail (was 8353/0).
`make -C runtime wovm-asan` clean; smoke: file form seeds + replays with
`app.db` the only artifact; missing parent and fifo refuse rc 2 naming
path + parent; dir and trailing slash unchanged; WO_EPHEMERAL conflict
inherited from 6a.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit b31bd4052624be460de1c18cf208661539397e09)
- emit.ml: a `try … catch (e) nil` is `?T` (ty_of_expr) and the nil arm takes that destination, so a `?Int` nil is WO_NIL_SCALAR and an Int body's legitimate 0 no longer reads as nil (it used to fall back to the zero word via the body type / enclosing return type)
- owner.ml: `transfer` on a projection (`d.tags`, `x[i]`) of an owned value reports WO-E305 instead of returning false silently — the silent path compiled `Out { tags: d.tags }` to an alias that both records dropped (the "json.decode as T corruption": not json's, a double free language 44's poison now aborts on); heap scalars exempt (store sites copy)
- error catalog: WO-E305 row; owner.ml module doc updated
- corpus: run/try-nil-int-zero, compile-fail/no-partial-move, run/decode-record-crosses-return (Text copied, record moved whole — the archived `.. ""` workaround is unnecessary)
- verified: oop-e2e 126/0, tests/regress/lang-41 compile, --emit sweep over the non-porch examples, web-app gate 56/0 (porch in project mode) — no legitimate program trips WO-E305
- story 41: both side defects marked fixed; board prose updated
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 2d54710e693fafee4b8d6561cc9cba7b95415d89)
- tls-server-accept.sh: each probe pins openssl s_client's -ciphersuites — ec/ChaCha20-Poly1305, rsa/AES-128-GCM, plus openssl's default list whose first suite (AES-256-GCM) the server must skip — 5/0
- tls-accept.sh: the Python/OpenSSL stub prints the negotiated suite; the happy-path ok line carries it — 5/0 (ChaCha under the peer's server-preference default)
- rv2 8 story: E landed (real-protocol interop replaces the infeasible `openssl enc` AEAD check); D (encrypted-cookie wrapper) re-homed to porch as the consumer's phase after porch 2 — fork auto-approved, review_pending; status: done
- porch 2: the encrypted-cookie out-of-scope bullet now points at the landed primitives and names the wrapper as its follow-on
- board row rv2 8: in-progress -> done
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ac3bf74da4f45f624d7d3b440c8bcf17f4aec3a9)
- four forks settled with KISS defaults grounded in runtime/src: counters+gauges only (profiling split out), Prometheus text rendered in .wo from a map<Text, Int>, pull via proc.metrics(), stack trace on trap lands first
- phases A (trace on trap at both trap sites) / B (proc.metrics from existing gc/arena/fiber fields) / C (porch mounts /metrics — consumer's phase)
- builtin id to be confirmed against WO_B_MAX at build time (random_bytes claims 119 per porch 2's brief)
- review_pending marker: forks auto-approved 2026-09-09, developer second review before code lands
- board row: refine -> ready
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit feb11c3aad613ed7f41b280b27c1f6c0dda92ec7)
- wo_arena_free stamps the header: class_id = WO_CLS_FREED (0xFFFFFFFF), shard_id = 0xFFFF, flags/pad = 0
- freelist link moves from offset 0 to offset 8 so the poison survives on the list; wo_arena_alloc pops from offset 8
- wo_drop_obj aborts first on a poisoned header: a double free is a diagnostic, not a catchable state
- WO_CLS_FREED defined in wob.h beside the builtin id space
- test_arena: test_poison_on_free (poison stamped, LIFO chain through the relocated link, class drains to a fresh bump) — 17/0
- full suite SUITE_ALL_ZERO, wovm + wovm_asan rebuilt, just db-actor 10/0 (lang-41 5x marshal gate unchanged)
- story 44 status: done; board row + dependency graph L44 (41 -.follow-up.-> 44)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 78ae3be403ba533db6f0e181bff201717f789a30)
- pmul_ct: double-and-add-always over the Renes–Costello–Batina complete
projective addition formula (Alg. 4, a=-3) — one exception-free formula for
add and double, identity (0:1:0), so there is NO point-at-infinity branch.
Closes the documented residual: the Jacobian jadd/jdouble ladder's fp_zero
checks leaked k's leading-zero count (a bit-length hint) during ECDSA sign
- wo_ecdsa_p256_sha256_sign uses it; affine x = X * Z^-1 (projective), the
inversion via the constant-time modexp. Dead Jacobian jmul_ct/jpt_cmov removed
- RFC 6979 A.2.5 vectors still byte-exact (test_crypto 130/0); server loopback
(signs with this ladder) still green (test_tls 123/0); ASan/UBSan clean
- docs: rv2 9 review_pending — close_notify + complete-formula ladder moved
from deferred to landed; lang-41 decision 4 fixture marked landed
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit fba30352b965e0f3b749168421a920a832df541b)
net.close on a TLS connection now seals a close_notify alert (warning,
close_notify; RFC 8446 §6.1) with the application write keys and sends it
best-effort/non-blocking before the inbound drain + close(). Peers see a
clean end of stream instead of truncation — openssl's "unexpected eof while
reading" is gone (verified), browsers stop treating the reply as aborted.
Covers both directions (one code path). just tls 5/0, just tls-server 4/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 54020a45fdb1efab792212170b8f36c2d38d95be)
Code is the source of truth; these claims no longer matched runtime/src:
- "net.connect does not exist" — landed 2026-09-07 (id 110); net.connect_tls /
read_tls / write_tls (115-117) + net.accept_tls (118), WO_B_MAX 118. Fixed
in jarvis 00-story (problem statement + architecture + out-of-scope), porch
00-story (proxy middleware row), rv2 7 (push-collector fork), 00-code-review
- "TLS: none / proxy-mandated forever" — retired by rv2 9 (in-process TLS both
directions). Fixed in porch + web-app + site example READMEs (proxy is now a
deployment choice; HSTS row), 00-code-review
- "no RNG anywhere in the runtime" — imprecise: the runtime has a getrandom(2)
source since rv2 9 (TLS ephemerals), but nothing exposes it to .wo yet.
Fixed in CODE-LOGIC (digests), lang 34, porch 2, status lang-39 row
- "porch 9 blocked on language 41" — lang 41 fixed 63065ff. Fixed in porch 1,
jarvis 00-story, status NEXT PLAN, dependency graph (L41 done, P9 ready)
- dependency graph §7 rewritten: the runtime side is done; jarvis 1 waits only
on porch (developer's porch-first order). Adds jarvis 1's dependency table +
the build order that satisfies it
- 00-code-review: a dated 2026-09-09 re-verification appended (record kept)
- site README lives in the writeonce-site submodule: committed there, pointer
bumped here
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit f1049dd9b7c7770da28bcabfc1cb1324621e7ee6)
Root cause (decision 1): cross-shard send/call/monitor pointer-shared the
message into the receiver's shard (e->payload = msg_val), so a worker read and
eventually dropped an object living in the sender's arena — a double free, then
a class-0 forge, then a modulo self-route livelock, all downstream of that one
broken invariant ("VM heaps are never read cross-shard", which wo_db_rpc keeps).
- actor_marshal: the sender encodes the message into an arena-independent neutral
form (wo_db_val_encode, the same marshal wo_db_rpc uses) and drops its own
original — no pointer crosses an arena boundary, so the double-free class is
gone by construction. actor_unmarshal rebuilds it in the receiver's arena
(wo_val_decode_vm) and frees the neutral. Applied to the 4 cross-shard
producers (send x2, call, monitor) + the 3 consumers (kinds 0/5/7). Same-shard
paths untouched (the WO_SHARDS=1 fast path never failed). Call replies are
scalars by contract, so kind 6 needs no marshal.
- eng_settle_inboxes: undrained kind-0/5/7 payloads at teardown are the neutral
form now — free with wo_db_val_free, not wo_drop_obj (caught by ASan mid-fix).
- decision 2: wo_route_free traps a shard_id >= nshards header (a corrupt/freed
block) instead of self-routing it into the settle livelock.
- proof: tests/regress/lang-41/cross-shard-marshal.wo (a multi<Text> sent +
called cross-shard, both sides drop) — clean 12x/5x under WO_SHARDS=4 + ASan;
shard-settle repro still clean 8x; full runtime suite 0 fail (same-shard
byte-unchanged). `just db-actor` extended with the new fixture.
- unblocks porch 9. Follow-ups: poison-on-free (decision 3), corpus fixture (4).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 63065ff75799f7f43b2bce6de61e77856799566f)
- rv2 9 story -> status: done. §G G3 landed; ladder A–G complete, live-gated
both directions (just tls 5/0, just tls-server 4/0). review_pending +
phase rows + G sub-phases updated
- doctrine retired where the story named it: language 34 ("TLS permanently
the proxy's job"), language 38 ("proxy-terminated ... no HTTPS clients"),
porch 00-story ("TLS ... proxy-terminated") — each corrected to point at
in-process TLS (net.connect_tls / net.accept_tls)
- status board: rv2 9 row DONE + a top summary; NEXT PLAN = porch then
jarvis (sequencing set: jarvis follows porch)
- jarvis 00-story: sequencing note (no longer runtime-blocked; porch first)
- CODE-LOGIC: the inbound-server section (net.accept_tls, signing, slot
refactor, RST-drain, gate)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit f3a3c962e5f288c25e505851edef4e0a5df9a85f)
- net.accept_tls(listener, certfile, keyfile) -> Int (id 118, WO_B_MAX->118):
accept (parks like net.accept), load+cache the server identity per path in
the shard, run the blocking deadline-bounded server handshake, return a TLS
conn fd. Real clients terminate against the runtime — no front proxy
- wo_tls_conn refactored: holds the negotiated application keys (not an
embedded driver), so read_tls/write_tls serve both client and server
connections via the record layer; the handshake drivers are transient
(heap, ~100KB, freed after). net.close drains a TLS conn's inbound before
close() so it sends FIN not RST (clients send close_notify)
- server handshake loops past the client's change_cipher_spec (TLS 1.3
middlebox-compat) before its Finished — the openssl-interop fix
- private-key file loading: wo_tls_pem_one (any-label PEM block) +
wo_pkey_parse; per-shard identity cache (vm->tls_id), freed in reap
- docs/examples/tls-server + `just tls-server`: openssl s_client validates
our hand-rolled server (EC + RSA certs) and gets the reply — 4/0; the
outbound `just tls` gate stays 5/0 through the refactor
- wiring: wob.h, loader.c, builtin.c dispatch, types.ml, vm.h
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 2d4c30033c36c88de5b7ab7cc1042c9537238297)
- wo_pkey_parse: PKCS#8 PrivateKeyInfo (wrapping PKCS#1/SEC1), bare PKCS#1
RSAPrivateKey, and bare SEC1 ECPrivateKey -> RSA (n,d) or the EC P-256
32-byte scalar. Reuses the X.509 DER reader; spans point into the buffer
- KAT: all three formats parse, and the extracted key signs a hash our
verify accepts (RSA-PSS + ECDSA); garbage rejected. test_crypto 130,
ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 819d67226a94c4cd5a765ec403e57466c64f1e65)
§G sub-phase G2: the sans-io server handshake FSM (wo_tls_server) landed,
loopback-KAT'd against the client driver (EC + RSA identities, app
round-trip). Remaining G3: net.accept_tls + private-key parse + live gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 57613bddc621a90970d9443ae5a5deacffe0cfca)
- wo_tls_server: the mirror of the client driver. parse ClientHello (pick
suite, extract x25519 share, echo session id; reject no-x25519/no-1.3),
build ServerHello, derive the role-symmetric keys, emit the encrypted
flight (EncryptedExtensions + Certificate + a signed CertificateVerify +
Finished), verify the client Finished, switch to application keys
- server_sign_cv signs the CertificateVerify with the phase-G1 primitives
(RSA-PSS or ECDSA-P256 + a minimal DER SEQ{r,s} encoder); parse_client_hello
+ build helpers reuse the file's wire reader/writer
- wo_tls_server_start builds the Certificate message from a cert chain +
private key (RSA n/d or EC scalar) + ephemeral; encrypt/decrypt over the
application keys
- KAT: loopback — our client driver against our server driver, EC then RSA
server identity, reaching ESTABLISHED with an app round-trip both ways.
test_tls 123, ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 34d2b8f87cebe536cd2b1b33e6948251ec11684f)
§G sub-phase G1: constant-time RSA-PSS + ECDSA-P256 signing landed and
KAT'd (RSA vs python from-spec; ECDSA vs RFC 6979 A.2.5). Remaining G1c
(private-key PEM/DER parse) folded into G3 (which reads key files); the
server FSM takes raw key material.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit f02518cdabab02043a04c6bdd28a81b86bb9fbd4)
- wo_ecdsa_p256_sha256_sign: deterministic nonce (RFC 6979 HMAC-DRBG over
the key + message — no RNG, no nonce-reuse/bias risk), then r = (k*G).x
mod n and s = k^-1 (z + r*d) mod n
- constant-time in the secret: jmul_ct (double-and-add-always + point
cmov) for k*G, and bn_modexp_ct for k^-1 mod n and the affine inversion.
Known residual (documented): the ladder leaks k's leading-zero count (a
bit-length hint, not the key) — a complete-formula/Montgomery-ladder
upgrade is the named follow-up
- KAT: byte-for-byte vs the RFC 6979 A.2.5 P-256/SHA-256 vectors ("sample"
+ "test"), our sign verifies with our verify, determinism checked.
test_crypto 115, ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 1bc6d04f9e18180dc7d0a6e5e7021dbd588e499a)
- bn_modexp_ct: constant-time modexp for the SECRET exponent — squares and
multiplies every bit, selects the product with a mask (bn_cmov), so the
op sequence is independent of d (the existing bn_modexp branches on the
bit, fine only for the public e)
- wo_rsa_pss_sha256_sign: EMSA-PSS-ENCODE (RFC 8017 §9.1.1) + modexp with d;
caller supplies the salt (fresh in production; fixed makes the KAT
deterministic). Private key (n,d)
- KAT: deterministic sign vs a python from-spec oracle byte-for-byte
(fixed salt), our sign round-trips through our verify, tamper rejected.
test_crypto 108, ASan/UBSan clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit cf8fdfcc47b2b076b63b9c63bf56411615b0d6f9)
- §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)