- 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>
7.6 KiB
| iteration | status |
|---|---|
| 18 | hold |
Iteration 18 — framework v2: memory-rich features over the embedded database
Scope label (2026-08-20): this iteration is FRAMEWORK V2. Framework v1 is the transport/routing/body/security surface tracked in the framework README's status ledger; v2 is what the embedded store adds on top. v1 gaps land before or alongside v2 as slices, per the ledger.
Format:
product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.Inserted 2026-08-20, forks settled the same day (developer decisions below). Next step: spec + plan, implementation on approval — the iteration-17 discipline.
Why this iteration exists
The single binary owns its memory AND its durable store — the class of
features other stacks buy with Redis, a message broker, and an outbox
pattern falls out of what is already here: an in-process map needs no
serialization format or cache-invalidation protocol (one process, one
node); a @table is a durable queue with WAL recovery for free; and the
WAL already stages multi-write batches internally (wal_append* →
wal_commit) — db.c merely commits per statement today, so exposing the
batch as a language transaction makes "enqueue a job and write the order
in ONE commit" true, which is the outbox problem dissolved rather than
worked around.
Ground truths the design stands on (verified 2026-08-20)
time.nowexists (Timestamp) — TTL/expiry is expressible; there are no timers, so expiry must piggyback on requests (lazy), never on a clock.- The runtime is single-threaded and
net.acceptblocks without a timeout: an IDLE server executes nothing. Any "background" work runs only when requests give it time — disclosed, not hidden. - Long-lived state lives on the instances the app wires (
App, middleware, handler fields) — they survive across requests; GC (7b) handles the churn. No globals needed, none exist. - WAL commit is per statement in
db.c; the staged-batch machinery beneath it is already transactional in shape (RAM apply → append → commit=write+fdatasync).
Settled decisions (2026-08-20)
- Scope: TTL cache, feature flags, background jobs. Pub/sub for WebSockets is REJECTED for now — WebSockets do not exist, and long-lived connections on a single-threaded accept loop is the iteration-16 starvation lesson magnified; both wait for shards/fibers (8/11). A channel data structure without delivery is mechanism without a consumer.
- Jobs execution model: drain-on-request. A bounded job budget runs after each served response, in-process, adjacent to the app's own writes. Honest limit stated everywhere it matters: an idle server drains nothing until the next request arrives. Fibers (11) later replaces the scheduler; the queue table and job shape stay. (Rejected for v1: a second worker process over 20 attach — real parallelism but blocks on finishing 20; parking jobs entirely — the queue-plus-drain is useful today.)
transaction { }ships in this iteration. Language block deferringwal_committo block end; a trap unwinding out of the block aborts the staged batch (nothing committed, RAM state rolled back or rebuilt per the spec's choice — the spec must settle recovery semantics precisely). This is the headline: job enqueue + business write, one fdatasync, no outbox.- Recorded as iteration 18; spec + plan before any code.
Goals (draft — the spec refines)
framework/cache.wo: a TTL + size-cap cache class (lazytime.nowexpiry on read, evict-on-write over capacity) usable as a field on any long-lived instance. Pure.wo, no engine change.- Feature flags:
@table-backed flags with a cached read-through map and bump-on-write invalidation — a framework pattern (and helper) proving "table + cached read" with zero cross-node invalidation problem. - Background jobs: a
@tablequeue (durable, WAL-recovered, restart-run proven like the storefront's) + a framework drain seam — the serve loop offers a bounded after-response tick to a job runner the app registers; job handlers are classes satisfying an interface (the Handler doctrine). transaction { }: multi-statement atomicity exposed in the language, engine-backed by the existing staged batch; enqueue-with-write becomes one commit. Trap = abort.- The web-app demonstrates: an order-confirmation job enqueued in the same transaction as the order insert, drained after later requests.
Open questions for the spec (not forks — details)
- Transaction semantics under trap: RAM apply happens before append — abort must undo RAM state; the spec settles whether the engine keeps an undo list or applies RAM changes only at commit.
- Nested
transaction { }: reject (WO-E1xx) or flatten; leaning reject. - Job table shape: id, kind, payload (json Text), attempts, not_before — the spec fixes it; retries/backoff policy stays app-side in v1.
- Drain seam shape: interface on the Dispatcher, or a second registration
on
App(app.jobs(runner, budget)); leaning the App registration. - Cache eviction order: exact LRU needs an ordered structure — the spec decides between approximate (FIFO of keys) and true LRU cost.
Acceptance Criteria (draft)
- Given a cache with TTL 1 and capacity N, when a request reads an expired key or writes past capacity, then the entry is gone / evicted — proven by a probe without sleeping the server (stamps injected, not waited).
- Given two writes inside
transaction { }and a crash (SIGKILL) between block end and the next request, when the server restarts, then both rows exist; given a trap inside the block, then neither row exists and the server keeps serving. - Given an order POST that enqueues a job transactionally, when the response has been sent and a subsequent request arrives, then the job has run within the drain budget; given a SIGKILL before the drain, then the job survives restart and runs after the next request.
- Given a flag flipped through its table, when the next request
reads it, then the cached read reflects the write (bump
invalidation), and
just web-appstays green throughout.
Not this iteration (noted 2026-08-26): the Fiber v3.5.0 parity study (
plan/exploration/fiber/00-fiber-parity.md) found a second axis of framework gaps — cookies, sessions, CSRF, rate limiting, idempotency, request ids, response helpers. Those went to iteration 39 rather than here, deliberately: this iteration's spec is approved for the memory-rich half (TTL cache,@tableflags, durable job queue,transaction { }) and widening it would invalidate that approval. The one overlap is the TTL cache, which stays this iteration's — 39's out-of-scope list points back here for it.
Out Of Scope
Pub/sub and WebSockets (behind 8/11); streaming job payloads; cross-node
anything (there is one node by doctrine); job priorities/cron scheduling
(the log-watcher run mode already covers time-based execution
externally); exposing the WAL batch API beyond transaction { };
distributed cache invalidation (does not exist to invalidate).
Proposed Solution
Spec next: transaction semantics (the one engine+language seam), the four
framework pieces as .wo (cache, flags, queue+drain, web-app demo), and
just web-app extended as the gate — including the SIGKILL/restart
transactional-jobs proof. Plan follows the spec; implementation on
approval.