Commit graph

87 commits

Author SHA1 Message Date
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
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