writeonce/docs/stories/language-runtime-database/18-memory-db-features.md
shoney.arickathil 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

147 lines
7.6 KiB
Markdown

---
iteration: "18"
status: 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](../../examples/writeonce-serve/README.md);
> 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](00-story.md).
>
> **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.now` exists (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.accept` blocks 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)
1. **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.
2. **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.)
3. **`transaction { }` ships in this iteration.** Language block deferring
`wal_commit` to 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.
4. **Recorded as iteration 18; spec + plan before any code.**
## Goals (draft — the spec refines)
- `framework/cache.wo`: a TTL + size-cap cache class (lazy `time.now`
expiry 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 `@table` queue (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-app` stays green throughout.
> **Not this iteration (noted 2026-08-26):** the Fiber v3.5.0 parity study
> ([`plan/exploration/fiber/00-fiber-parity.md`](../../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](39-web-framework-parity.md) rather than here, deliberately:
> this iteration's spec is approved for the memory-rich half (TTL cache,
> `@table` flags, 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.