Commit graph

156 commits

Author SHA1 Message Date
841f6c8f3f feat(db2-keys): gate the residency measurement, close out task 7
- new `residency` leg in db-bench.py driving docs/examples/residency-bench:
  two tables identical except the annotation, control cap + binding cap
- ITS OWN PROGRAM, not a db-bench mode: declaring a resident: keys table
  is a WHOLE-PROGRAM constraint, so the no-WO_DATA refusal fires for
  every mode in the module. Putting those classes in db-bench's shared
  types made growth/ceiling/randread — which run without WO_DATA —
  refuse to start. Caught by running the leg, not by reading it
- gates the RATIOS, waives the absolutes: ops/sec under a cap is swap
  and disk I/O and belongs to the box. Same split randread makes
- rss_ratio 2.55 floor 2.0 tol 10% (structural, like bytes_per_row);
  overcap_vs_swap_x 1.53 floor 1.0; in_ram_cost_x 4.23 ceiling 8.0;
  all_collapse_x 105.4 floor 2.0
- all_collapse_x exists because the leg's FIRST run silently measured
  nothing: at QUICK's 40k rows a 48 MiB cap binds neither mode, so the
  "over-cap" half was not over cap. The cap now scales with N and the
  leg asserts it binds
- verified the gate bites: rss_ratio 1.4, overcap_vs_swap_x 0.6 and
  in_ram_cost_x 12.0 are all rejected
- task 7 closed: both criteria moved to Met with how each was verified

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a310496664982c372f51b43113465eb8ad9e9fb5)
2026-08-30 20:38:03 +02:00
882c1f7d24 feat(db2-keys): task 7 — measure resident: keys against swapping
- two tables identical except the annotation, 200k rows, 40k reads in
  one key order, WAL on ext4 (not /tmp, which is tmpfs here and would
  have put the log in RAM), rootless cgroup v2 cap
- WIDE shape, 2.55x smaller resident set: 34.4 MB vs 87.5 MB. That is
  the real win and the thing the mode was built for
- under a 48 MB cap (between the two resident sets): keys 19635 ops/s
  vs all 12854 — only 1.53x faster than letting the kernel swap
- degradation is far gentler though: all collapses 105x from its own
  uncapped throughput, keys 16x
- costs 4.2x read throughput when memory is not tight, and writes are
  markedly slower — the keys fill did not finish in 2 min where the
  resident fill plus 40k reads did. No design doc had costed writes
- THE UNANTICIPATED FINDING: cgroup limits charge the PAGE CACHE, so
  moving rows to a file does not escape a container memory limit. WAL
  37 MB + RSS 34 MB cannot both live under a 48 MB cap, so every pread
  reaches disk. The premise "the page cache will hold the hot rows"
  fails in exactly the deployment this targets
- first attempt used Int-only rows and showed parity; recorded, because
  drop_payload frees a field's VALUE and an Int's value is its inline
  slot word, so that shape cannot benefit and would have condemned the
  feature for the wrong reason
- verdict: keep it, to fit ~2.5x more data in given RAM — not to make
  an over-capacity table fast

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7cba9b1174b0bf581314b3147e25cc49e6f49464)
2026-08-30 20:38:03 +02:00
cfd660a5e6 docs(db2-chains): spec + story for bounding a row's delta chain
- fixes a limitation iteration 2 shipped: compaction was supposed to
  bound chain length, but wo_wal_should_compact triggers on a whole-log
  byte ratio and cannot see one hot row's chain
- tier 1, flatten on update: the update path ALREADY folds the row for
  index maintenance and the fold already walks hop by hop, so it reports
  depth for free. Past a fixed K it writes a full row instead of a
  delta. Read <= K+1 reads, replay O(K^2) per row. No format change, no
  per-row RAM, no new trigger
- tier 2: our compaction policy has a proportional term and a
  SUPPRESSOR misleadingly called a floor; postgres's floor TRIGGERS on
  small absolute garbage. Add that term and a ceiling
- design read from .dev/reference/postgresql, not recalled:
  heap_page_prune_opt gates on an O(1) on-page hint then page fullness
  against Max(fillfactor, BLCKSZ/10); autovacuum uses base + scale *
  reltuples clamped by a max (50, 0.2, 1e8). Neither thresholds on
  new-bytes-versus-old-bytes
- K deliberately does NOT scale with table size: postgres scales a
  table-level aggregate with proportional harm, ours is per-row with
  additive cost, so scaling up would make big databases boot worst
- the story says plainly it should NOT be next: task 7 has still never
  measured whether resident: keys beats the kernel's own paging

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f667cad2cfbe187b5973440ab1af015b2df288f8)
2026-08-30 20:38:03 +02:00
64035bf177 docs(db2-delta): resident:keys has storage; move done criteria to Met
- "no storage behind it" / "nothing yet stores a table that way" was
  false — CRUD, checkpoint survival and updates all landed; replaced
  with an accurate summary naming task 6/7 as what remains
- the three checked delete/delete-replay/update criteria sat in
  Outstanding despite being done; moved to Met, leaving Outstanding
  holding only genuine task 6/7 work

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit b575678ee95fa3125fa4e8145320a9cdca10ba38)
2026-08-30 20:38:03 +02:00
e08c26309a feat(db2-delta): lift the resident:keys refusal, prove it end to end
- loader.c: delete the INCOMPLETE-update BAIL; durable:false +
  resident:keys stays refused (nowhere to read from)
- table.c: root-cause fix for the Text-index gap — a keys-resident
  borrow now holds ENGINE values, matching wo_row_ptr's contract
  (table.h's "no VM pointer" doctrine), not a VM-decoded row. Fixes
  idx_hash/idx_cols_equal/wo_idx_probe AND db.c's GET_FIELD/PROBE
  arms with one change; reproduced pre-fix as an ASan
  heap-buffer-overflow
- docs/examples/residency: Product is genuinely resident:keys;
  residency-accept.sh's refusal leg replaced by proving the program
  runs and stock survives a restart (11/0)
- test_wal.c: oracle test drives resident:all and resident:keys
  through the same update sequence and asserts identical rows;
  Text-indexed-update test catches the representation bug; five
  pre-existing tests corrected to the fixed contract (4746/0)
- story, README, status board, CODE-LOGIC.md updated; three known
  limitations documented: mid-drain stale reads, O(N^2) replay in
  chain length, compaction blind to per-row chain length

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit b87c68f950f01aa5e572fbb86a0f374adc83d813)
2026-08-30 20:37:49 +02:00
7b39da7eb6 fix(db2-keys): a logged delete must replay on a keys-resident table
- wo_row_remove's keys arm borrows the row from the log to find its
  index entries, and a borrow reads through db->rt->wal. At boot that
  pointer is not wired yet: main.c replays first (main.c:226) and
  assigns rt.wal afterwards (main.c:268)
- so the borrow found no log, the remove failed, and replay reported a
  valid tombstone as CORRUPTION. An UPDATE record would have failed the
  same way, since replay applies it as remove-then-recreate
- replay now lends the runtime a read-only view over the fd it already
  has open, for the replay's duration only, and restores what was there
- broken by the delete fix in 76b8fd9 — deletes worked in-process but
  their tombstones broke the next boot. Unreachable in production only
  because the loader still refuses the annotation
- pinned by test_keys_resident_delete_then_replay, verified failing
  against the unfixed code (2 failures) and clean with it
- found by asking whether the read-modify-append plan was ready, not by
  a gate

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit dc25462461b9f79d70c803f7174adc90fa16c90e)
2026-08-30 20:37:27 +02:00
516bd8362d docs(db2-keys): a runnable example for per-table storage
- docs/examples/residency: one program, two tables filled by the same
  loop, differing only in the annotation. Run twice against one WO_DATA
  and orders replay while sessions do not
- the example checks its own claim (exits 1 if a durable:false table
  survives, or a durable:true one fails to replay) rather than narrating
  it in a print
- resident: keys is written out as a commented block with the loader's
  exact refusal, so the frontier is visible in the example rather than
  only in a story. It documents WHERE the refusal happens: woc compiles
  it and emits a .wob; wovm exits 2, because the annotation is a
  load-time property
- residency-accept gains two legs: the example runs and its restart
  claim holds, and the refusal message the README quotes is checked so
  doc and code cannot drift apart
- the gate writes the example's output to /tmp/residency.log,
  banner-separated, for tail -F
- README commands verified verbatim; they needed mkdir -p because wovm
  will not create WO_DATA

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit c9c7e03e62c3918cb65ef7994d1e33a0c5337b71)
2026-08-30 20:37:27 +02:00
7ad52937b2 fix(db2-keys): delete on a keys-resident table was memory corruption
- wo_row_remove read the id map's value as a slot, but on a keys table
  that value is a LOG OFFSET (hput(t, id, wal_off + 1)). slot_row does
  no bounds check, so a delete indexed t->slabs[] with a byte offset and
  then called db_val_free on whatever it landed on — arbitrary frees,
  not a wrong answer
- keys tables now take their own arm: no slab slot, no bitmap bit, no
  free-list entry to return. The index hook needs the row's values, so
  the row is borrowed from the log for exactly that long
- wo_row_ptr carried the same trap and is public. It cannot refuse keys
  tables outright (insert legitimately calls it while the map still
  holds a slot), so it now detects the offset case — index past the
  slabs, or bitmap bit clear — and returns NULL. Callers all handle NULL
- test_keys_resident_delete pins it; it SEGVs against the old code,
  verified by reverting the fix rather than assumed
- found while auditing every hget() reader before narrowing the loader
  refusal to allow benchmarking. The refusal was justified in the docs
  by "updates are unimplemented" while actually standing in front of
  this too: a guard whose stated reason is narrower than its real one
  gets removed by someone who believes the stated reason

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 76b8fd944af9ed062467bdf9ab93c2e96dd198cf)
2026-08-30 20:37:27 +02:00
8311330531 docs(db2-keys): reconcile databasev2 and porch markdown with the code
- loader's resident:keys refusal said "rows are still fully resident"
  and "until tasks 5c/5d land". Both false since f606fc9. Corrected to
  name the real blocker: UPDATE needs read-modify-append
- databasev2 00-story: the sequence graph drew 2->3->4, which reads as
  3 needing 2 and 4 needing 3. Both backwards, and it still drew the
  2->5->6 path the 2026-08-27 amendment retired. Redrawn stating only
  real dependencies, with 4 and 3 shown as composing rather than
  ordered, and the execution order that actually happened
- databasev2 03: the hazard and its Outstanding entry both claimed
  nothing fails "because iteration 2's storage half is unimplemented".
  Marked discharged, and recorded that the hazard named only half the
  danger — the bitmap walk would have dropped keys rows outright
- databasev2 06: pending -> hold (largely superseded, revisit only on
  a measurement); dated its 5c/5d references
- porch 01: rewritten to the settled shape. readiness ready, status
  in-progress, phases B and C marked superseded with why
- porch 01 claimed time.after "is still a reserved builtin id". False —
  builtin 90, implemented. That claim is what made the iteration look
  cheaper than it is
- porch README gains honest ledger rows for both features (partial,
  being rebuilt), not shipped
- skill-catalog README pointed at a story path that moved tracks;
  linkcheck now 0 broken

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit b3d8c403e1d19ac27ec966de85cb293e0765795c)
2026-08-30 20:36:55 +02:00
533fc5294f feat(db2-keys): rewire remaining readers, survive compaction
- wo_row_read and the @unique shadow probe go through borrow/release;
  release runs before every exit, including wo_row_read's early return
- updates on a keys table refused explicitly in wo_row_update_field and
  the slot variant: no slab slot to mutate, and writing the borrow's
  scratch would discard the write silently. Needs read-modify-append
- compaction walked the bitmap, which a keys row has no bit in — every
  such row would have been dropped from the new log. Now walks
  wo_row_next_id and re-points each row to where it lands
- moves records byte-for-byte (copy_record) rather than decoding: a
  borrowed row holds VM values, enc_val expects engine values, and ASan
  caught that mismatch as a 4294967292-byte memcpy
- wo_row_set_offset updates a value in place and never rehashes, so a
  wo_row_next_id cursor stays valid while compaction re-points
- a compaction that fails after moving rows is fatal: the map would name
  an unlinked temp file, and the intact log replays correctly
- test_keys_resident_survives_compaction pins both failure modes; rows
  rewrite in hash order so offsets really move
- loader still refuses resident: keys — updates are not implemented

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f606fc9b76d983cac2b348f03f7d4f01433cd905)
2026-08-30 20:36:31 +02:00
02b4b13a52 Merge master into db-residency-doctrine — and close the two half-exposed features
The branch was 17 ahead / 25 behind with 11 conflicting files, and drifting
further: db.c had been rewritten twice on master since (group commit, then
compaction). Resolved rather than rebased so both histories stay legible.

Conflicts, and how each was settled:

- db.c: BOTH semantics kept. Master's fatal path and compaction check now sit
  behind the branch's `table_is_durable` predicate, in all three inline arms —
  a volatile table reaches neither the barrier nor the compaction check
- db-bench sample: every mode from both sides (growth, growth-verify, randread,
  replayseed, wmix) and ONE `boot` mode, which both sides had added
  independently
- db-bench.py: all six legs kept. Both sides had also grown the same
  WAL-size helper under different names; collapsed into one
- perf-targets: the branch's §5 (RAM ceiling) then master's §6/§7 — master's
  numbering had already assumed a §5 it did not have
- story frontmatter: master's `status` (the landing truth) plus the branch's
  `readiness` axis. 03 would have read `done` + `refine`, which is a
  contradiction — it was brainstormed and landed on master, so `ready`
- board: both standup blocks newest-first; master's chain rows (a superset);
  the branch's databasev2 1-2 rows with master's 3-4. Fixed a stray `|` in
  master's row 3
- baseline: master's, then REGENERATED from a full campaign — 143 metrics,
  132 checks, 0 failures with both sides' legs present

TWO HALF-EXPOSED FEATURES FIXED, because the merge rule is that master gets
no feature that is honoured in name only:

- `resident: keys` PARSED, set a .wob flag, and did nothing: rows stayed fully
  resident. A developer could declare a 120 GB table keys-resident, watch it
  compile, and be OOM-killed. The loader now REFUSES it with a message naming
  what to write instead, until tasks 5c/5d land. The compiler still parses it
  and its AST golden still passes, so the grammar work stays tested
- `durable: false` was honoured ONLY on the inline path. wo_db_exec_req had no
  guard at all, so a volatile table written from an actor on a worker shard
  would still be logged — precisely porch's session-table case, and precisely
  what iteration 2 exists to provide. All three request-path arms now carry the
  same predicate. Found by reading the merged code, not by a test: the obvious
  probe runs main() on the primary and therefore only exercises the inline path

Verified on the merged tree: wovm-test 0, woc-test 0, oop-e2e 122/0,
residency-accept 8/0, db-bench 132/0, linkcheck clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 10:14:25 +02:00
8b29eb492c docs(db): T6 closeout — checkpoint documented, chain's last link lands
databasev2 3, task 6. Documentation, plus three gate-tolerance
corrections that are justified rather than silent.

- 04-db-binding.md: the NORMATIVE rule — compaction may run only where
  nothing is staged (a correctness requirement, not scheduling), recovery
  is unchanged, and a failed compaction is a missed optimisation rather
  than a durability event
- database/src/CODE-LOGIC.md: why one file and not snapshot-plus-tail
  (Postgres CANNOT compact — page deltas; ours are full row images, so a
  compacted log IS a store), why rename is the whole crash-safety story,
  why the dump flushes but does NOT fsync when it does, why the
  replacement is preallocated, and where the trigger is checked
- README: the checkpoint knobs, the extended walstats line, the boot mode
- story -> status: done, with criteria split met/outstanding
- board: standup entry in the six-question shape, both rows rewritten

THE OBLIGATION IS AT THE COMPACTOR, not only in a spec: compaction moves
every record, so it invalidates every WAL offset iteration 2's
`resident: keys` stores, and the loop that knows each record's new
position must rebuild that map. Nothing fails today because that storage
half is unimplemented — it would fail later, looking like corruption.

Board claim corrected before it shipped: I wrote that the concurrency
chain is "complete". It is not — chain 5 stays in-progress because
databasev2 4's part B was never done and its premise was invalidated by
part A. Every link has landed its PLANNED work; that is a different
statement.

Gate tolerances, each with the measurement that justifies it:

- ckpt.pause_us_max is no longer gated relatively. The raw pause scales
  with the live set and this workload's live set is not fixed (wmix's
  hist_dump inserts a row per latency bucket), so gating it gates the
  box. Added ckpt.pause_us_per_mb — the engine's own rate, gated for
  real, and the metric that would have caught the 8x dump regression —
  with the absolute 50ms budget still guarding the raw pause
- ram.*.msgrate 15% -> 70%. PRE-EXISTING, and measured: 10.7M-17.9M
  msgs/sec across ten full runs, several predating this work — a 1.67x
  spread against a 15% gate
- durable.sN.*.p99us 100% -> 300%, with more evidence than the first
  widening: mixread 1043/2318/4147us, mixwrite 1623/4446us on the same
  build. Floors stay the real guard and are not slack

Battery: wovm-test 36 suites 0 fail, woc-test, oop-e2e 119/0,
db-bench 117 checks 0 failures, linkcheck clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 06:48:34 +02:00
74399ffc68 docs(plan): WAL checkpoint — 6 tasks, databasev2 3
Plan for the approved spec. Code-free per the repo convention
(docs/plan/discarded.md:54); the executor writes the code.

- T1 wo_wal_compact: walk live rows via the bitmap, append one INSERT
  each through the EXISTING append path, fsync, rename over the live log,
  fsync the parent dir, reopen the descriptor. Test asserts BOTH that the
  log shrank AND that a replay reproduces the same rows/ids/values —
  shorter alone is worthless, a truncating bug also passes that
- T2 a stale temp file is removed at open and never read. The test uses
  PLAUSIBLE records, not garbage: garbage would be rejected anyway and
  would prove nothing
- T3 the trigger as a PURE decision (used bytes, last compaction's
  measured output, floor) so it is unit-testable without a store; env
  knobs for floor and ratio, which is what makes the policy testable at
  all. No timer, with the reason. The check is called only where nothing
  is staged, asserted by a test that stages and expects deferral
- T4 kill -9 DURING compaction, extending the existing fork-based crash
  battery. Asserts the PROPERTY — the store equals the pre- or the
  post-compaction content, never a mixture, and every acked id survives.
  Run repeatedly and state the count: it is a race, one green run proves
  little
- T5 measure space reclaimed, boot before/after, and the stop-the-world
  PAUSE against a stated budget. If the pause exceeds it, stop and report
  — the alternatives are bought against that number, not before it
- T6 closeout, including the normative ordering rule in 04-db-binding.md

Constraints carried from the spec into every task:

- recovery must NOT change; a task editing the replay path should stop
- the dump must FLUSH PERIODICALLY. stage() grows the staging buffer by
  doubling, so dumping a whole store through one buffer would hold the
  entire store in RAM — the unbounded growth databasev2 1 identified as
  how this engine dies
- a FAILED compaction is a missed optimisation, not a durability event,
  so it must not take databasev2 4's fatal path
- gate tolerances must not be waived wholesale (part A's T4 made that
  mistake), and the baseline is full-mode — writing a quick-mode baseline
  over it is a regression part A also made

Deliberately NOT a task: rebuilding the `resident: keys` offset map. It
cannot be implemented against a feature that does not exist yet, so T6
records it as an obligation at the compactor and in the story instead of
a stub nobody can test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 17:44:18 +02:00
69c34c9a89 docs(spec): WAL checkpoint — compact by rewrite + atomic rename
databasev2 3, chain 6. Brainstormed 2026-08-28 after databasev2 4 part A
landed.

Design: compact the log by rewriting it as one record per live row into a
temp file, fsync, rename over the live WAL, fsync the parent dir, reopen.
Recovery is COMPLETELY UNCHANGED — boot still opens one file and replays
it — and the crash criterion ("the same store as if the checkpoint had
never started") is satisfied by rename, not by code we must get right.

Read .dev/reference/postgresql for this. The finding is that PG's design
is UNAVAILABLE to us, which is what makes the simpler option legitimate:

- PG never compacts its WAL; segments before the redo point are recycled
  by rename or unlinked. Its records are page deltas, so a compacted redo
  log is not a store — hence heap files, a control file, a redo pointer,
  a second recovery source and a separate process
- ours are FULL ROW IMAGES (apply_record implements UPDATE as
  remove-then-recreate), so a compacted log IS a complete store. That one
  difference deletes all of the above from the design
- what IS worth porting is the ordering discipline: publish the new
  "recovery starts here" atomically and LAST, so a crash falls back. PG
  needs a start-of-checkpoint redo pointer plus an end-of-checkpoint
  control file update; we get the same property from one rename, because
  we can swap the whole data set atomically and PG cannot

Forks settled:

- no snapshot format — the compacted log is the snapshot, existing grammar,
  so no new encoder or decoder and the dump reuses wo_wal_append_insert
- one source, not two
- volume-only trigger, as a ratio against the LAST compaction's measured
  output (the denominator is known exactly; estimating the live set would
  mean estimating Text) with an absolute floor. NO TIMER — PG's exists to
  bound loss from unflushed buffers and we have none; an idle log does not
  grow. Copying the mechanism without the reason was the trap
- stop-the-world, with the pause measured against a stated budget rather
  than assumed acceptable; alternatives are bought against a number
- compaction may run ONLY where nothing is staged (right after a barrier),
  or a staged record lands in a file about to be replaced. Normative

Recorded before it can be found late: compaction invalidates every WAL
offset iteration 2's `resident: keys` stores, so the compactor rebuilds the
offset map as it writes. Nothing breaks today because that storage half is
unimplemented — it would break later, looking like corruption.

Also corrected exploration/postgresql/buffer-and-checkpoint.md, which was
wrong on two counts: PG does NOT update its control file by rename (in-place
full-block write + CRC32C), and its checkpoint sketch assumes writeonce has
segment files, which it does not and deliberately will not.

Grounding measured on master: seed 20000 leaves a 986614-byte log; 20000
updates take it to 2590262 bytes with the SAME live rows, and boot+verify on
that store is 155ms.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 17:39:38 +02:00
0b618ace19 docs+fix(db): T6 closeout — and reads no longer wait for the barrier
databasev2 4 part A, task 6. Mostly documentation, plus one real fix the
full battery caught.

THE FIX. The drain held EVERY DB reply until the barrier — including
reads, which stage nothing and have no stake in durability. That parked
readers behind an fsync for no reason: durable.sN.mixread.p99 rose from
~1043us to 4057us. Only a statement that actually staged a record now has
its reply held. Caught by the gate, not by review.

THE TRADE, recorded rather than smoothed over. What remains is inherent: a
barrier blocks the owner shard LONGER (more records per fsync) though LESS
OFTEN, so anything queued behind one waits. Three full runs of the same
build gave durable.sN.mixread.p99 of 1043 / 2318 / 4147us and wmix.p99 of
8758 / 20000us — a 2-4x spread with the box near idle. So part A buys ~3x
write throughput at the cost of a longer, noisier tail on the owner shard,
and that is the strongest argument for part B (submit and keep serving).

- durable.sN.*.p99us tolerance widened to 100% WITH the reason in the
  code: a 2-4x-variable tail gated at 50% gates the disk, not the engine.
  The floor is the real guard and is not slack — mixread's (4172us) came
  within 25us of tripping on the worst run. Baseline refreshed; a fresh
  full run then passed 106 checks 0 failures

EXIT STATUS MOVED 3 -> 74 (sysexits EX_IOERR). 3 and 4 are already used by
SAMPLES for their own meanings — db-bench's own `verify` exits 3 on a
checksum mismatch, and it is the gate that exercises durability, so a
durability abort exiting 3 would have been indistinguishable from the
mismatch it should help diagnose. The low range belongs to programs.

Docs:

- story: progress, the payoff measured two ways, the cost side, criteria
  split met/outstanding, and a "part B — its premise changed" section:
  it was justified by "close the 66x gap", but that gap is two problems
  and only the concurrent one was a batching problem
- board: standup entry in the six-question shape; both databasev2 4 rows
  rewritten. They had said "close the 66x gap" — recorded as MIS-STATED
  rather than quietly renumbered
- 00-wob-format.md and 04-db-binding.md: the normative failure contract
  ("a failed WAL commit traps WO_T_IO after un-applying the row") was
  false; corrected, along with the tick-scoped group commit that never
  happened
- database/src/CODE-LOGIC.md: where the barrier runs and why there, why
  replies are held, why the inline path is asymmetric, the one failure
  rule, and how to measure it
- db-bench README: the wmix mode, the env knobs, and the tmpfs warning

Battery: wovm-test 36 suites 0 fail, woc-test, oop-e2e 119/0,
db-bench 106/0, linkcheck clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 16:48:23 +02:00
026919762b docs(plan): WAL group commit — 6 tasks, databasev2 4 part A
Plan for the approved spec. Code-free per the repo convention
(docs/plan/discarded.md:54); the executor writes the code.

- T1 a failed barrier is detected and fatal — one entry point that names
  the operation, errno, WAL path and batch size, then exits. The abort
  path itself stays unexercised and the task says so rather than buying
  coverage with a fault-injection switch
- T2 the barrier moves to the drain point and replies are held; the
  request path stops committing per append. Riskiest task, and its risk
  is one place: the crash legs. Plan says STOP if they fail, do not
  adjust the test
- T3 the inline path takes the same fatal rule but keeps its own barrier,
  with a comment explaining the asymmetry so the next reader does not
  "fix" it. Looks like a no-op; without it the two paths disagree, which
  is the unevenness the spec exists to remove
- T4 prove batches actually form BEFORE measuring the payoff — otherwise
  a win gets attributed to the wrong cause. Also records peak staged
  bytes, settling the no-cap decision with a number
- T5 measure, gate, write it down. If the payoff is absent, say so and
  stop: part B must not start on an unproven premise
- T6 closeout, including the error catalogue — WO_T_IO leaving the write
  path is language-visible and must be written down

Spec corrected while planning: it pointed at durable.s1.seed as the
payoff. Wrong, structurally — worker shards hold no WAL, so a queue only
exists when other shards write, and a serial writer has nothing to batch
with. The real target is durable.sN.mixwrite: 480 ops/s at p99 5888us
against s1's 1023 at p99 664, so adding shards currently makes durable
writing WORSE. That inversion is a better argument for the iteration than
the one the story recorded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 09:06:09 +02:00
75aedf1216 docs(spec): WAL group commit — databasev2 4 part A
Brainstormed 2026-08-28. The iteration is split: part A batches, part B
(io_uring submission) is deferred until A's measurement says whether the
blocking boundary still dominates.

The story's premise needed correcting first:

- it says "replace fsync-per-commit with io_uring group-commit", but the
  engine commits per STATEMENT — db.c calls wo_wal_commit right after
  every append, all six sites, so each row change is one pwrite + one
  fdatasync
- so two independent wins were being carried as one, and only the second
  needs io_uring. The staging buffer already holds any number of records;
  today it never holds more than one. Part A is mostly deleting calls
- iteration 22's numbers say A is where the payoff is: durable writes
  4460 ops/s, mixwrite 1023 ops/s p99 664us, against 1.28M ops/s reads

Forks settled:

- batch boundary is QUEUE-DRAIN, not the tick this story had recorded: a
  tick adds latency to a lone writer, taxing an idle system to serve a
  busy one. Queue-drain self-tunes and needs no knob
- shard 0 holds each reply envelope instead of sending it, commits once
  when the queue empties, then releases all — so a writer is acked after
  the barrier carrying ITS record, which today is true only because
  every batch has one member
- a failure between "RAM mutated" and "record durable" is a FATAL,
  diagnosed abort. This replaces uneven behaviour that already exists:
  insert rolls back, update and delete do not and say so in a comment
  ("RAM ahead of disk"). Batching would have multiplied that
- consequence stated, not slipped in: WO_T_IO leaves the write path
- no batch cap initially; peak staged bytes is measured so the question
  is settled by a number

One gap disclosed rather than hidden: forcing a real fdatasync failure
needs mount privileges, so the unit test proves the error is DETECTED and
the abort itself stays covered by inspection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 07:48:11 +02:00
62d29d6a77 docs(24): T10 closeout — stories done, board, graph, ledger, CODE-LOGIC
Iteration 24 closes, absorbing 31 and 34. No code in this commit.

- stories 24, 31, 34 -> `status: done`, each with a landing banner. 24's
  records the gate numbers and BOTH disclosed deviations: monitor takes
  three arguments (the caller may be `main`, which has no mailbox) and a
  v1 `call` reply is a typed scalar (which is what let the agreement be
  checked at compile time, WO-E226). 31's notes it landed INSIDE 24 and
  that a fifth mechanism it never anticipated came out of proving the
  gate — the drain guarantee (40). 34's names the gap it did NOT close:
  still no RNG, so CSRF/sessions stay blocked
- board: in-progress row cleared, marker doc deleted (convention), the
  standup entry in the six-question shape, chain note — next link is
  databasev2 4 (io_uring group-commit, chain 5)
- graph: PUBSUB2 (pub/sub + WebSockets, "rejected until here") -> done
- porch ledger: a WebSocket/pub-sub row added; the cancellation row now
  says what it actually waits on rather than repeating "the arc"; the
  README's "no WebSockets/SSE" limitation was stale — WebSockets are
  supported, SSE and chunked encoding are not
- CODE-LOGIC: runtime/src gains the actor-lifecycle section (call, death,
  the cap counter's sender/home-thread split, the monitor walk, the timer
  list), the drain guarantee, and the digest section; docs/examples/chat
  gains its own — actor topology, WHY two actors per connection, fd
  ownership, and the shutdown choreography

Battery after the doc edits: wovm-test 36 suites 0 fail, woc-test exit 0,
oop-e2e 119/0, chat 11/0, web-app 46/0, linkcheck clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:06:31 +02:00
d87846354e docs(board): iteration 24 is 9 of 10 and on master; only T10 closeout remains
- narrative said "five of ten tasks landed" and named the branch as the
  live location; T4/T5 (ids 89/90) had landed and the slice merged to
  master 2026-08-27 (60414a1, fast-forward)
- records what was verified ON master: chat 11/0 at the full 1000-client
  soak, runtime 36 suites 0 fail, compiler 556 checks, corpus 119 checks
- T10 closeout is what still holds stories 24/31/34 open

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 23:44:56 +02:00
60414a1754 feat(runtime): the shutdown drain guarantee — iteration 40
A message sent before the stop flag is observed must be delivered and run
before the engine stops. One rule; a spin count could never express it.

- root cause in `shard_main` (runtime/src/vm.c): NEXT_RUNNABLE() already
  stated the contract — "a WORKER on stop keeps DRAINING ... so queued
  shutdown messages (close frames!) still run" — but the IDLE branch
  contradicted it, calling fib_reap_all and breaking on WO_IO_STOP,
  abandoning its inbox for wo_engine_stop() to free wholesale
- an actor between messages is exactly that idle case, which is why a WARM
  soak server hid it: warm shards held live fibers and took the right path
- fix: while the primary's drain window is open, an idle worker adopts its
  inbox and runs what arrives; sched_yield on an empty poll so a drain
  cannot burn a core per shard and starve the actors it exists to let run
- unreachable at WO_SHARDS=1: wo_engine_stop returns early at nshards <= 1

Measured:

- fresh-server SIGTERM drain: 5 of 16 failing before, 20 of 20 clean after
- `just chat` at the FULL 1000-client soak: 11 checks, 0 failures, both
  WO_IO backends, ASan clean with zero leaks
- the fd leg settled at scale too: 1000 connections left the count at 44,
  unchanged after 20 more — lazy per-shard init, not a leak
- runtime battery 36 suites (18 x both dispatch flavors) 0 fail;
  compiler 556 checks 0 fail

- story: docs/stories/language-runtime-database/40-shutdown-drain-guarantee.md
  (chain 3 with 31, status done), board row, slice marker updated
- outstanding and named: a pin below the gate needs new multithreaded test
  infrastructure — nothing in runtime/test/ drives wo_engine_start/stop and
  no corpus fixture can trigger a stop

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 23:43:45 +02:00
3507aafea3 docs(databasev2): propagate iteration 1's findings to every consumer
Audit found 4 of 10 iterations citing it1 and 4 carrying stale claims the
measurement contradicts.

- 05: framing was contradicted, not merely incomplete. Its goal expected a
  gradient to detect ("back-pressure before the cliff"); there is no cliff
  — SIGKILL with swap off, exit 0 with swap on, and read latency STEPS
  (1us -> 487us) rather than departing. Heading and goal rewritten; the
  measurement makes the goal stronger, not weaker
- 05: budget must be bytes — 3.3x footprint spread — with headroom for
  index doublings, else it fires during a rehash
- 05: new goal — eviction policy QUALITY is decisive, since getting the
  resident set wrong costs 273x, not a few percent
- 06: its revival question now has a reference point. 273x is the KERNEL
  SWAP path; `resident: keys` preads via page cache and must beat it. This
  file revives only if 5c/5d lands near 273x rather than well below
- 04: write path is not where pressure bites (append ~1%, read 273x), so
  the io_uring question that matters is iteration 2's deferred read-path
  one, not group-commit
- 00-story: problem statement asserted the store "refuses the insert
  rather than dying". Corrected in place — a banner above it was not
  enough, a skimmer never reaches it
- residency spec: "swap thrash and the OOM killer" named exits that were
  not measured; replaced with silence-or-a-corpse

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:47:37 +02:00
5b1a8c96a1 feat(db-bench): replay baseline — boot cost tracks history, not data
Closes the last gap in databasev2 1; gives databasev2 3 its "before".

- `boot` mode: does NOTHING. WO_DATA replay runs before main, so a mode
  with no work measures replay plus a fixed startup
- `replayseed N M`: N inserts + M updates — same live rows, longer log
- `replay` leg: empty-store startup floor measured and SUBTRACTED, then
  two shapes timed, median of 3 boots each
- premise check: updates must actually append WAL records, else the two
  shapes are one measurement and the penalty means nothing
- WAL bytes = non-zero prefix, never file size (fallocate'd to 1 MiB)
- per-record cost stored in NANOseconds: as us it rounded 5.5 and 5.3 to
  6 and 5, too coarse for the number a checkpoint exists to improve
- 148 checks, 0 failures; gate bites on a doctored ns_per_record

Measured — same 20 000 live rows, different history:

- 20 000 records:  980 035 B WAL, 110 ms replay, 5.5 us/record
- 40 000 records: 1 960 035 B WAL, 211 ms replay, 5.3 us/record
- 1.9x boot cost for an IDENTICAL dataset; per-record cost flat, so
  replay is linear in records not rows
- extrapolated: 10M records ~55 s of boot, 100M ~9 min

- databasev2 3 correction: it planned to use "22's aged-store replay
  numbers", which never existed — 22 proved restart correctness, never
  timed it
- databasev2 3 hazard recorded: compaction rewrites the log and moves
  every record, so it invalidates every `resident: keys` offset — an
  arbitrary byte in a rewritten file, not stale-but-readable
- databasev2 1 -> status: done

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:25:24 +02:00
a873cf7331 feat(db-bench): random-read-over-cap leg — the 273x collapse
- `randread N R` in the sample: fill N rows, read R across the WHOLE range
- Weyl order `i*2654435761 mod n` — no RNG in the language, none needed;
  both legs read the SAME key order so residency is the only variable
- `randread` driver leg: control (256 MiB, does not bind) vs over-cap
  (6 MiB + swap), sizes kept modest — quick resolves it in ~5s
- gates the RATIO, not the absolutes: over-cap reads/sec belongs to the
  box's swap device, the factor between two runs belongs to the engine
- reads must all resolve (hits == R) or the leg fails; a collapse measured
  over unresolved reads is noise
- 133 checks, 0 failures; gate bites on a doctored collapse_x

Measured — this closes the gap the swap leg left:

- resident 1 851 166 reads/sec, p50 0us p99 1us
- over-cap    6 771 reads/sec, p50 128us p99 487us
- 273x throughput, ~480x p99, all 20 000 reads resolving in both
- so the two access patterns sit ~270x apart under identical pressure:
  append-mostly insert ~1%, random read 273x
- departure is a STEP not a curve (1us -> 487us, nothing between), which
  is why p99_departure_decile finds no knee — there is none

- caveat recorded, NOT inherited: this is demand-paged anonymous memory
  through swap (4 KiB/fault, no readahead). `resident: keys` preads via
  the page cache — should be better, but databasev2 2 task 7 must measure
  its own read path. New criterion added there

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:56:19 +02:00
0c9b2c45d8 feat(db-bench): measure the RAM ceiling — databasev2 1
- `Wide` text-heavy reference shape beside Int-only `Item`
- `growth N int|text`: per-decile RSS read from own /proc/self/status
- `growth-verify`: survivor of a crash must be a contiguous intact prefix
- four footprint legs under a rootless cgroup v2 cap, swap on/off
- `ceiling` leg: die at the cap, then replay must come back intact
- footprint read as median-of-marginals; doublings a separate metric
- 121 checks, 0 failures; footprint gated ±10%, kill-timing ±100%

Measured, and it inverted two of the iteration's own predictions:

- footprint 96.5-100 B/row Int vs 320.6-324 B/row text = 3.3x, NOT the
  "order of magnitude" three docs asserted
- table storage has NO checked ceiling: SIGKILL signal 9, not a catchable
  WO_T_OOM. overcommit lets malloc succeed; kernel kills on page touch
- swap is NOT latency collapse: 900k rows 148s capped-with-swap vs 150s
  uncapped. Append-mostly never re-touches cold pages
- ack-after-fsync survives an OOM kill: ~40k rows, no holes, no corruption
- iteration 2's budget dependency is REMOVED not satisfied — there is no
  "swap onset" to derive a fraction from

- fix: subprocess returncode -9 was labelled a "checked refusal"; 137 is
  the shell spelling of the same signal

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:18:47 +02:00
1fe808b7a4 docs(stories): add readiness, retire status: refine, sweep all 47 iterations
- `readiness: ready | refine` is a SECOND axis, orthogonal to status.
  `ready` = the brainstorm is complete and the decisions are LOCKED (a spec
  approved, or the forks explicitly confirmed). `refine` = open forks remain
  and it cannot be planned yet
- `status: refine` RETIRED because it carried both meanings at once, so a held
  iteration with an approved spec (language 18, 26) was indistinguishable from
  one nobody had thought about. status is now purely where the WORK is:
  done | in-progress | pending | hold — `pending` was already the board's own
  rendering word, so nothing new was invented
- all 47 iterations classified from EVIDENCE in their own text, not by guess:
  "the four forks are SETTLED" / "spec + plan approved" / "Approved spec:" for
  ready; "Forks the spec must settle" / "no spec exists yet" for refine. Every
  shipped iteration is ready by definition. 19 done, 5 in-progress, 15
  pending, 8 hold; 27 ready, 20 refine
- two iterations moved refine -> in-progress rather than -> pending: language
  31 and 34 are absorbed into 24 and work on them is literally happening, which
  the board already showed as 🔄 while their frontmatter said otherwise. That
  disagreement is now gone
- board legend, board-views' frontmatter contract, and two new Dataview
  queries updated — the useful one being `readiness: ready AND status:
  pending`, the startable set

WHAT THE NEW AXIS IMMEDIATELY SURFACED: of 15 pending iterations, exactly ONE
is startable — databasev2 4, io_uring group-commit, whose forks were confirmed
settled 2026-08-20. Everything else pending needs a brainstorm first. That was
invisible while one key carried both meanings, and it is now on the board.

Also caught by the sweep, unrelated to readiness but found by cross-checking
frontmatter against the board: SIX duplicate rows. Every iteration moved into
databasev2 was still listed in the LANGUAGE pending table under its retired id
(23, 32, 33, 20, 21, 27) as well as its new one. Stale copies removed. And two
databasev2 rows made claims the sweep contradicts — iteration 1 was billed
"startable today" while its forks are open, and 6 still called itself the
ceiling-raiser after 2 took that role.

Docs only. linkcheck 0 broken / 0 anchors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 16:54:45 +02:00
51dd42f9db docs(databasev2): rewrite iteration 2 to match what was designed and built
Flagged by the developer: the iteration still described the pre-brainstorm
three-mode design behind a "superseded in part" banner while four tasks had
landed against it.

- iteration 2 rewritten around the shape as built: two keys
  (`durable: true|false`, `resident: all|keys`), not a `mode:` enum with
  `cold`. status refine -> in-progress
- added a task-by-task progress table with commit hashes, and split the
  acceptance criteria into MET (each with how it was verified, not just that
  it passed — e.g. the goldens-unchanged claim is `git diff` over golden/
  being empty after a WOC_BLESS run, since blessing rewrites all of them) and
  OUTSTANDING with the task that owns each
- kept the history rather than deleting it: the three-mode replacement, the
  "one real rewrite" that was fiction, and the opposite half that turned out
  genuinely deep. An iteration file is where that record belongs
- board row rewritten to agree; the track index's "the lever" section, its
  principle-7 paragraph and its sequence rationale all still taught the dead
  three-mode design

ITERATION 6 IS NOW LARGELY SUPERSEDED, and bannered as such rather than
quietly gutted. `resident: keys` is the ceiling-raiser and it lives in
iteration 2 (tasks 5c/5d). More than relocated: 6's premise — a user-space
resident working set with faulting and 5's eviction policy — was specifically
REJECTED by the spec in favour of the kernel page cache, since a pread against
a cached page is a memcpy. What may still be left is recorded honestly: revisit
only with a measurement showing the page cache insufficient. Its fork list
survives, especially "does the language surface the fault cost at the use
site", which is still open and still the largest question about what writeonce
is. The sequence rationale is amended too — it had 6 as the ceiling-raiser and
5 as a prerequisite on the critical path; neither holds.

Docs only. linkcheck 0 broken / 0 anchors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 13:10:30 +02:00
566559cf70 docs: name the residency value keys, not index (review change)
- `resident: all | keys` replaces `resident: all | index`. Two reasons beyond
  taste: it kills the collision with the `index:` argument
  (`@table(index: [customer], resident: index)` read badly), and it puts both
  values on ONE axis — each now answers "what row data stays resident",
  where `all`/`index` mixed a quantity with a structure name
- accurate as well as clearer: what stays resident is the id->offset map, the
  secondary indexes and the unique shadows — all key structures; row payloads
  are exactly what leaves. `resident: none` was rejected as overclaiming,
  since the indexes very much are resident
- checked for collisions: neither `all` nor `keys` is a keyword or a builtin
  (`key_at`/`val_at` exist, bare `keys` does not)
- the spec's wart note became a recorded decision; the rejected spelling is
  kept quoted so the rationale still reads
- fixes a bug I introduced in the 2026-08-26 track move: all six moved
  iterations carried a banner reading "Part of [Story — the database beyond
  RAM]" whose link pointed at the LANGUAGE arc — correct target, lying text,
  the exact failure mode the link audit warned about. Banners now point at
  the databasev2 story, and the original "Part of" line says plainly which
  track the iteration was authored in before the move
- linkcheck 0 broken / 0 anchors

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:47:24 +02:00
da30aa6527 docs: amend principle 7 — the log is authoritative, residency is declared
- driving case: a 120 GB order table on a 32 GB host. Not a tuning problem;
  no eviction policy fixes it. Developer accepted reconsidering the principle
- principle 7 rewritten: durability half UNCHANGED and unconditional
  (WAL-logged, fsync before ack, CRC-dropped torn tail); residency half
  demoted from law to per-table declaration. Old wording quoted in place so
  the amendment is legible, with the reason: a doctrine a real workload
  cannot satisfy gets ignored, and the failure it produced was an OOM kill
- spec: docs/superpowers/specs/2026-08-26-table-residency-design.md
  One log-structured engine — the WAL already holds every row, so keep an
  in-RAM id->offset map and pread rows back. No second engine, no user-space
  row cache (the kernel page cache is the hot copy, which is already this
  repo's stated position and why it avoids O_DIRECT)
- arithmetic that makes it work: 240M rows x 16 B of index = ~3.8 GB
  resident in 32 GB. Indexes stay resident, rows do not. Buys ~2 orders of
  magnitude, not infinity — stated plainly in the spec
- grammar: two optional keys, `durable: true|false` and `resident: all|index`,
  both defaulting to today's behaviour, so all 28 existing @table
  declarations compile untouched and no golden is reblessed
- rejected, with reasons recorded: mmap (rows are pointer-bearing —
  table.c returns (uintptr_t)t as the slot word), buffer pool (the Rust-era
  phase-12 design that died with that track), paged B-tree (stays rejected),
  a three-valued enum, automatic spill, disk-backed-by-default
- self-review caught the budget defaulting to "none" while promising the ERP
  developer a diagnostic instead of the OOM killer — contradiction fixed:
  the budget defaults to a fraction of host memory, and its value comes from
  databasev2 1's swap-onset measurement
- live docs that contradicted the amendment updated (subagent doctrine,
  its guide, discarded.md's two rows, iteration 04's read claim, 07, 38);
  dated specs/plans left as records. linkcheck 0 broken / 0 anchors

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:42:27 +02:00
746dc2b42b docs(databasev2): third track — the database beyond RAM, with per-table storage modes
- docs/stories/databasev2/, numbered from 1. Six PENDING database iterations
  moved from the language track and renumbered, keeping the old id in
  `was_language_iteration:` so a search for "iteration 32" still finds it:
  32 -> 3 WAL checkpoint, 23 -> 4 io_uring commit, 33 -> 7 single-file store,
  27 -> 8 query grammar, 20 -> 9 cross-program, 21 -> 10 keypair auth.
  Done work (9, 9b, 22) stays as v1 history; language 18 left whole
- the problem, read off the engine not guessed: rows are malloc'd slabs with
  addresses stable forever, NO eviction/spill/paging anywhere in database/src,
  the WAL never checkpoints so boot replays all history, and durability is one
  process-global WO_DATA so no table can say it matters more than another.
  An allocation failure IS a clean catchable WO_T_OOM — but swap thrash
  arrives first and carries no error signal at all, which is the real hazard
- four new iterations:
  1 measure the ceiling FIRST (curve not cliff; the three exits; kill -9 at
    exhaustion) — every later default should follow from a number
  2 `@table(mode: ram | durable | cold)` — the grammar ask. Small surface
    (Ast.table_cfg gains a key, the parser already rejects unknown args), big
    semantics: `durable` defaults so nothing changes silently, and the
    compiler refuses a durable row holding a `ref` into a ram table
  5 bounded tables + refuse/evict/back-pressure, shedding BEFORE the OS acts
  6 cold tiering — mostly forks, incl. whether the language surfaces the
    fault cost and whether @unique on cold is refused outright. A paged
    B-tree stays rejected: if tiering needs one, reject tiering
- 39 links repointed, link TEXT renumbered to track-local ids; arc gains one
  pointer row replacing the six moved; board + board-views cover three tracks
- linkcheck 0 broken / 0 anchors; no code blocks in any story

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:52:48 +02:00
01df75245f docs(porch): give the framework its own story track, iterations 1-8
- docs/stories/porch/ — a TRACK folder, not a status folder: status still
  lives only in frontmatter. Adds `track: porch` so a query over
  docs/stories/ can tell a porch 3 from a language 3
- 00-story.md carries the sequence, the dependency graph, and a table of
  what the track explicitly does NOT own (binding -> 29, cache -> 18,
  proxy -> 38, metrics -> 30, TLS/templates -> doctrine)
- eight iterations, each with phases, per-phase tasks, Given/When/Then
  criteria, out-of-scope and the forks a spec must settle:
  1 store-backed middleware (limiter + idempotency — needs nothing new,
    first on purpose so the store pattern is proven cheaply)
  2 randomness + cookies (phase A is language-track: a CSPRNG builtin;
    `Resp.headers` being a map cannot emit two Set-Cookie lines)
  3 sessions   4 CSRF   5 routing/response ergonomics (independent)
  6 streaming core (the seam 7 and 8 wait on; chunked-request refusal
    must survive)   7 SSE + compression   8 static + lifecycle hooks
- language iteration 39 -> status: hold, retitled superseded, with a row
  mapping each of its goals to the porch iteration that took it. Kept, not
  deleted: the Fiber study cites it and its randomness argument is what
  this track is built on
- board gains a porch section; board-views gains porch and both-track
  Dataview queries; porch README and the Fiber study §7 point at the track
- no code blocks in any story (plans carry concept and actions in words);
  linkcheck 0 broken / 0 anchors

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:17:33 +02:00
ffd791d05b refactor(porch): name the web framework porch, fix the wo.toml identifier claim
- docs/examples/writeonce-serve -> docs/examples/porch (git mv, history kept);
  `[deps]` key and import are now `porch` / `porch/http` / `porch/router`
- name history preserved on the library README, not rewritten into dated
  records: writeonce-framework -> writeonce-serve (08-25) -> porch (08-26).
  Stories, specs, plans and the audit reports keep the older name by the
  repo's own convention; only live docs and every path link were rewritten
- left alone deliberately: `internal/serve.wo`, `pub fn serve`, `serve_conn`,
  `app.serve(...)` — those are functions, not the module name
- web-app/wo.toml comment corrected: it claimed hyphens are not identifier
  characters and named a key this file never used. lexer.ml's `is_ident_cont`
  DOES accept `-` (an internal dash is part of the identifier, which is why
  binary minus needs spaces), so a hyphenated key would be legal too
- site now teaches the name: package card, the two-deps chapter and the
  handlers-are-classes chapter say `porch`; site-accept asserted the old
  /packages/serve route and caught the rename, as a gate should
- gates: web-app 46/0, site 21/0, deps-accept 8/0, oop-e2e 116/0,
  linkcheck 0 broken / 0 anchors; porch typechecks entry-less as kind=library

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:36:34 +02:00
c0b0dbb846 docs: audit all markdown against the code, fix findings, flatten status folders
- README: shipped concurrency/HTTP/WebSockets sat in the roadmap as "not yet
  available"; "no package manager" contradicted [deps]; the deps example
  would not have compiled (the key IS the module name)
- runtime/README: leads with wovm, wo-rt.c demoted to a historical section;
  dropped 2 nonexistent recipes, crates/rt, @gc refcounting, 13 suites -> 18
- employee + log-watcher READMEs claimed "does not compile"; both are gates
- error catalog: +10 emitted codes incl WO-E250, the only diagnostic the
  shipped query surface raises; recorded why the sweep rotted
- language-surface: group-by parses, then the typechecker refuses it
- 00-code-review + 00-link-audit re-run; history kept, not rewritten
- 48 dead Rust-era exploration links de-linked rather than re-pointed (their
  prose names the retired plan by number); successor map -> discarded.md
- 08-project-structure: compiler/plan/ never existed; corpus has 9 dirs, 5 empty
- releasing.md: dropped a --draft step the workflow never had
- new docs/00-doc-audit.md: findings + disposition, incl one row where the
  audit was wrong and the doc it accused was right
- status folders removed: 34 stories flat, status only in frontmatter; 252
  links recomputed from resolved paths; board/board-views/structure retaught
- story 24 -> in-progress, since frontmatter is now the only truth
- new iteration 38: fs mutation verbs + net.connect, the two capability
  families no iteration owned
- new iteration 39: gofiber/fiber v3.5.0 parity study. The ledger called
  CSRF/sessions unblocked by iteration 34's HMAC, but the runtime has no
  source of randomness at all
- linkcheck skips .dev/.superpowers: 0 broken paths, 0 bad anchors

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:20:22 +02:00
ef37b8ffa2 feat(serve+view): file serving, downloads, supported systems; rename
- rename the two libraries: writeonce-framework -> writeonce-serve
  (`use serve`), wo-html -> writeonce-view (`use view`). Names say the
  ROLE now; every sample, script, gate and live doc follows
- stories/specs/plans keep the old names: they are dated records, and
  both library READMEs carry a "renamed 2026-08-25" note
- serve/http/files.wo: StaticFiles { dir, max_bytes } — traversal
  refused not normalised, extension content types, attachment
  disposition for archives. Lifted out of the shop, which had said in
  a comment that it belonged in the framework
- shop drops its private copy and mounts the framework's
- site: /dl/*path over $WO_DIST (default ./dist), 16 MiB ceiling
- /install gains supported systems — Linux x86-64, glibc >= 2.38,
  not musl — read off `file` and the binaries' GLIBC_ symbol
  versions, not off a wish list; plus GitHub release as primary,
  /dl as mirror, and the sha256 verify step
- site-accept: 17 -> 21 checks (supported systems, gzip download with
  a binary-safe probe, checksum, /dl traversal 404)

Verified on 192.168.0.165: the real 960,820-byte tarball downloads
as application/gzip and its sha256 matches the published digest.

Gates: oop-accept MET, site 21/0, web-app 46/0, fibers 10/0,
db-actor 8/0; shop rebuilt and its /assets served by the framework.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 04:41:00 +02:00
23550e7021 feat(lang+wo-html): raw text literals, component layer, MVC samples
- lexer: backtick raw text literal — content verbatim, no escape
  processing, common source margin removed at lex time; `${ }` raw and
  `{{ }}` auto-escaping holes
- `{{ e }}` desugars to `esc(${e})` in parser.ml — a Call on the `esc`
  in scope, so types/owner/emit/.wob/VM are untouched
- WO-E004 unterminated raw literal; WO-E005 newline inside "..." —
  closes a hole where a missing quote silently ate the rest of the file
- wo-html: `Component` interface, `render_all`, `Layout`, README
- framework: `ok_html` joins ok_text/ok_json in http/types.wo
- site + shop restructured to one-feature-one-module MVC (view +
  controller per directory, model at the root, bootstrap-only main)
- removed the filler `pad: Int` convention — verified unnecessary for
  plain classes, interface dispatch, containers and actors
- corrected recorded claims: gap #1 blocks neither the build nor the
  layout; a class crosses module lines, only a free fn is scoped
- docs/guides/language-surface.md — the full grammar inventory
- story 37 landed and moved to done/

Gates: oop-accept MET, oop-e2e 116/0, woc-test 556/0, site 11/0,
web-app 46/0, fibers 10/0, db-actor 8/0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 03:58:41 +02:00
bbf5fb66d3 feat(shop): 37 target = in-class raw template literals (developer form)
- separate view.html files dropped; every render() now carries its
  '-- 37 target:' literal above the hand-lowered body — the pair is
  the DX referendum in one file
- story 37 re-pointed: raw multi-line literal + {{ }} auto-escaped
  typed holes + {!! !!} raw slots; structural control stays if/for;
  w:if/w:for and .html files demoted to later; forks revised
- rebuild verified on untouched toolchain

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 00:15:38 +02:00
89df517613 feat(shop): the program template — MVC on disk; story 37 redirected
- docs/examples/shop: types.wo model, per-feature view modules
  (render() classes), root controller files, layout shell/header/
  footer, static assets controller + real style.css, README with
  Angular file map + run + DX referendum notes
- buy flow proven by hand: stock check/decrement, 409s, traversal
  404, css typed, WAL-durable; builds on the UNPATCHED toolchain
- two language gaps recorded, not fixed (per directive): pub+@table
  cannot combine (controllers forced into root module); @view
  projection classes absent
- story 37 REDIRECTED per Vue-SFC review: view.html compiled by woc
  ({{}} auto-escaped, w:if/w:for, typed against view class, no
  runtime engine); render()-as-concatenation failed the referendum;
  forks revised; shop named the acceptance consumer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 23:57:44 +02:00
67cd039533 docs: fix stale story-35 links (file moved to done/ at landing)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 19:36:18 +02:00
8f96584682 docs: story 37 — wo-html components (MVC view layer, Angular format studied)
- Component interface (render->Text), layouts/slots, site migrates
  as acceptance; framework stays micro (view layer = library)
- Angular map recorded: inputs/templates/ngFor/projection translate,
  DI/bindings/client-side rejected by doctrine (no closures, no JS)
- four forks for the spec; unscheduled, off-chain; table + board rows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 19:35:41 +02:00
a32550d968 feat: iteration 35 — net seams + the serving slice (fiber-per-connection)
- runtime ids 91-95: net.read_dl/accept_dl/write_dl (per-call deadline,
  nil/false = the EXPECTED timeout; ms<=0 = old behavior bit for bit),
  net.listen_unix (unlink-before-bind, O_NONBLOCK on the listener —
  probe-found: accept4's flag covers accepted sockets only), net.peer
- plane: one-op-per-park stays law — deadlines ride one per-shard
  TIMEOUT tick (sentinel user_data) + post-CQE expiry sweep +
  POLL_REMOVE tombstone; epoll's deadline scan grew the fd-park case;
  fibers POOL instead of freeing mid-run (stale-CQE UAF); plain parks
  zero park_deadline (no stale sleep deadlines)
- probe: all five seams verified on BOTH WO_IO backends (timeout
  timing exact, peer round-trip, unix rebind)
- framework: parse_request grows first_ms/read_ms; serve_conn — the
  keep-alive loop with deadlines where parked idle conns are LEGAL
  (close-when-idle RETIRED); App.handle_conn exposes it; plain serve()
  unchanged for simple apps
- web-app: app-owned accept_dl loop + ConnWorker actor per connection
  (each builds its own App; cross-shard placement rides the DB actor);
  WA_IDLE_MS knob; gate grows to 41 checks — two slow requests served
  in PARALLEL, stalled client evicted at the idle deadline, slow-loris
  torn at the read deadline (400)
- docs: story 35 -> done with banner; SQE/CQE design spec LANDED (was
  the review doc); ledger rows (timeouts/unix/keep-alive/peer), graph
  (NETSEAM cleared, KEEPAL done), builtin-surface rows, runtime
  CODE-LOGIC section, board entry
- battery 13/13 fresh-built

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 08:04:32 +02:00
5fc32b4926 feat: iteration 34 — digest builtins sha1/sha256/hmac_sha256 (ids 85-87)
- runtime/src/crypto.c: hand-rolled cores, whole-value over Bytes,
  allocation-free tails; VM half returns fresh Bytes, WO_T_BOUNDS on
  wrong class id (Bytes builtins' message shape)
- test_crypto: RFC 3174 + FIPS 180-4 + RFC 4231 (incl. long-key case 6)
  + 63/64/65 block-boundary sweep + the RFC 6455 handshake input, 18/0
- compiler surface flat per house convention (sha1, not crypto.sha1 —
  matches base64_encode): types.ml signatures + result types, emit.ml
  ids/dispatch/arity/known-list/drop-table (fresh-Bytes entries so
  results get their drops)
- corpus run/crypto-digests pins the .wo path through base64_encode
- no .wob bump (ticks-84 precedent); WO_B_MAX 87; surface doc rows
- slice marker + board row: iteration 24 (absorbing 31+34) executing
- battery 12/12 fresh-built

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 00:42:43 +02:00
64a4700190 docs: reconcile story 34 + gates with merged master
- story 34: premise fixed — iteration 36 landed bitwise/hex, digests
  and HMAC now expressible in pure .wo; C-builtin vs pure-.wo is the
  story's brainstorm call, not an impossibility
- dependency graph: crypto gate names story 34; net-seam gate names
  story 35; radix gate corrected — 22 benched the DB, router scan
  still unmeasured, perf-targets entry first
- framework README ledger: timeouts/unix/peer rows point at story 35;
  ETag row at story 34; path-matching row repointed off iteration 22

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 23:30:55 +02:00
ee018f06e2 Merge branch 'read-path-index' 2026-08-22 23:26:01 +02:00
dc3e5b7e5b Merge branch 'db-bench' (iteration 22 — durability/throughput baseline)
- brings time.ticks builtin (id 84), bench sample + campaign driver
  (scripts/db-bench.py), just db-bench/db-bench-quick recipes, first
  baseline recorded, story 22 to done/, postgres study cards
- conflicts resolved: board in-progress table (iteration 36 +
  framework rows kept, db-bench row now "22 landed"; dangling order
  anchor repointed); story 36 moved back to in-progress/ (dir-rename
  inference dragged it to done/ — 36 still awaits the manual pass)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 22:57:51 +02:00
4ec01c4c43 Merge branch 'concurrency-arc-stage3' (iteration 8 complete)
- brings arc stage 3: transparent DB actor (T7) + closeout (T8),
  db-actor sample + gate, board/story reorg (stories/00-status.md)
- conflicts resolved: runtime CODE-LOGIC (kept iteration 36 bitwise
  section AND stage-3 DB-actor section), board in-progress table
  (kept iteration 36 + framework rows AND db-bench row, links fixed
  for the moved board path)
- full battery fresh-built 11/11: woc-build wovm-build woc-test
  wovm-test oop-e2e deps-accept web-app log-watcher employee fibers
  db-actor (first run hit a stale pre-merge wovm — gates require
  built binaries and never rebuild; builds now run first)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 22:50:18 +02:00
ed6bfeea3d feat: iteration 36 task 5 — operators sample, docs closeout
- docs/examples/operators: manual-test workload (ok/FAIL lines per
  expression; trap mode proves WO_T_SHIFT); NO test fixtures by
  developer directive — acceptance is the manual pass
- 00-wob-format.md: v6 section (opcodes 42-46, T_SHIFT, header v6)
- CODE-LOGIC.md both sides: precedence-into-existing-rungs, Lua not,
  rewind-and-reparse compound assigns, trap-not-mask, 63-bit hex limit
- story 36 refine -> in-progress with landing blockquote; board updated
- gates: woc-test 543/0, wovm-test ASan, oop-accept ALL MET,
  deps-accept 8/0, web-app 26/0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 21:42:08 +02:00
6f7fc577c3 docs: story 36 + plan — operator parity (not, bitwise, hex literals, compound assigns)
- story: gap survey vs Go reference; forks settled (arithmetic >>, trap
  on out-of-range shift count, Lua-placement not)
- plan: 5 tasks, lexer -> parser -> checker -> emit/VM (.wob v6) -> closeout
- latent defect recorded: += / -= lex but never parse (dead tokens)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 21:18:33 +02:00
526a837102 docs: story 35 — net runtime seams (deadlines, unix sockets, peer addr)
- owns the framework ledger's three seam rows; deadlines compose with
  the arc's park plane (POLL_ADD+TIMEOUT fork recorded); framework
  knobs explicitly out of scope; stalled-client soak in acceptance
- board + table rows (held seqs bumped); pairs naturally with 24

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 17:37:12 +02:00
38457973c3 docs: story 34 — crypto builtins (digests + HMAC)
- SHA-1 (WS-handshake hard req, RFC 6455 worked example as acceptance),
  SHA-256, HMAC-SHA256 over Bytes; hand-rolled C per doctrine, FIPS/RFC
  vector fixtures; four forks recorded (namespace, shape, source, file)
- gates chain item 24; digest floor for held 21 + ETag row; 24's
  dependency note repointed; board + table rows (held seqs bumped)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 17:34:27 +02:00
b3baf6dd9a docs: story 33 — single-file store (WO_DATA=<path>.db)
- file path IS the wal; dir form byte-identical; one fork (nonexistent
  path semantics) leaning recorded; off-chain, driver-only
- board + story table rows (held tail seqs bumped)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 17:32:18 +02:00
a8829232dc docs: perf-targets register — measured optimization candidates
- target 1: write path (update-through-query re-probe, triple index
  walk, per-field encode, whole-row WAL update record) — re-measure
  after 23, then decide
- targets 2-4 recorded with owners (DB-RPC by design/24, mutex inbox
  /31, fsync bound /23)
- board pending + comparison README link the register

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 17:19:46 +02:00
39b035b513 docs: board — read-path index slice landed (x850 reads)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 16:45:16 +02:00
5d6a72bb82 docs: iteration 22 landed — closeout (T6)
- story 22 to done/ with landing banner (numbers, deviations,
  standing multi<TableClass> finding); marker deleted
- board standup from measured numbers; next slice = 31 (has its
  mutex-inbox number now); spec/plan banners LANDED; graph node done
- msgrate floors value/8 (quick's small N spawn-dominated, grazed /4)
- full battery + db-bench-quick 87/0 green; links verified

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:57:27 +02:00
3a30b4ac3b feat(db-bench): first baseline — the measurement contract (T5)
- bench/baseline.json: 74 metrics from the first full campaign;
  tolerances tuned by a two-run repeatability check (mix* 50%,
  read/query 35%, rest 15% — rationale in _config)
- gate bites: --check mode; doctored copy fails, both real runs 74/0
- headline: durable seed 4.5k/s vs ram 297k/s (23's case); reads
  O(table) at ~1.5k/s; mixread 1280 vs 21 ops/s single-vs-multi
  (the arc's price); msgrate 13.4M vs 2.45M (mutex-inbox number)
- arc delta recorded in story 8; findings in sample README

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:54:13 +02:00
555a836d90 docs: iteration 22 plan ready; slice active
- plan 2026-08-21-db-bench.md: 6 tasks (time.ticks builtin, sample,
  concurrent modes, driver+gate, first baseline + gate-bites proof,
  closeout); words + verification commands per convention
- spec banner APPROVED; story 22 to in-progress/ (frontmatter synced);
  marker doc created; board standup/rows/pending updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:07:51 +02:00
49cc734ae8 docs: arc closeout (T8) — stage 3 landed, chain advances to 22
- stories 08+11 to done/ with banners (11: fs-park re-scoped out of
  v1, disclosed); guarantee-contract table re-homed in story 08;
  marker doc deleted per convention
- board standup: implemented/findings/learned/unblocked/next/.dev-ref
  for the landing; In-progress = nothing active, next = 22 spec
- arc plan status ARC COMPLETE, T7/T8 boxes checked with deviations;
  graph nodes done; framework ledger rows note arc-unblocked;
  18's pub/sub rejection expired note
- CODE-LOGIC: runtime DB-actor + ring-params section, database slot
  surface; oop-vm/03 contract gains the reply-park protocol
- 22 precursor recorded: remote insert ~8us/op RAM-only
- full battery + db-actor gate green after doc edits; links verified

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 13:16:25 +02:00
ca2ed96e49 docs: story frontmatter + Dataview board views
- 28 story files gain YAML frontmatter: iteration id, status
  (mirrors folder), chain position (7 files, positions 1-6)
- board-views.md: Dataview queries (not-done, by-status lanes,
  chain order, active); Kanban caveat — view only, frontmatter
  is source of truth, folder move + status key change together
- board points at the views

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 12:17:50 +02:00
e7ca2fdd7c docs: stage 3 refined with guarantee contract; story 32 born
- five-property map in marker doc: atomicity/durability/recovery
  proven or held (18); concurrency control = stage 3's property;
  space reclamation RAM done (slot reuse), disk = new story 32
- story 08: three stage-3 criteria (one commit per write RPC +
  ack-after-owner-fsync, workers WAL-free + replay-before-serve,
  no torn reads under TSan corpus); arc plan stage 3 carries them
- refine/32-wal-checkpoint.md: snapshot + truncate, bounded replay,
  crash-during-checkpoint safe; four forks; after 23
- chain now stage 3 -> 22 -> 31 -> 24 -> 23 -> 32 in all 10 docs;
  boards + seq bumps synced

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 11:26:45 +02:00
eacc7fe4f2 docs: oop-vm/03 — stackless coroutine contract (no async)
- normative reference for the concurrency chain: fiber = interpreter
  state, suspension only at VM boundaries, park/resume protocols
  (re-execute vs continue-past, park_wr_at), back-edge budget,
  no-coloring rule, rejected-alternatives table
- README slot 03 repointed (old shard-actor row rode discarded plan 4)
- story 11 links the contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 11:07:52 +02:00
9a3988e979 docs: NEXT PLAN = concurrency + fiber chain, standup-shaped
- stage 3 active; six standup answers (implemented/findings/learned/
  unblocked/next/.dev-reference)
- framework v1 block demoted to landed section, content kept

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:51:33 +02:00
a3a642b8bb docs: status board moved to docs/stories/00-status.md
- developer move; all inbound links repointed (root docs, plan/,
  plan/compiler/, exploration, superpowers plans+specs, in-progress
  marker), board's own links re-based one level deeper
- prose mentions inside landed plans left as historical records

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:07:20 +02:00
299b448181 docs: active stories 08+11 into stories in-progress/ (slip fix)
- 08 landed in discarded/ by mis-drop, swept into prior commit with
  two dead links; corrected to stories/.../in-progress/ per intent
- 11 joins it (one arc, active slice)
- board doctrine: active stories bucket named; all links re-verified

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 06:21:33 +02:00
f691818c4b docs: in-progress/ marker — arc stage 3 is the active slice
- one marker doc, deleted on landing; board doctrine names the
  second folder exception
- board In-progress row was stale (nothing active + dead anchor);
  now points at marker + arc plan tasks 7-8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 06:19:03 +02:00
d3f77d6850 docs: discard 2026-08-01 shard-actor plan (epoll-based)
- io_uring is a must; epoll approach discarded (developer decision)
- plan superseded by shard-fiber-arc plan of record; banner + row in
  plan/discarded.md; file kept as idea reference
- three live pointers repointed: status language-track row 8,
  principles enforced-by, story 08 note

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 05:59:21 +02:00
1cfcd6292a docs: stories 08+11 promoted to root — ready to implement
- code-verified ready: arc stages 1+2 merged to master; stage 3
  concrete in plan (tasks 7-8); rt.db set on primary only
  (main.c), worker_late_init memsets rt — WO_T_DB hole real
- 22/23/24/31 stay in refine/: open forks, no bench harness,
  no crypto builtins, chain-blocked
- links fixed both directions; board doctrine names hold/ bucket

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 05:10:39 +02:00
039cb9ba4f docs: concurrency chain re-refined + resequenced
- order now stage 3 -> 22 -> 31 -> 24 -> 23: correctness before
  measurement (multi-shard DB traps WO_T_DB today)
- stories 08/11 catch up to landed arc stages 1+2 (plan of record,
  deviations, settled open questions)
- 22 gains multi-shard + mutex-inbox targets; deltas owed retroactively
- 23 rides arc's per-shard ring (T4); old-id order string superseded
- 24 depends on 31; 19 landed so Bytes ready
- new story: refine/31-actor-lifecycle.md (request/response,
  bounded mailboxes, death/supervision, timers)
- 00-story table + 00-status pending resequenced; held rows link
  hold/; ids stay immutable, no renames

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 05:01:11 +02:00
b4d9d507a0 docs: sync superpowers specs/plans to hold decision
- iterations 18/25/26 marked hold in their spec + plan headers
- 25's story file removal committed; plan doc stays for resumption
- web-framework spec's relates-to flags 25 held

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 05:00:56 +02:00
26bfc42bfe hold few iterations 2026-08-21 04:11:10 +02:00
d24c705860 feat: iterations 19 + 17 — Float/Bytes scalars (.wob v5), library kind + internal/
- Float full stack: literals (fraction/exponent; `0..10` still a range), f64
  opcodes 34-41, @table column, WAL bit-exact replay, json fractions in and
  shortest-round-trip out. IEEE-quiet — FDIV never traps where DIV does.
- Bytes: a wo_str with its own class id, so alloc/free/copy are shared but no
  Text builtin accepts one; len/at/slice/eq/concat, base64 both ways, json
  boundary as base64; TEXT_COPY preserves the kind.
- No implicit Int/Float mixing (WO-E201 in the typechecker, not the emitter,
  which picks the opcode from one side and would misread the other).
- One IEEE deviation: float_cmp total order (NaN last, -0.0 == +0.0) for
  indexes and order-by, keys canonicalized to match. `?Float` nil is a
  reserved quiet NaN — the zero word is +0.0, WO_NIL_SCALAR's bits are -2.0.
- Renderer prefers fixed over exponential in 1e-6..1e21: pure shortest makes
  a price of 900.0 read `9e+02`. One renderer for interp/json/float_to_text.
- Fixed en route: lexer double-counted the leading digit; is_scalar_shaped
  took Float/Bytes as Int-shaped; Bytes ownership needed a shared heap-scalar
  predicate or temps never dropped; order-by bit-compared negatives backwards.
- Iteration 17: `kind = "library"` (absent = program; bad value = WO-E109),
  entry-less check mode retiring the `--emit` workaround, Go's `internal/` as
  WO-E108 at the consumer's `use`. Driver-only; VM/.wob/GC untouched.
- Framework reorg: internal/{parse,serve}.wo; http/form.wo split out to keep
  media_type/form_values public (parse.wo had grown public surface).
- Docs: link audit (97 -> 88 broken, conflict markers resolved, 2 duplicate
  stories removed), 00-code-review verified 26/27, iterations re-sequenced.
- Also carries the pre-staged pub(read)/using/#if work from the index.
- Gates: corpus 103/0, test_wal 156/0, web-app 26/0, oop-accept ALL MET.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 19:24:15 +02:00
5521d21a84 docs: pending iterations renumbered by dependency + priority
- developer directive: pending iteration IDs now ARE the priority order;
  LANDED iterations keep historical numbers (code comments and commit
  history cite them — records, not a queue); 8/11 (the half-landed
  arc), 17 (parked, artifacts on a branch), 18 (next, artifacts named)
  also frozen
- mapping (recorded in 00-story): 19<-20 Float+Bytes, 20<-9c attach,
  21<-9d keypair, 22<-9e benchmarks, 23<-9f io_uring WAL, 24<-19 chat,
  25<-10 services, 26<-12 blue-green, 27<-9g query corpus,
  28<-14 skillhost, 29<-13 metaprogramming
- 11 story files renamed; every doc reference re-numbered (word-boundary
  sweep for the lettered 9x ids, phrase-level for numeric ones); the
  iterations table rewritten with Seq == priority and "(was N)" notes;
  story-scoped link check: zero broken
- merge-recovery folded in: the partial master merge had dropped the
  chat story, the fibers exploration note, the arc spec+plan, the
  framework-v2 plan, and the iteration-17 spec+plan — all restored from
  their branches and renumbered consistently

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 14:31:09 +02:00
4c8a3bd2ed docs: iteration 20 — Float + Bytes, the missing scalars (forks settled)
- brainstorm settled four forks: Float FULL STACK in one iteration
  (literal, IEEE f64 VM ops via u64 bitcast, @table column + WAL slot,
  json fidelity — json.c's own comment names the hole: fractions are
  malformed because "the language has no [float]"); IEEE-754 QUIET
  semantics (division never traps, NaN flows; Int keeps DIV0; no
  implicit mixing — float(i)/trunc(f) bridges); BYTES ships alongside
  (binary carrier: multipart files, WS frames for 19, crypto digests;
  Text goes back to meaning text); iteration 20 + spec before code
  (.wob/WAL version bump earns a written spec)
- the rest of the missing-type survey recorded with reasons: Result/
  Option expressible today (?T + payload unions), tuples covered by
  records + doctrine, Decimal stays cents-until-a-workload, Char/
  Unicode its own future story, Set/ADTs parked post-12, scalar
  newtypes need the rejected `abstract`
- one indexed-storage deviation from raw IEEE spec'd loudly: a Float
  index needs a total order — NaN sorts last
- board row, story-table row (seq 26), graph node (9+16 -> 20 -> 19;
  crypto gate wants Bytes)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 14:21:58 +02:00
2a4c304378 docs: stories foldered by state — done/ and refine/
- done/ (11): 1, 2, 3, 4, 6, 7, 7b, 9, 9b, 15, 16 — landed iterations
  (9/9b remainders live in the post-12 drain list, not in the files)
- refine/ (8): 9c, 9d, 9e, 9f, 9g, 11, 13, 14 — everything marked
  "no spec yet / brainstorm before planning"
- root keeps: 00-story (index), 05 (partial, plan 8 open), 8/10/12
  (specs or plans exist), 17 (parked, spec+plan approved), 18 (next)
- every cross-reference re-pathed and VERIFIED resolving: board, specs,
  plans, employee-list README, story table, intra-story links (moved
  files' relative links deepened one level; done/7b's 9e pointer now
  crosses to refine/)
- pre-existing dead link noted, not touched: refine/11-fibers.md points
  at docs/plan/exploration/fibers/00-fibers.md which does not exist
  (predates the move)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 04:33:24 +02:00
ba428c362c docs: story iterations table re-sequenced by dependency
- 00-story.md iterations table rows reordered into implementation order
  with a Seq column; # stays an immutable ID (files never renumber —
  every board/spec/plan references by number)
- order: 1-7b, 9, 9b, 15, 16 (landed, landing order) -> 18 NEXT (spec
  approved) -> 9c -> 9d -> 9e -> 8 -> 9f -> 11 -> 10 -> 12 -> 9g -> 14
  -> 13; 17 parked row at the end, slots anywhere after 16 on directive
- story note + board implementation-order list synced (18 inserted as
  item 2 after the parked-17 note; 9g now explicitly before 14; list
  renumbered)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 04:25:43 +02:00
234bd43466 docs: framework v1/v2 split + per-feature status ledger
- framework README core checklist expanded into the v1 STATUS LEDGER:
  seven categories (transport, routing, request/response, context &
  middleware, storage integration, security, crypto), every item
  marked done / partial-with-named-gap / candidate / parked-behind-8-11
  / needs-runtime-seam
- verified before labeling: BODY_MAX caps headers AND body (size limits
  done); net has no timeout or unix-socket or peer-address surface
  (runtime seams); language has NO bitwise operators, so SHA/HMAC/CRC32
  must be C runtime builtins or bit ops land first (fork to brainstorm);
  radix routing waits for 9e to measure the linear scan first
- crypto hard stop recorded: HS256 unlocks and nothing past it
- memory-rich features relabeled FRAMEWORK V2 = iteration 18 (story +
  spec banners + board rows); v1 gaps land as slices per the ledger

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 04:07:39 +02:00
deb2dc805c docs: iteration 18 — memory-rich features over the embedded DB (no code)
- brainstorm settled four forks same-day: scope = TTL cache + @table
  feature flags + durable @table job queue; jobs = drain-on-request
  (idle server drains nothing, disclosed); transaction { } ships in 18
  (WAL already stages batches, db.c merely commits per statement);
  pub/sub REJECTED until 8/11 (no WebSockets, starvation lesson)
- ground truths verified and recorded: time.now exists, no timers (lazy
  expiry only), accept blocks without timeout, state lives on wired
  instances, wal_append*/wal_commit is the txn seam
- headline: enqueue + business write in ONE commit — outbox dissolved
- draft acceptance incl. SIGKILL/restart transactional-jobs proof
- board row 18 + NEXT PLAN next-step + story roadmap row

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:52:59 +02:00
0dd5fda180 feat(framework): multipart/form-data parsing — Part, multipart_parts, part_named
- http/multipart.wo: RFC 7578 whole-body parsing within BODY_MAX —
  boundary from the raw content-type (quoted or bare, key
  case-insensitive), parts split on --boundary, each part = headers,
  blank line, content; filename + per-part content-type kept (lowercased)
- strict malformed-is-nil: no closing --boundary-- marker, a part
  without content-disposition, missing blank line, no boundary param,
  wrong media type — all nil, the caller's 400
- part_named(parts, name): first matching field's content, caller-owned
- web-app CreateProduct now accepts multipart/form/JSON (curl -F shape)
  into the shared insert path
- probe 13/13 + 3x reuse loop (fields, crlf-in-content, quoted boundary,
  file part, zero-part close, five malformed shapes) release + ASan
- gate grows 19 -> 21: multipart create 201, missing closing marker 400
- README: multipart row ✅ (all three body hooks done), limits updated;
  story 16 + board record the landing
- gates: web-app 21/0, oop-e2e 89/0, deps-accept 8/0, log-watcher 7/0,
  employee 8/0, woc-test green

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:33:14 +02:00
a5826495e9 feat(framework): form-encoded body parsing — media_type + form_values
- media_type(req): content-type lowercased, "; charset=..." stripped,
  "" when absent — the content-negotiation hook
- form_values(req): application/x-www-form-urlencoded body -> decoded
  pairs through the existing query decoder ('+' as space, %XX); nil on
  any other content-type so a JSON body is never misread as a form key
- web-app CreateProduct accepts form OR JSON; shared create_product
  insert path; field/number validation answers 400
- probe 7/7 (plus/pct decode, empty value, case + charset param, json
  and missing content-type nil, empty body, media_type strip) + ASan
- gate grows 17 -> 19: form create 201 with decoded name, non-numeric
  price 400; hit() gains a content-type argument
- README: checklist row form ✅ (multipart stays candidate), limits
  paragraph updated; story 16 + board record the landing
- gates: web-app 19/0, oop-e2e 89/0, deps-accept 8/0, log-watcher 7/0,
  employee 8/0, woc-test green

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:21:48 +02:00
fb86156d04 feat(framework): auth in core — Bearer/Basic mechanism + principal slot
- http/auth.wo: auth_header (scheme split, case-insensitive, RFC 9110),
  bearer_token, basic_credentials (first-colon split, RFC 7617),
  pure-.wo base64_decode (RFC 4648, strict padding), ct_eq constant-time
  compare (no early exit, both Basic fields always compared)
- req.principal: the blessed "who is this" slot, "" until authenticated;
  Middleware.before now takes mut req so auth can write it
- BearerAuth { token, principal } and BasicAuth { user, pass, realm }
  middlewares; BasicAuth answers the WWW-Authenticate challenge; policy
  (routes/users/secrets) stays app-side on the exposed fns
- web-app dogfoods BearerAuth; its hand-rolled Auth class deleted
- probe matrix 26/26 (RFC 4648 vectors, rfc7617 pair, pass-with-colon,
  bad padding/chars/length, deny paths, challenge header) release+ASan
- gate grows 16 -> 17: wrong bearer token answers 401 over the wire
- README: auth bullet + the core CHECKLIST (done / candidate / parked
  behind 8-11 by design); story 16 + board record the landing
- all gates green: web-app 17/0, oop-e2e 89/0, deps-accept 8/0,
  log-watcher 7/0, employee 8/0, woc-test green

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:15:16 +02:00
d84b72f0b4 feat(framework): v1 polish — helpers, 405+Allow, HEAD, Logging, set_header
- App.get/post/put/delete_(pattern, take h: Handler) — the take-interface
  shape probe-proven release + ASan before landing; retires plan-16
  deviation 1; delete_ because delete is the query keyword
- dispatch matches path-first: wrong method on a known path answers 405
  with Allow in registration order; unknown path stays 404
- HEAD routed as GET, body suppressed, Content-Length names the body a
  GET would carry (serialize gains head_only)
- Logging middleware (request line to stderr) ships in router/
- set_header(mut r, name, value) — the builder escape hatch
- web-app registers through the helpers (dogfood); README documents all
- gate grows 14 -> 16: 405+Allow, HEAD-vs-GET content-length equality
- all gates green: web-app 16/0, woc-test 540/0, oop-e2e 89/0,
  deps-accept 8/0, log-watcher 7/0, employee 8/0
- board/story: iteration 17 parked (spec+plan ready on library-internal),
  16 carries the v1-polish landing, order list updated

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:04:57 +02:00
2b5762500b docs: goal shift — log-watcher (met) to web framework as library
- NEXT PLAN rewritten: iteration 17 is the goal slice (merge branch, spec/
  plan, kind = "library", internal/ WO-E1xx, framework reorg, gate list)
- previous NEXT PLAN retitled "Landed 2026-08-15 — the executable milestone";
  all six measured items were already done, board rows were stale
- board row 7: in-progress -> landed 2026-08-15 (ASan-clean, SIGTERM, fd-flat,
  soak, just log-watcher 7/0); iteration 07 story banner updated to match
  its plan doc's done banner
- in-progress table now carries iteration 17 spec/plan
- implementation order re-sequenced for framework goal: 17, 9c/9d, 9e, 8,
  9f, 11 (+ h2c unparks), 10, 12, then 14/9g demoted (skillhost no longer
  the driving workload), 13 + parked drain last
- story notes record the shift and the new order

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 02:04:41 +02:00
d1aab85dbe docs: sequence pending iterations in implementation order
- new "Implementation order (sequenced 2026-08-20)" section in 00-status.md
  pending bucket: 7-finish, 17, 9g, 14, 9c/9d, 9e, 8, 9f, 11, 10, 12,
  13 + parked drain
- forcing rules recorded: 9f after 8+9e; 9c precedes 10; 9d folds into 9c;
  12 after 9+10; 11 rides 8's scheduler; h2c behind 8/9f/11; post-12 park
  directive unchanged
- 9e placed before 8 so restructure/perf work has a signed baseline
- 14 early as next driving workload (stdlib-shaped, shard-independent)
- story 00-story.md notes point at the sequenced list

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 01:50:03 +02:00
3e22e42da5 docs: iteration 17 forks settled — kind key, internal/ rule (no code)
- fork 1: library-ness manifest-declared, kind = "library", default program
- fork 2: privacy = Go internal/ directory rule, named diagnostic at use
- fork 3: dep-boundary-only scope; Go subtree rule recorded as later tightening
- fork 4: lib+bin dual — library default action is check, explicit build works
- Go-inherited rule pinned: internal type in public signature allowed, no check
- impact analysis added: framework loses --emit workaround, plumbing under
  internal/; compiler = two seams (driver kind + dep-use refusal WO-E1xx)
- VM zero impact by construction: no .wob change, libs compile whole-program
  into consumer image, internal modules still emitted (privacy strips nothing)
- GC zero mechanism impact; pinned: inference stays whole-program, app usage
  can promote dep classes, internal/ invisible to gcinfer — intended, not bug
- board + roadmap rows: needs-refinement -> forks settled, spec/plan next

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 01:45:06 +02:00
f430bff5c4 docs: iteration 17 — library projects + dependency privacy (needs refinement)
Brainstorm outcome, deliberately NOT implemented (developer decision: keep
as an iteration needing further refinement). Records:
- the two gaps iterations 15/16 exposed: library-ness is implicit (a
  no-main project fails woc <dir> build mode — the framework is verified
  via an --emit workaround) and the dep boundary leaks internals (pub has
  no dep-private tier: parse_request is as importable as Handler).
- the conventions corpus: Go (package decides program-ness; cmd/;
  internal/ = directory-shaped privacy, zero keywords) vs Rust ([lib]/
  [[bin]] manifest targets; pub(crate)-family keyword visibility). Doctrine
  fit points at Go's shape with an explicit manifest key (writeonce HAS a
  manifest; explicit beats inference in errors).
- four open forks for the spec: kind declaration form; internal/ vs
  pub(lib) vs export-allowlist; dep-boundary-only vs Go's subtree rule;
  lib+bin duality. Draft acceptance criteria; web-app 14/0 as the
  regression gate. Roadmap + board rows added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 01:23:10 +02:00
d6025e131d docs: iteration 16 closeout (Task 6)
Board row 16 -> landed (web-app 14/0), pending row removed; story header
records the landing + the two as-built discoveries (idle-keep-alive
starvation policy; the two compiler gaps the chain exposed and fixed);
README gains the framework+web-app sample entry; plan checkboxes ticked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:58:13 +02:00
4c75ba4fbd docs: implementation plan for iteration 16 (web framework + web-app)
6 tasks, words-only: (1) framework skeleton — Req/Resp records + builders,
standalone-typechecking project; (2) HTTP/1.1 parse/serialize + keep-alive
serve loop with the try-bounded dispatch seam (400-close on malformed, 500-
survive on handler traps, fd/stop clean); (3) router with :param captures +
Handler/Middleware interfaces + App.serve; (4) web-app storefront —
@table Product/Order, auth middleware, json routes, [deps] manifest carrying
the future GitHub URL; (5) scripts/web-app-accept.sh — temp git remote from
the framework dir, file:// substitution into a temp app copy, full curl
matrix + restart persistence + SIGTERM + opt-in soak, wired as just web-app;
(6) docs closeout. Both enabling risks retired pre-plan (interface-field
dispatch probe; owned-move container fix pinned by run/container-owned-move).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:38:21 +02:00
f6b5333d40 docs: iteration 15 closeout (Task 5)
- error catalog: WO-E106 (dependency fetch/shape failures, one code, message
  names dep + step) and WO-E107 (dep/local module-name collision) rows.
- README: a Dependencies subsection under the manifest docs — [deps] syntax,
  .wo-deps/wo.lock behavior, offline-when-locked, --update-deps, flat-only.
- board: iteration 15 row -> landed (deps-accept 8/0), pending row removed;
  story 15 header records the landing; 08-project-structure notes
  .wo-deps (gitignored) + wo.lock (committed); plan checkboxes all ticked.

(One self-inflicted casualty during this task, restored from git before
commit: a buggy doc-edit script truncated 08-project-structure.md; the file
was recovered intact and the intended one-liner applied by hand.)

Gates at closeout: deps-accept 8/0, woc-test 540/0, oop-e2e 87/0,
log-watcher 7/0, employee 8/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:23:18 +02:00
8fcebe60f9 docs: implementation plan for iteration 15 (deps)
5 tasks, words-only per house rule, each with its verify step: (1) manifest
grows the [deps] section + one-line inline-table value (only under [deps]);
(2) resolver — git-binary fetch into .wo-deps/, wo.lock pinning, warm-path
offline guarantee, drift diagnostic, --update-deps, guard rails (transitive
refusal, non-writeonce dep, name collision WO-E107, all fetch failures
WO-E106); (3) multi-root discovery + module_of prefixing dep roots by dep
name + entry restricted to the app's own files; (4) scripts/deps-accept.sh
gate over file:// remotes (8 checks, network-free) + just recipe; (5) docs
closeout (catalog E106/E107, README deps subsection, board/story/structure).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:11:05 +02:00
976ccda225 docs: web framework + deps design — spec + iterations 15/16
Brainstorm outcome (forks locked with the developer):
- TLS: proxy-terminated (nginx/caddy gives browsers TLS+ALPN+h2; the
  framework speaks HTTP/1.1 behind it) — zero TLS in the toolchain, no
  doctrine fight; homegrown TLS refused outright.
- Dependencies: a real mini package manager — wo.toml [deps] with exact-rev
  git deps, wo.lock, .wo-deps cache, `use <dep>` as a module root; fetch by
  shelling to the git binary (no network code in woc); flat-only v1.
- HTTP/2: v1 is HTTP/1.1 keep-alive; h2c is the parked successor behind
  iterations 8/9f/11 (multiplexing needs a scheduler to pay off); the
  bytes/buffer type rides with it, not v1.
- Handler model: no function values by doctrine, so Handler/Middleware are
  structural interfaces (ICALL dispatch, WO-E205-checked); middleware returns
  ?Resp and rides the shipped ?T narrowing.
- Incubation: framework at docs/examples/writeonce-framework/, consuming
  storefront at docs/examples/web-app/ importing it THROUGH [deps] — the
  sample exercises fetch -> lock -> build -> serve -> durable-restart.
- Iteration 10 relationship: service blocks later LOWER ONTO this library.

Files: specs/2026-08-18-web-framework-design.md (A deps normative, B
framework normative, C h2c parked); stories 15-deps-package-manager.md +
16-web-framework.md; roadmap + board rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:07:16 +02:00
28485a271c docs: iteration 7b migration — amend the normative docs (Phase 4)
The spec's §8 migration table, applied:

- 00-principles.md P3: "@gc is a per-class opt-in, reference-counted" ->
  GC-ness is inferred; incremental per-shard mark-sweep in budgeted slices;
  still no global pause by construction.
- OOP spec: decision-table GC row -> inferred (hybrid rule named); §3 rule 5
  -> traced classes alias freely, which classes is inferred; §4 memory model
  -> the RC + Bacon-Rajan paragraph replaced by tracing (snapshot roots,
  Yuasa barrier, born-black, budgeted slices); header rc comment -> union'd
  sweep link; mixing rule restated for tracing.
- 00-wob-format.md: header says version 4; opcodes 27-28 -> reserved (loader
  rejects); the owned-temporary rule's @gc exclusion restated for tracing.
- 08-builtin-surface.md: the push RC_INC special case and the set(m,k,v)
  retention gap DELETED — neither exists without RC; the corpus cycle is
  collected by tracing.
- story 07b: status -> LANDED 2026-08-18 (with the historical note kept);
  board: 7b row ✅ (supersedes iteration 2's RC memory model), pending row
  removed.
- gc-cycle README: Phase 3 flipped to landed (the ring runs, is reclaimed,
  ASan-clean; the ?Node RC_DEC-on-nil trap no longer exists); the barrier
  prose corrected to the as-built design (snapshot-at-beginning + deletion
  barrier + born-black, not per-slice root re-reads).
- plan 2026-08-18: all checkboxes ticked + a completion banner recording the
  four deviations from the plan as written.

(Error catalog was already amended with the keyword-removal commit: WO-E104
added, WO-W201 retired.)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 17:11:35 +02:00
b6151333c3 docs: iteration 14 — skillhost as a host-shaped driving workload
- frames a writeonce port of ~/projects/skillhost (C++ MCP host that
  links libllama in-process, discovers filesystem skills, runs their
  scripts confined) as the sample that drives host capabilities into
  the open — the way log-watcher drove the systems stdlib
- names each gap as a candidate iteration (surveyed 2026-08-16 vs the
  running compiler + skillhost source):
    - Blocker A: in-process native-lib FFI (no FFI today) — fork:
      FFI-as-language vs out-of-process model driver over proc/net+json
      (llama-server, needs nothing new); leaning out-of-process
    - Blocker B: stdio transport — no stdin/stdout builtins; port uses
      a TCP socket meanwhile; io.stdin_read/stdout_write a candidate
    - Blocker C: bounded/killable subprocess — proc.run has no timeout/
      signal/process-group kill; smallest + most broadly useful, do 1st
    - partials: recursive fs walk, exec-bit check, symlink-resolving
      confinement (realpath) — one small fs-metadata iteration
- records what is already expressible (catalog via @table/9g skill-
  catalog, discovery, frontmatter text-parse, config, single-thread
  serve, context-gate arithmetic — no VRAM query needed)
- out of scope: in-process libllama/CUDA, VRAM introspection, exact
  sampler chain / per-turn memory clear
- roadmap + board rows added

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 19:53:06 +02:00
86e1fd4a4e docs: iteration 9g story -- query grammar from real embedded-DB corpora
- method: an embedded-SQL app is a grammar corpus; catalogue what it
  actually uses and add only that, translating its statements 1:1 as
  the acceptance (the postgres/System.Linq reference pattern applied to
  a whole application)
- corpus #1 = ~/projects/skillhost (C++ MCP host, embedded SQLite skill
  catalog): surveyed, entire SQL footprint is one file — 1 table, a
  single-row parameterized INSERT, 4 SELECTs. 3 of 5 statements already
  run on the 9b surface (insert, where name==?, order by name; PK ≈
  @unique). Exactly 2 are the real gap:
    - whole-query `count` (group-free; the degenerate aggregate, NOT
      the parked group-by)
    - correlated `not exists` subquery (skillhost's roots-of-the-tree)
- notable finding: skillhost's NOT EXISTS is naturally a `backlink`
  emptiness in writeonce (children backlink + len==0), so the corpus
  may be fully expressible once len(query) is confirmed — the iteration
  may collapse to "confirm len(query) + add exists"; forks record this
- explicitly parks everything skillhost does NOT use (join/having/
  offset/distinct/CTE/window/union/upsert/returning/json/fts/triggers)
  and the full group-by; each enters only when a corpus demands it
- acceptance: docs/examples/skill-catalog mirroring skillhost's schema
  + its 5 catalog ops as writeonce translations
- roadmap + board rows added

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 19:28:25 +02:00
edd6091579 Merge branch 'query-surface' into database-engine 2026-08-16 19:01:16 +02:00
2677c1457e feat: FK restrict on delete + employee sample runs; group-by parked (9b)
- FK restrict: deleting a row a non-nullable `ref` still points at traps
  WO_T_FK (11), catchable. The compiler now records a `ref` field's
  target class in the class-table field_class metadata; the engine
  (wo_row_has_referrers) scans referencing scalar columns before a
  delete. Correctness-first full scan; the backlink-index optimization
  is recorded for later
- docs/examples/employee now COMPILES AND RUNS all six modes against a
  WAL-durable database: seed (+@unique trap across restart), report
  (per-dept aggregates + payroll), staff (unique probe + backlink +
  ref nav), raise (update-through-row), drop (FK restrict), and
  persistence via replay
- group-by SYNTAX parked to a future iteration (user decision): the
  report mode is hand-rolled from the shipped primitives meanwhile
  (same numbers). "table relations and FK" is complete
- scripts/employee-accept.sh (8 checks) + a `just employee` module;
  manifest parser tolerates iteration 9c's [share]/[[share.clients]]
  sections so `woc .` builds the sample on this branch
- fixtures trap/db-fk-restrict (code 11) + run/db-fk-restrict-catch;
  oop-e2e 79/0, woc-test 566/0, 15 runtime suites, log-watcher 7/0,
  employee-accept 8/0
- 9b story + status board updated

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 18:37:23 +02:00
ad8cfb456e docs: iteration 13 story -- compile-time metaprogramming (derive)
- captures the toCSV/reflection thread: principle 13 forbids runtime
  reflection, so a generic serializer can't be a user-written function;
  Rust answers with derive macros (compile-time codegen), and
  json.encode is already a single hand-built instance of exactly that
- iteration generalizes json.encode's mechanism into a reusable derive
  facility: @derive(Json/Csv/Eq/Hash/Show) -> the compiler generates
  per-type routines from the class-table metadata it already emits,
  monomorphic, no runtime type tag, no dynamic dispatch
- closes the query-result-serialization gap
  (csv.encode(from e in Employee ... select e)) that has no expression
  today; acceptance requires json.encode retrofitted onto the framework
  with byte-identical output, and disassembly proving no reflection
- four forks: request surface (lean @derive annotation), invocation
  (lean compiler-recognized encode builtins, no UFCS/methods), Eq/Hash
  sharing the engine's key comparison, static applicability checking
- out of scope: full trait/typeclass system, user proc-macros, general
  generics, cross-channel derive -- a CLOSED compiler-known derivable
  set, the pragmatic 80% without the type-system weight
- numbered 13 to echo the principle it lives inside; roadmap + board
  rows added

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 04:57:00 +02:00
71d3985e81 docs: performance arc as story iterations (9e measure, 9f io_uring)
- 9e durability/throughput/scale: the measurement backbone -- run the
  employee program, restart to prove persistence, benchmark read/write
  through compiled .wo, ~1M-row mixed load with throughput floor + p99
  ceiling + flat RSS; the gate every optimization signs (before/after
  delta required, no measured delta = not accepted)
- 9f io_uring group-commit: replace fsync-per-commit with batched
  io_uring durability overlapped on shard threads; same ack-after-
  durable contract, crash battery unchanged, automatic fsync fallback
  on kernels without it; deliberately LAST (needs 8's threads to
  overlap and 9e's baseline to beat)
- wired the existing levers into the arc: iteration 8 (thread-per-core)
  = "optimize multithreading", 7b (mark-sweep) = "implement GC" --
  each now gated by re-running 9e and recording the delta
- explicit sequence recorded in 9e: 9b lands -> 9e baseline -> 7b
  re-bench -> 8 re-bench -> 9f re-bench
- roadmap + board rows for 9e/9f; four forks each for the specs
  (load generator, absolute vs relative budgets, what "1M" means,
  durable vs RAM headline; ring model, liburing vs raw, batch
  boundary, fallback testing)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 22:12:48 +02:00
2e1041daed docs: employee-list sample -- program B's manifest pair (9c/9d target)
- docs/examples/employee-list: attaches to the running employee
  program; modes list / report (byte-identical to A's own) /
  staff <dept> / probe-write (registered read-only, insert must trap
  access-denied, exit 4, A unchanged)
- the manifests ARE the design, written as a pair: A's [share] gains
  listen = unix socket beside WO_DATA plus [[share.clients]] naming
  B's public-key fingerprint with rights = "read"; B's
  [connect.employee] carries A's ipc string, A's PINNED fingerprint,
  and project = ../employee for compile-time shapes -- the connect
  section's name is the code's namespace (employee.Employee)
- fingerprints are PASTE-HERE placeholders by design: keys generate
  into WO_DATA at first boot (9d), tomls carry fingerprints only,
  printed by --identity
- sample-first: compiles after 9/9b/9c/9d; README maps each mode to
  the acceptance line it exists for; 9c/9d stories now name this
  sample as their workload

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 12:34:19 +02:00
fe56ba0944 docs: iteration 9d story -- keypair authentication for attach
- promotes 9c's identity fork to its own iteration: program identity
  is a keypair (first-boot generated into WO_DATA, 0600, printable
  fingerprint); A's [share] grants name PUBLIC KEYS, B pins A's key
  in [connect.a]; mutual challenge-response, fresh nonces, transcript-
  hash signing (protocol tag + fingerprints + nonces + channel)
- acceptance criteria: registered-key attach carries 9c rights
  unchanged; unregistered key refused pre-statement with fingerprint
  logged; same-uid-wrong-key refused (uid SUPERSEDED, not
  supplemented); impostor on A's socket path aborted by B's pinned-key
  check; handshake replay refused; rotation = manifest change
- four forks recorded: crypto provenance (lean: vendored compact
  Ed25519 as the one sanctioned vendored component), keygen home
  (lean: first-boot into WO_DATA), signed-transcript layout, uid
  survival (lean: keys only, peer-cred demoted to log enrichment)
- out of scope: transport encryption, CA machinery, key escrow,
  root-attacker protection
- plan folds into 9c's when specced (neither ships alone); 9c fork 3
  marked superseded-as-end-state; roadmap + board rows added

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 12:26:49 +02:00
b92357b6b9 docs: iteration 9c story -- cross-program tables (attach over IPC)
- program B attaches to running program A's persistent database via an
  IPC string in B's wo.toml [connect.<name>]; A registers clients by
  name with read / read+write rights in its [share] manifest section;
  unregistered = refused at connect, under-privileged = catchable trap
- doctrine preserved: A stays the single writer -- B's statements
  execute inside A through the same choke-point row API, B never
  touches A's WAL or slabs; typed statements checked by B's compiler
  against A's table shapes, schema handshake at attach
- four forks recorded for the spec: channel carrier (lean: unix
  socket + SO_PEERCRED), how B's compiler learns A's shapes (lean:
  project reference + live handshake), grant granularity (lean:
  whole-db rights, name+uid identity), blocking semantics (lean:
  blocking round-trip, stop-flag rule applies)
- acceptance sketch: employee sample as A, a thin employee-report
  client as B (read-only GroupBy over the wire) + audit-log writer
  exercising the rights matrix
- slots after 9b (shares its typed surface), before 10 (HTTP is the
  external face; this is the writeonce-native one); prior art:
  04-client-api.md wire protocol + the WAL's value encoding
- roadmap + status board rows added

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 12:13:38 +02:00
1749440351 docs: borrow/GC analysis for the database engine, into the iterations
- 9b spec gains section 6 "Ownership, borrows, and GC across the
  engine boundary": two one-way copy gates (no VM pointer enters a
  row, everything a select returns is copied out), so the collector
  never traces engine memory and the engine never touches refcounts
- row views are borrows WITHOUT a runtime net: rows share the VM's
  field encoding but not its header, so no borrow word backs them --
  the compile-time escape rule is load-bearing alone
- cursor stability settled: scans materialize their id list before
  the body, row updates through the view stay legal (raise mode
  updates an indexed column mid-scan and is the proving fixture),
  insert/delete on a table with an open cursor is a new WO-E5xx
- GC-pause interaction recorded: collector runs between statements,
  a long scan delays slices -- accepted, documented
- iteration-7b ordering constraint: GC inference must classify before
  table-field validation, diagnostic names the inference reason --
  noted in 7b story, iteration-9 plan constraints, 9b plan tasks
- stories 09/09b Info sections point at the analysis; 9b plan Tasks
  3/5 carry the enforceable checkboxes (ASan boundary assertion)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 09:57:53 +02:00
5943bf6890 docs: iteration 9b spec + plan — @table, relations, query (employee)
- spec settles 9b's three forks: SQL/Cypher layer superseded as the
  program surface (design history + wo-db engine-semantics reference);
  comprehension syntax desugared at compile time (no function values);
  System.Linq = operator vocabulary + edge cases, PostgreSQL = execution
  + integrity vocabulary (both references surveyed 2026-08-15)
- aggregate semantics normative: count/sum total 0 on empty, avg/min/max
  are ?T with nil (empty is data, not a fault); nil skipped; sum wraps
  like language arithmetic; GroupBy lowers as group-and-reduce
  (AggregateBy shape), transition/finalize ABI from nodeAgg
- relations: ref = FK with direct-index-probe check (nil passes,
  unchanged-key skips), backlink = secondary-index scan, delete is
  restrict-only; nil never joins, nil is a legal group key
- lowering: the compiler is the planner — queries become bytecode loops
  over cursor/group builtins, longest-prefix index selection, no plan
  tree, no SQL text in the image (disassembly-provable)
- plan: 6 tasks gated by a new docs/examples/employee sample
  (Department/Employee, @unique, composite index, ref/backlink,
  GroupBy report mode) with its own acceptance script + crash step;
  blocked on iteration 9's engine plan
- story 09b + status board updated; 02-wo-language.md carries the
  supersession note

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 09:10:08 +02:00
7c10df67a3 docs: reprioritise to log-watcher-executable only; update stories and plans
Every remaining item is now traced to a measurement on the sample; anything the
sample does not exercise is deferred by name with the measurement that says so.

- new plan docs/plan/compiler/2026-08-14-logwatcher-executable.md — six tasks
  between "it runs" and "you can leave it running": the ownership pass learning
  stdlib return types (>1 MB leaked in 8s of `run` mode, one fs.read_all
  result), dropping a projected temporary (`for e in parse_dir(d).entries`
  leaks the shell per rescan), the runtime's own argv container (128 B every
  run), honouring the stop signal in blocking calls (a server in accept ignores
  SIGTERM), closing accepted connections (net.close exists, unused), and a soak
  that would have caught all of it. Opens with the measured starting point and
  closes with an explicit out-of-scope list
- story 7 (log-watcher proof): status banner separating the met compile-and-run
  half from the executable half, plus a new Given/When/Then — clean SIGTERM
  exit, zero leaks, flat RSS and descriptors across a soak
- story 5: grammar half landed, strictness half deliberately deferred
- story 6: landed for the surface the workload uses, with the two lifetime
  defects it exposed pointed at the new plan
- story 7b: recorded as off this workload's path, measured — the sample has no
  @gc class, 0 RC_INC/RC_DEC against 78 DROPs
- 00-status.md: NEXT PLAN is the executable list; story table and in-progress
  row point at the new plan; deferrals carry their evidence
- plan 8 (haxe-parity): banner now says on hold behind the executable plan, and
  its task states are corrected — Task 5 shipped, Tasks 6 and 7 are half done

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 22:16:14 +02:00