writeonce/docs/00-principles.md
shoney.arickathil 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

169 lines
9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# The writeonce principles
The doctrine in one page. Every design argument in this repo eventually
lands on one of these thirteen; later documents link here instead of
re-arguing them. Each principle: what it is, why it holds, where it is
enforced.
## 1. One binary is the whole system
The application, the database, the API, and (later) the UI ship as a single
deployable — there is nothing else to install, operate, or version-skew.
*Why:* the assembled-stack tax (app + DB server + proxy + glue) is the
problem writeonce exists to delete.
*Enforced by:* [`01-problem.md`](01-problem.md), the single-binary story in
[the OOP spec](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).
## 2. Zero dependencies — kernel primitives only
The runtime is C on libc; the compiler is OCaml on its stdlib; everything
else is epoll/io_uring, inotify, eventfd, signalfd, sendfile, mmap. The
kernel is the framework.
*Why:* every dependency is a supply chain, an upgrade treadmill, and a
black box in the one binary that must be understood end to end.
*Enforced by:* [the kernel-primitives catalogue](plan/exploration/linux/00-linux.md),
the dependency doctrine in [the OOP spec](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).
## 3. Memory safety without a GC tax
Objects are owned values: one owner, moves on assignment, second-class
borrows checked mostly at compile time (mutable value semantics — the
Rust-borrow shape without lifetime inference). GC-ness is **inferred** by
the compiler (iteration 7b): a class in a reference cycle, or one whose
values must escape as long-lived aliases, is traced by an incremental
per-shard tri-color mark-sweep collector in budgeted slices — the developer
writes no memory annotation, and no global pause exists by construction.
*Why:* deterministic memory for the default case, aliasing freedom where
the design wants it, and never a stop-the-world in a runtime that is also
the database.
*Enforced by:* [the OOP spec §4](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).
## 4. No inheritance, ever
No `extends`, no `override`, no virtual hierarchies. Is-a is a tagged
union; has-a is composition; polymorphism is structural interfaces.
*Why:* hierarchies fossilize early guesses and make dispatch, ownership,
and diagnostics all harder; composition keeps every unit flat and movable.
*Enforced by:* [the OOP spec](superpowers/specs/2026-08-01-oop-compiler-vm-design.md),
the reject rows of [the systems-track verdict table](superpowers/specs/2026-08-01-systems-track-design.md).
## 5. Thread-per-core shards; ownership moves, data never shares
One pinned worker per core, each owning its engine, heap, and event loop.
Cross-shard work is a message send that moves ownership. There is no
`Arc<Mutex<…>>` anywhere and never will be.
*Why:* sharing mutable state buys contention, locks, and heisenbugs;
moving ownership buys linear scaling and per-shard GC.
*Enforced by:* [the shard-fiber arc plan](superpowers/plans/2026-08-20-shard-fiber-arc.md)
(stages 1+2 landed; supersedes the discarded 2026-08-01 shard-actor plan
and the Rust-era plan 09, removed with that track 2026-08-18).
## 6. The runtime never stops
The executable is a systemd service that deploys without restarting: two
VM slots (Blue/Green), in-runtime compile of an approved proposal, atomic
dispatch switch, previous version resident for instant rollback — and the
binary embeds its own source, so prod is always self-describing.
*Why:* restarts drop connections, dump caches, and turn deploys into
events; a database that is also the app must not blink.
*Enforced by:* [the blue-green spec](superpowers/specs/2026-08-03-blue-green-vm-design.md).
## 7. The log is authoritative; residency is a declared per-table policy
**Amended 2026-08-26.** This principle read "RAM is authoritative; the WAL
makes it durable. All reads serve from memory." The durability half was never
under strain and is unchanged. The residency half was false for a real
workload, so it is now a declaration rather than a law.
**Durability, unconditional:** every mutation is WAL-logged and fsynced before
acknowledgment; boot replays the log; a torn tail is dropped whole by CRC; an
ack means the commit reached disk. Mirrors (Postgres) are reconstructible
backups that reads and acks never depend on. None of this is per-table and
none of it is negotiable.
**Residency, declared:** what a table keeps in memory is stated at the
declaration site. The default keeps every row resident and serves reads at
memory speed. A table that cannot fit says so, and then only its indexes are
resident while rows are read from the log by offset — the kernel page cache is
the hot copy, which is why the engine uses `pread` and deliberately not
`O_DIRECT`.
*Why the amendment:* the original wording is right for a knowledge-management
app and simply false for a 120 GB order table on a 32 GB host. A doctrine a
real workload cannot satisfy does not get followed, it gets ignored — and the
failure it produced was an OOM kill, which is the least debuggable outcome
available. The fix keeps one storage engine and one source of truth: the log
*is* the database, and RAM is how much of it you choose to serve fast. What was
rejected in 2026-08-18 and stays rejected is a *second* engine — a paged
B-tree with its own buffer pool ([`plan/discarded.md`](plan/discarded.md)).
Reading rows from the log we already write is not that.
*Enforced by:* [the db-engine binding plan](superpowers/plans/2026-08-01-db-engine-binding.md)
(typed WAL + boot replay, shipped); the residency declaration and its
enforcement are [databasev2 2](stories/databasev2/02-table-storage-modes.md);
the mirror-is-backup doctrine is recorded in
[`plan/discarded.md`](plan/discarded.md) (the Rust-era WAL and mirror plans
11/16 were removed with that track 2026-08-18).
## 8. Samples force the grammar
Language features exist when a sample program exercises them; the examples
directory is the de facto integration suite, and new surface is proven by
re-expressing real workloads (blog, ecommerce, pricing, log-watcher).
*Why:* grammars designed in the abstract grow features nobody needs and
miss the ones real programs demand.
*Enforced by:* [the web-app sample](examples/web-app/README.md)
(the blog sample left with the Rust track),
the sample-workload acceptance in [the systems-track spec](superpowers/specs/2026-08-01-systems-track-design.md).
## 9. Linux is the target
Not POSIX, not portable-someday: Linux syscalls, Linux fd semantics,
systemd as the process manager. Portability abstractions are refused.
*Why:* targeting one kernel lets the runtime use its sharpest primitives
directly instead of the lowest common denominator.
*Enforced by:* [the kernel-primitives catalogue](plan/exploration/linux/00-linux.md).
## 10. Capabilities are typed builtins — no FFI
Programs reach the system only through audited stdlib builtins — six
reserved namespaces (`fs`, `proc`, `net`, `time`, `json`, `env`): bounded
reads, args-array-only process runs, handles that close on drop. There is
no `extern`, no escape hatch.
*Why:* one FFI hole voids the entire memory-safety and security story;
typed capabilities make the safe path the only path.
*Enforced by:* [the systems-track spec Parts 2–3](superpowers/specs/2026-08-01-systems-track-design.md).
## 11. Plain diagnostics are the product
Stable `WO-E###` codes, `file:line:col`, source excerpts, ownership errors
naming both sites, many errors per run.
*Why:* mutable value semantics only beats Rust ergonomics if the errors
read like sentences; the compiler's error text is a first-class feature.
*Enforced by:* [the OOP spec §6](superpowers/specs/2026-08-01-oop-compiler-vm-design.md),
[the compiler architecture doctrine](plan/compiler/architecture.md).
## 12. The runtime is a recipe box
Transports, fibers, routing, subscriptions, the DB engine, deploy
machinery — each stays a separable capability. A web framework or a custom
database experience is a `.wo` library composing them; the runtime itself
stays framework-agnostic.
*Why:* the next stories (web framework, richer database surfaces) must be
buildable *on* the runtime without forking it.
*Enforced by:* [the blue-green vision §2](plan/exploration/blue-green-vm/00-vision.md).
## 13. Statically typed, all the way to the register
Every slot's type is known at compile time: no `Dynamic`, no `untyped`, no
`cast`, no runtime reflection. The VM runs untagged 64-bit registers
because the compiler already knows; JSON enters through checked decodes
(`as T` yielding `?T`), never through dynamic objects.
*Why:* the type system is the foundation the untagged VM, the borrow
checker, and the annotation ORM (`@table` classes, `ref`/`multi`
relations) all stand on — one dynamic hole collapses all three. The Haxe
reference workload shows the alternative: its transcompiled C++ pays a
hashed `__Field` lookup on every typedef access.
*Enforced by:* the `Dynamic`/`untyped`/`cast` reject rows of
[the systems-track verdict table](superpowers/specs/2026-08-01-systems-track-design.md),
untagged registers in [the OOP spec §5](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).