docs: status board at docs/00-status.md; gap-closure spec applied; recover lost doc
- Board renamed docs/plan/00-kanban.md -> docs/00-status.md and rebuilt: ▶ NEXT PLAN pointer (iteration 4 — emitter, corpus, `woc build`) then six buckets — stories, in progress, done, pending, discarded, learnings. It covered only the Rust runtime before, so the whole OOP track was invisible. All 16 inbound refs repointed; `Kanban:` banners renamed to `Status:`. - New discarded.md (settled rejections with reasons: inheritance, `abstract`, Money/SKU/Float, Dynamic/cast/macro/extern, AOT-to-C, Menhir, shared engine state) and learnings.md (plumbed≠enforced, vacuous goldens, exit-0-wrong- output, malloc-path ASan trick, deferred checks that never reach the VM). - RECOVERED docs/plan/exploration/blue-green-vm/00-vision.md — gone from disk, never committed (gitignored path), cited by five docs incl. principle 12. Root cause was broader: all seven forward-roadmap plans in docs/superpowers/plans/ were untracked and ignored, on one disk only. Dropped the docs ignore rules with a do-not-re-add note; added __pycache__/*.pyc. - Repaired broken links across docs/, 270 -> 36: fixes a regression from the earlier reference/ -> .dev/reference/ move (relative paths at ../../ and deeper were skipped), plus depth and reorg drift. The 36 residual point at content that does not exist and need decisions, not paths. - New spec docs/superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md, applied: `and`/`or` verdict row; Part 3 gains `env` (six modules), swaps time.mono for iso/local, adds 22 bare core builtins; throw/time.mono/is cut (0 uses in the sample). Plan 8: Task 2 gains and/or, Task 5 drops throw, abstract+`is` task deleted, 8/9 renumber to 7/8. Plan 9 gains core builtins. Plan 10 gains the 307 -> 0 diagnostic gate. WO-E205 re-filed unreachable-by- design. types.ml header drops its false satisfaction-set claim. 00-code- review.md reduced to a stub — its rival Phase 1-4 roadmap retired.
This commit is contained in:
parent
49872a4b11
commit
a55971d857
66 changed files with 722 additions and 459 deletions
|
|
@ -2,7 +2,7 @@
|
|||
Two-pass:
|
||||
1. Collect all declarations (classes, interfaces, free fns, typedefs)
|
||||
2. Typecheck bodies with full symbol tables.
|
||||
Produces typed AST + per-class field-kind table + interface satisfaction set. *)
|
||||
Produces typed AST + per-class field-kind table. *)
|
||||
|
||||
open Ast
|
||||
|
||||
|
|
|
|||
8
docs/00-code-review.md
Normal file
8
docs/00-code-review.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Code Review: log-watcher Compilation Requirements
|
||||
|
||||
This document was a gap analysis of what the `woc` front end needs before the
|
||||
log-watcher sample compiles. Its findings were extracted on 2026-08-10 into
|
||||
[`superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md`](superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md)
|
||||
and the plans it amends; its Phase 1–4 roadmap is retired in favour of the
|
||||
approved story iterations. See [`00-status.md`](00-status.md) for current
|
||||
status.
|
||||
201
docs/00-status.md
Normal file
201
docs/00-status.md
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
# Status board — what is done, what is next
|
||||
|
||||
The single place to learn where this project stands. Organised in six buckets:
|
||||
**stories** (the narrative arc), **in progress**, **done**, **pending**,
|
||||
**discarded**, **learnings**. Every phase doc carries a matching status banner;
|
||||
this board is the index.
|
||||
|
||||
Update this board in the same change that finishes work — move the item to done
|
||||
with *what actually landed*, set the next in-progress item, and record any
|
||||
rejection in [`discarded.md`](plan/discarded.md) with its reason.
|
||||
|
||||
Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **parked**
|
||||
|
||||
---
|
||||
|
||||
## ▶ NEXT PLAN
|
||||
|
||||
**Story iteration 4 — single binary end-to-end.**
|
||||
Plan: [`compiler/plan/2026-08-01-wob-emit-e2e-single-binary.md`](plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md) ·
|
||||
Story slice: [`docs/stories/language-runtime-database/04-single-binary-e2e.md`](stories/language-runtime-database/04-single-binary-e2e.md)
|
||||
|
||||
The bytecode emitter, the three-kind conformance corpus, and `woc build`. This
|
||||
is the milestone where `.wo` source becomes a running self-contained binary —
|
||||
compiler front (iteration 3) and VM core (iteration 2) both shipped, so it is
|
||||
unblocked. Its inputs are the four ownership tables `owner.ml` now produces;
|
||||
`dump.ml`'s format-contract comments are normative for it, **including the
|
||||
requirement to coalesce borrow guards per operand**.
|
||||
|
||||
Iteration 4 proves the pipeline on the **milestone grammar only** — the
|
||||
emitter, corpus, and `woc build` exercise the grammar iteration 3 already
|
||||
compiles, not the log-watcher sample, which still needs ~200 constructs the
|
||||
front end cannot yet parse (iterations 5–6 work). Iteration 4 must not be
|
||||
judged against the sample (gap-closure spec, §6).
|
||||
|
||||
Two tracks run in this repo. The critical path is the **language track**:
|
||||
iterations 3 → 4 → 5 → 6 → 7, ending at *compile and run log-watcher*. The
|
||||
Rust-runtime track is shipped-and-maintained, not advancing.
|
||||
|
||||
---
|
||||
|
||||
## Stories
|
||||
|
||||
[`docs/stories/language-runtime-database/`](stories/language-runtime-database/00-story.md)
|
||||
— one language, one runtime, one database, one binary. Twelve iterations, each
|
||||
an unsplittable slice with Given/When/Then acceptance and a pointer to the plan
|
||||
that sequences its tasks. Read one, approve, then the next starts.
|
||||
|
||||
| # | Iteration | State |
|
||||
| --- | --- | --- |
|
||||
| 1 | [Principles doc](stories/language-runtime-database/01-principles-doc.md) | ✅ |
|
||||
| 2 | [VM core (`wovm`)](stories/language-runtime-database/02-vm-core.md) | ✅ |
|
||||
| 3 | [Compiler front (`woc`)](stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
|
||||
| 4 | [Single binary end-to-end](stories/language-runtime-database/04-single-binary-e2e.md) | 🔄 **next** |
|
||||
| 5 | [Language surface](stories/language-runtime-database/05-language-surface.md) | ⬜ |
|
||||
| 6 | [Program mode + stdlib](stories/language-runtime-database/06-program-mode-stdlib.md) | ⬜ |
|
||||
| 7 | [log-watcher proof](stories/language-runtime-database/07-logwatcher-proof.md) | ⬜ acceptance |
|
||||
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
||||
| 9 | [Database engine](stories/language-runtime-database/09-database-engine.md) | ⬜ |
|
||||
| 10 | [HTTP service layer](stories/language-runtime-database/10-http-service.md) | ⬜ |
|
||||
| 11 | [Fibers](stories/language-runtime-database/11-fibers.md) | ⬜ |
|
||||
| 12 | [Blue-green deploy](stories/language-runtime-database/12-blue-green-deploy.md) | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## In progress
|
||||
|
||||
| Track | Item | Where |
|
||||
| --- | --- | --- |
|
||||
| Language | Iteration 4 — emitter, conformance corpus, `woc build` | [plan 3](plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md) |
|
||||
|
||||
Nothing else should be started until iteration 4 lands. Off-critical-path work
|
||||
is parked by explicit scope directive (2026-08-08).
|
||||
|
||||
---
|
||||
|
||||
## Done
|
||||
|
||||
### Language track — compiler + VM (OOP track)
|
||||
|
||||
| Status | Item | Doc | What actually landed |
|
||||
| --- | --- | --- | --- |
|
||||
| ✅ | Principles | [`../00-principles.md`](00-principles.md) | 13 principles, each with a why and a link to the doc that enforces it |
|
||||
| ✅ | `wovm` VM core | [plan 1](superpowers/plans/2026-08-01-wob-format-and-vm-core.md) | `.wob` v1 loader with full static validation, register interpreter (computed-goto + ISO-C fallback), arena with size-class free lists, borrow word, RC + budgeted Bacon–Rajan cycle collector, drop-map trap unwinding, containers, builtins, ICALL, CLI. 13 suites × 2 dispatch flavors + CLI smoke, ASan/UBSan clean |
|
||||
| ✅ | `.wob` format contract | [`oop-vm/00-wob-format.md`](plan/oop-vm/00-wob-format.md) | Normative; twinned with `runtime/src/wob.h` |
|
||||
| ✅ | `woc` compiler front | [plan 2](plan/compiler/2026-08-01-woc-compiler-front.md) | Tasks 1–8: dune scaffold, `diag` (WO-E codes, two-site related errors, ordered dedup), newline-significant lexer at rt parity, declaration + statement/expression parser with skip-on-block and multi-error recovery, typechecker (field kinds, `?T` plumbing, W201, E225, E214), MVS ownership pass with the four emitter tables, driver with directory discovery + cross-file programs. 14 + 264 checks |
|
||||
| ✅ | Error catalog | [`oop-vm/01-error-catalog.md`](plan/oop-vm/01-error-catalog.md) | 14 emitted codes + 10 reserved, each with the reason it is not yet emitted |
|
||||
| ✅ | log-watcher `.wo` sample | [`../examples/log-watcher/`](examples/log-watcher/README.md) | Eight-file port authored docs-first with its `.hx` mapping table; compiles for real in iteration 7 |
|
||||
| ✅ | Scalar cleanup | [`discarded.md`](plan/discarded.md) | `Money`/`SKU`/`Float` and the abstract allowlist removed; `abstract` flipped adopt → reject |
|
||||
|
||||
**Known gaps carried out of iteration 3** — recorded, not silently owed:
|
||||
|
||||
- **`?T` is plumbed but unenforced.** Lexer/token/AST/parser/dump all handle
|
||||
`?T`; the semantics do not exist (`WO-E211`/`E212`/`E213` declared, never
|
||||
emitted — a probe returning `?Int` as `Int` exits 0). Owned by iteration 5,
|
||||
plan 8 Task 6, which is that iteration's first task because it blocks the
|
||||
log-watcher port. See [`compiler/nullable-types-implementation.md`](plan/compiler/nullable-types-implementation.md).
|
||||
- **Structural interface satisfaction is not checked** (`WO-E205` dead), along
|
||||
with type mismatch, bad arity, and unknown-fn (`E201`/`E203`/`E204`) — all
|
||||
named in plan 2 Task 6's own must-fail list. Gaps in shipped work, catalogued
|
||||
as reserved.
|
||||
- Six further narrowings (W201 heuristic, E225 reach, dead code after `return`,
|
||||
unresolved-callee drops, RC table ordering, residual b-side role) are listed
|
||||
in the plan-2 SDD ledger and in the affected files' own comments.
|
||||
|
||||
### Rust runtime track — Stage 2 shipped, maintained
|
||||
|
||||
| Status | Phase | Doc | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| ✅ | 01 crate scaffolding | [done/01](plan/done/01-scafolding-crates.md) | 15 crates |
|
||||
| ✅ | 02 epoll event loop | [done/02](plan/done/02-event-loop-epoll.md) | `runtime/netpoll_epoll.rs` |
|
||||
| ✅ | 03 hand-rolled HTTP | [done/03](plan/done/03-hand-rolled-http.md) | + keep-alive & pipelining |
|
||||
| ✅ | 04 tokio/axum cutover | [done/04](plan/done/04-cutover-remove-tokio-axum.md) | deps now: anyhow, serde, serde_json, libc |
|
||||
| ✅ | 09a thread-per-core | [09](plan/09-concurrency-scaleout.md) | `scheduler.rs`, `SO_REUSEPORT`, pinned `wo-shard-<t>` workers |
|
||||
| ✅ | 09b sharded engine | [09](plan/09-concurrency-scaleout.md) | `shard.rs` bus; `Arc<Mutex<Engine>>` deleted; interleaved ids |
|
||||
| ✅ | 09c per-shard WAL | [09](plan/09-concurrency-scaleout.md) | ack-after-fsync; boot replay; `meta` shard guard |
|
||||
| ✅ | — keep-alive follow-up | [09](plan/09-concurrency-scaleout.md) | reads ×3.4 → 770k/s |
|
||||
| ✅ | — io_uring group commit | [09](plan/09-concurrency-scaleout.md) | raw ring; 4.7× durable writes on real disk |
|
||||
| ✅ | 16a PG wire client | [16](plan/16-postgres-mirror.md) | hand-rolled protocol v3, zero crates |
|
||||
| ✅ | 16b PG backup mirror | [16](plan/16-postgres-mirror.md) | async JSONB upserts behind the WAL ack; RAM authoritative |
|
||||
| ✅ | 13a class surface | [13](plan/13-class-model-live-pricing.md) | `class` parses, CRUD serves |
|
||||
| ✅ | 13b method execution | [13](plan/13-class-model-live-pricing.md) | row-scoped txn per call; abort → 409 rollback |
|
||||
| ✅ | — `@table` + indexed DML | [13](plan/13-class-model-live-pricing.md) | secondary indexes, `find_by`, `select Type{…}`, REST filters |
|
||||
| ✅ | C proving ground A–F | [exploration/c-runtime/00-plan.md](plan/exploration/c-runtime/00-plan.md) | 859k reads/s, 618k durable commits/s; found the ack-ordering + fd-ABA bugs the Rust port avoided |
|
||||
|
||||
Ecommerce sample (verified 2026-06-13): `api.rest` 17/17 expected statuses pass.
|
||||
|
||||
---
|
||||
|
||||
## Pending
|
||||
|
||||
### Language track — sequenced, on the critical path
|
||||
|
||||
| # | Item | Plan |
|
||||
| --- | --- | --- |
|
||||
| 5 | Haxe-parity language surface — **`?T` forced handling first**, then switch expressions, records, enum payloads, try/catch, statics, `using`, modules, `is`, `pub(read)`, `#if` | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
|
||||
| 6 | Program mode + systems stdlib — `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` | [plan 9](superpowers/plans/2026-08-01-program-mode-stdlib.md) |
|
||||
| 7 | log-watcher proof — the sample compiles and detects a silent death live | [plan 10](superpowers/plans/2026-08-01-log-watcher-sample.md) |
|
||||
| 8 | Shard-actor runtime | [plan 4](superpowers/plans/2026-08-01-shard-actor-vm-runtime.md) |
|
||||
| 9 | Database engine binding | [plan 5](superpowers/plans/2026-08-01-db-engine-binding.md) |
|
||||
| 10 | HTTP service layer | [plan 6](superpowers/plans/2026-08-01-http-service-layer.md) |
|
||||
| 11 | Fibers | vision §3, [blue-green exploration](plan/exploration/blue-green-vm/00-vision.md) |
|
||||
| 12 | Blue-green deploy | [spec](superpowers/specs/2026-08-03-blue-green-vm-design.md) — plan authored after iterations 9–10 |
|
||||
|
||||
### Language track — parked until after iteration 12
|
||||
|
||||
Recorded 2026-08-08 by scope directive; nothing here lands before the
|
||||
log-watcher proof.
|
||||
|
||||
- `WO-W201` `@gc`-suggestion refinement beyond the self-reference heuristic
|
||||
- `WO-E225` broadened to `ref`/`multi`/`map` element types and fn signatures
|
||||
- ADT container roster adoption (Stack, Queue, Set, Tree, Graph, …) — see the
|
||||
roster in [`compiler/nullable-types-implementation.md`](plan/compiler/nullable-types-implementation.md)
|
||||
- Web framework as a `.wo` library; UI (`##ui` SSR + live patches);
|
||||
script-based destructive migrations; MCP/agent wrapper over the management plane
|
||||
- `throw` (explicit raise) — cut 2026-08-10, 0 uses in the driving workload
|
||||
(log-watcher); catch frames ship without it
|
||||
- `time.mono` — cut 2026-08-10, 0 uses in the driving workload; returns when a
|
||||
workload needs monotonic math
|
||||
- `is` — cut 2026-08-10, 0 uses in the driving workload; emptied plan 8's old
|
||||
Task 7, which is deleted rather than deferred
|
||||
|
||||
### Rust runtime track — not advancing while the language track runs
|
||||
|
||||
| Status | Phase | Doc | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| ⬜ | 05 hand-rolled JSON | [05](plan/05-hand-rolled-json.md) | removes serde/serde_json |
|
||||
| ⬜ | 06 bespoke error type | [06](plan/06-bespoke-error.md) | removes anyhow |
|
||||
| ⬜ | 07 inotify content watcher | [07](plan/07-inotify-content-watcher.md) | `wo dev` hot reload |
|
||||
| ⬜ | 08 sendfile static assets | [08](plan/08-sendfile-static-assets.md) | needed by the parked UI track |
|
||||
| ⬜ | 09d cross-shard subscriptions | [09](plan/09-concurrency-scaleout.md) | LIVE fan-out; pairs with Stage 3 |
|
||||
| ⬜ | 09e cross-shard transactions (2PC) | [09](plan/09-concurrency-scaleout.md) | needed by `fn checkout` spanning shards |
|
||||
| ⬜ | 09f observability & reshard | [09](plan/09-concurrency-scaleout.md) | per-shard metrics, `WO_RESHARD` |
|
||||
| ⬜ | 10–12 storage completion | [10](plan/10-storage-foundations.md), [11](plan/11-wal-and-recovery.md), [12](plan/12-engine-disk-cutover.md) | snapshots, compaction, WAL rotation, mmap arena engine |
|
||||
| ⬜ | 13c LIVE pricing push · Stage 3 wire layer · 13e at scale | [13](plan/13-class-model-live-pricing.md) | replaces the 501 stub |
|
||||
| ⬜ | 15a–15e MCP over streamable HTTP | [15](plan/15-mcp-streamable-http.md) | 15e needs 13c + 09d |
|
||||
| ⬜ | 16c–16f typed columns, lossless resync, restore, SCRAM | [16](plan/16-postgres-mirror.md) | |
|
||||
|
||||
### Frontend — parked
|
||||
|
||||
| Status | Phase | Doc |
|
||||
| --- | --- | --- |
|
||||
| ⏸ | 13d pricing UI | [13](plan/13-class-model-live-pricing.md) |
|
||||
| ⏸ | 14 MVC UI implementation (14a–f) | [14](plan/14-mvc-ui-implementation.md) |
|
||||
| ⏸ | UI exploration track | [exploration/ui/00-overview.md](plan/exploration/ui/00-overview.md) |
|
||||
|
||||
---
|
||||
|
||||
## Discarded
|
||||
|
||||
Settled rejections with their reasons live in [`discarded.md`](plan/discarded.md) —
|
||||
inheritance, `abstract` newtypes, `Money`/`SKU`/`Float`, `Dynamic`/`cast`/
|
||||
`macro`/`extern`, AOT-to-C, Menhir, shared mutable engine state, external
|
||||
deployer daemon, destructive migrations in v1, and more. Argue against the
|
||||
recorded reason rather than re-opening an entry as new.
|
||||
|
||||
## Learnings
|
||||
|
||||
What attempts taught, shipped or not, in [`learnings.md`](plan/learnings.md) —
|
||||
plumbed-is-not-enforced, vacuously-passing goldens, exit-0-with-wrong-output,
|
||||
the malloc-path ASan trick, deferred checks that never reach the runtime,
|
||||
validate-once-at-the-boundary, and reference-implement-in-C-first.
|
||||
|
|
@ -82,7 +82,7 @@ Unchanged from CLAUDE.md's description: `rt` is the monolithic Stage-2 runtime p
|
|||
### `.dev/reference/` — read-only study material
|
||||
|
||||
- `crates/` — the 13 v1 `wo-*` crates, a nested Cargo workspace (build with `cd .dev/reference/crates && cargo build`). Preserved per the wo-seg migration plan ([`runtime/database/07-wo-seg-migration.md`](runtime/database/07-wo-seg-migration.md)).
|
||||
- `colibri/`, `llama-cpp/` — vendored study trees (zero-dep C inference engine; MoE runtime) — see [`plan/exploration/colibri/`](plan/exploration/colibri/).
|
||||
- `colibri/`, `llama-cpp/` — vendored study trees (zero-dep C inference engine; MoE runtime) — see [`.dev/reference/colibri/`](.dev/reference/colibri/).
|
||||
- `linux/`, `go/` — per-developer symlinks to kernel and Go sources (gitignored; recreate per `CLAUDE.md`).
|
||||
- `rest/` — `.rest` HTTP files driving manual smoke against a running runtime; plan 6's blog smoke scripts the same sequences.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Two apps (customer **storefront** + ops **admin**) sharing one database, built from a common pool of types + business logic. Mirrors the Nx / Angular workspace pattern: `apps/*` for deployable binaries, `shared/*` for libraries imported across apps.
|
||||
|
||||
> This project is a **docs artifact** — illustrative `.wo` source showing what a production-shaped writeonce workspace looks like. The master plan for the compiler + client runtime + per-app build is at [`../../plan/ui/00-overview.md`](../../plan/ui/00-overview.md). Sub-phases UI/01–07 implement each piece.
|
||||
> This project is a **docs artifact** — illustrative `.wo` source showing what a production-shaped writeonce workspace looks like. The master plan for the compiler + client runtime + per-app build is at [`../../plan/ui/00-overview.md`](../../plan/exploration/ui/00-overview.md). Sub-phases UI/01–07 implement each piece.
|
||||
|
||||
## Layout
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ The current runtime at [`crates/rt/`](../../../crates/rt/) is a single-process S
|
|||
- `.htmlx` compilation from `##ui` blocks (UI sub-phases 01–03)
|
||||
- Per-app policy composition (UI sub-phase 07)
|
||||
|
||||
So `cargo run --bin wo -- run docs/examples/ecommerce` today walks the whole tree, finds every `.wo` file under `shared/` + `apps/`, parses the types, and serves the union REST API on :8080 — treating the monorepo as one giant app. Useful for exercising the types; not reflective of the production shape. See [`docs/plan/ui/00-overview.md`](../../plan/ui/00-overview.md) for the sub-phase sequence that gets each piece online.
|
||||
So `cargo run --bin wo -- run docs/examples/ecommerce` today walks the whole tree, finds every `.wo` file under `shared/` + `apps/`, parses the types, and serves the union REST API on :8080 — treating the monorepo as one giant app. Useful for exercising the types; not reflective of the production shape. See [`docs/plan/ui/00-overview.md`](../../plan/exploration/ui/00-overview.md) for the sub-phase sequence that gets each piece online.
|
||||
|
||||
## Comparison with the blog sample
|
||||
|
||||
|
|
@ -109,8 +109,8 @@ The [`blog` sample](../blog/) is still a single-app layout (`types/`, `ui/`, `lo
|
|||
|
||||
## Source pointers
|
||||
|
||||
- **Master plan:** [`../../plan/ui/00-overview.md`](../../plan/ui/00-overview.md)
|
||||
- **Master plan:** [`../../plan/ui/00-overview.md`](../../plan/exploration/ui/00-overview.md)
|
||||
- **Language spec the `##ui`/`##app`/`policy` blocks obey:** [`../../runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md)
|
||||
- **Wire protocol the app binaries speak to the DB daemon:** [`../../runtime/database/04-client-api.md`](../../runtime/database/04-client-api.md)
|
||||
- **v1 template engine that `.htmlx` compilation will reuse:** [`../../../reference/crates/wo-htmlx/`](../../../reference/crates/wo-htmlx/)
|
||||
- **v1 template engine that `.htmlx` compilation will reuse:** [`../../../.dev/reference/crates/wo-htmlx/`](../../../.dev/reference/crates/wo-htmlx/)
|
||||
- **Checkout transaction that's the canonical cross-paradigm test:** [`shared/logic/checkout.wo`](shared/logic/checkout.wo)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Users define relationships between articles in the JSON metadata. These mappings
|
|||
|
||||
### Mapping Fields in JSON Metadata
|
||||
|
||||
Per [06-markdown-render.md](./06-markdown-render.md), the JSON metadata is minimal. Add a `mappings` field:
|
||||
Per [06-markdown-render.md](../06-markdown-render.md), the JSON metadata is minimal. Add a `mappings` field:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
# Status board — what is done, what is next
|
||||
|
||||
The single place to learn where this project stands. Organised in six buckets:
|
||||
**stories** (the narrative arc), **in progress**, **done**, **pending**,
|
||||
**discarded**, **learnings**. Every phase doc carries a matching status banner;
|
||||
this board is the index.
|
||||
|
||||
Update this board in the same change that finishes work — move the item to done
|
||||
with *what actually landed*, set the next in-progress item, and record any
|
||||
rejection in [`discarded.md`](discarded.md) with its reason.
|
||||
|
||||
Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **parked**
|
||||
|
||||
---
|
||||
|
||||
## ▶ NEXT PLAN
|
||||
|
||||
**Story iteration 4 — single binary end-to-end.**
|
||||
Plan: [`compiler/plan/2026-08-01-wob-emit-e2e-single-binary.md`](compiler/2026-08-01-wob-emit-e2e-single-binary.md) ·
|
||||
Story slice: [`docs/stories/language-runtime-database/04-single-binary-e2e.md`](../stories/language-runtime-database/04-single-binary-e2e.md)
|
||||
|
||||
The bytecode emitter, the three-kind conformance corpus, and `woc build`. This
|
||||
is the milestone where `.wo` source becomes a running self-contained binary —
|
||||
compiler front (iteration 3) and VM core (iteration 2) both shipped, so it is
|
||||
unblocked. Its inputs are the four ownership tables `owner.ml` now produces;
|
||||
`dump.ml`'s format-contract comments are normative for it, **including the
|
||||
requirement to coalesce borrow guards per operand**.
|
||||
|
||||
Two tracks run in this repo. The critical path is the **language track**:
|
||||
iterations 3 → 4 → 5 → 6 → 7, ending at *compile and run log-watcher*. The
|
||||
Rust-runtime track is shipped-and-maintained, not advancing.
|
||||
|
||||
---
|
||||
|
||||
## Stories
|
||||
|
||||
[`docs/stories/language-runtime-database/`](../stories/language-runtime-database/00-story.md)
|
||||
— one language, one runtime, one database, one binary. Twelve iterations, each
|
||||
an unsplittable slice with Given/When/Then acceptance and a pointer to the plan
|
||||
that sequences its tasks. Read one, approve, then the next starts.
|
||||
|
||||
| # | Iteration | State |
|
||||
| --- | --- | --- |
|
||||
| 1 | [Principles doc](../stories/language-runtime-database/01-principles-doc.md) | ✅ |
|
||||
| 2 | [VM core (`wovm`)](../stories/language-runtime-database/02-vm-core.md) | ✅ |
|
||||
| 3 | [Compiler front (`woc`)](../stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
|
||||
| 4 | [Single binary end-to-end](../stories/language-runtime-database/04-single-binary-e2e.md) | 🔄 **next** |
|
||||
| 5 | [Language surface](../stories/language-runtime-database/05-language-surface.md) | ⬜ |
|
||||
| 6 | [Program mode + stdlib](../stories/language-runtime-database/06-program-mode-stdlib.md) | ⬜ |
|
||||
| 7 | [log-watcher proof](../stories/language-runtime-database/07-logwatcher-proof.md) | ⬜ acceptance |
|
||||
| 8 | [Shard-actor runtime](../stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
||||
| 9 | [Database engine](../stories/language-runtime-database/09-database-engine.md) | ⬜ |
|
||||
| 10 | [HTTP service layer](../stories/language-runtime-database/10-http-service.md) | ⬜ |
|
||||
| 11 | [Fibers](../stories/language-runtime-database/11-fibers.md) | ⬜ |
|
||||
| 12 | [Blue-green deploy](../stories/language-runtime-database/12-blue-green-deploy.md) | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## In progress
|
||||
|
||||
| Track | Item | Where |
|
||||
| --- | --- | --- |
|
||||
| Language | Iteration 4 — emitter, conformance corpus, `woc build` | [plan 3](compiler/2026-08-01-wob-emit-e2e-single-binary.md) |
|
||||
|
||||
Nothing else should be started until iteration 4 lands. Off-critical-path work
|
||||
is parked by explicit scope directive (2026-08-08).
|
||||
|
||||
---
|
||||
|
||||
## Done
|
||||
|
||||
### Language track — compiler + VM (OOP track)
|
||||
|
||||
| Status | Item | Doc | What actually landed |
|
||||
| --- | --- | --- | --- |
|
||||
| ✅ | Principles | [`../00-principles.md`](../00-principles.md) | 13 principles, each with a why and a link to the doc that enforces it |
|
||||
| ✅ | `wovm` VM core | [plan 1](../superpowers/plans/2026-08-01-wob-format-and-vm-core.md) | `.wob` v1 loader with full static validation, register interpreter (computed-goto + ISO-C fallback), arena with size-class free lists, borrow word, RC + budgeted Bacon–Rajan cycle collector, drop-map trap unwinding, containers, builtins, ICALL, CLI. 13 suites × 2 dispatch flavors + CLI smoke, ASan/UBSan clean |
|
||||
| ✅ | `.wob` format contract | [`oop-vm/00-wob-format.md`](oop-vm/00-wob-format.md) | Normative; twinned with `runtime/src/wob.h` |
|
||||
| ✅ | `woc` compiler front | [plan 2](compiler/2026-08-01-woc-compiler-front.md) | Tasks 1–8: dune scaffold, `diag` (WO-E codes, two-site related errors, ordered dedup), newline-significant lexer at rt parity, declaration + statement/expression parser with skip-on-block and multi-error recovery, typechecker (field kinds, `?T` plumbing, W201, E225, E214), MVS ownership pass with the four emitter tables, driver with directory discovery + cross-file programs. 14 + 264 checks |
|
||||
| ✅ | Error catalog | [`oop-vm/01-error-catalog.md`](oop-vm/01-error-catalog.md) | 14 emitted codes + 10 reserved, each with the reason it is not yet emitted |
|
||||
| ✅ | log-watcher `.wo` sample | [`../examples/log-watcher/`](../examples/log-watcher/README.md) | Eight-file port authored docs-first with its `.hx` mapping table; compiles for real in iteration 7 |
|
||||
| ✅ | Scalar cleanup | [`discarded.md`](discarded.md) | `Money`/`SKU`/`Float` and the abstract allowlist removed; `abstract` flipped adopt → reject |
|
||||
|
||||
**Known gaps carried out of iteration 3** — recorded, not silently owed:
|
||||
|
||||
- **`?T` is plumbed but unenforced.** Lexer/token/AST/parser/dump all handle
|
||||
`?T`; the semantics do not exist (`WO-E211`/`E212`/`E213` declared, never
|
||||
emitted — a probe returning `?Int` as `Int` exits 0). Owned by iteration 5,
|
||||
plan 8 Task 6, which is that iteration's first task because it blocks the
|
||||
log-watcher port. See [`compiler/nullable-types-implementation.md`](compiler/nullable-types-implementation.md).
|
||||
- **Structural interface satisfaction is not checked** (`WO-E205` dead), along
|
||||
with type mismatch, bad arity, and unknown-fn (`E201`/`E203`/`E204`) — all
|
||||
named in plan 2 Task 6's own must-fail list. Gaps in shipped work, catalogued
|
||||
as reserved.
|
||||
- Six further narrowings (W201 heuristic, E225 reach, dead code after `return`,
|
||||
unresolved-callee drops, RC table ordering, residual b-side role) are listed
|
||||
in the plan-2 SDD ledger and in the affected files' own comments.
|
||||
|
||||
### Rust runtime track — Stage 2 shipped, maintained
|
||||
|
||||
| Status | Phase | Doc | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| ✅ | 01 crate scaffolding | [done/01](done/01-scafolding-crates.md) | 15 crates |
|
||||
| ✅ | 02 epoll event loop | [done/02](done/02-event-loop-epoll.md) | `runtime/netpoll_epoll.rs` |
|
||||
| ✅ | 03 hand-rolled HTTP | [done/03](done/03-hand-rolled-http.md) | + keep-alive & pipelining |
|
||||
| ✅ | 04 tokio/axum cutover | [done/04](done/04-cutover-remove-tokio-axum.md) | deps now: anyhow, serde, serde_json, libc |
|
||||
| ✅ | 09a thread-per-core | [09](09-concurrency-scaleout.md) | `scheduler.rs`, `SO_REUSEPORT`, pinned `wo-shard-<t>` workers |
|
||||
| ✅ | 09b sharded engine | [09](09-concurrency-scaleout.md) | `shard.rs` bus; `Arc<Mutex<Engine>>` deleted; interleaved ids |
|
||||
| ✅ | 09c per-shard WAL | [09](09-concurrency-scaleout.md) | ack-after-fsync; boot replay; `meta` shard guard |
|
||||
| ✅ | — keep-alive follow-up | [09](09-concurrency-scaleout.md) | reads ×3.4 → 770k/s |
|
||||
| ✅ | — io_uring group commit | [09](09-concurrency-scaleout.md) | raw ring; 4.7× durable writes on real disk |
|
||||
| ✅ | 16a PG wire client | [16](16-postgres-mirror.md) | hand-rolled protocol v3, zero crates |
|
||||
| ✅ | 16b PG backup mirror | [16](16-postgres-mirror.md) | async JSONB upserts behind the WAL ack; RAM authoritative |
|
||||
| ✅ | 13a class surface | [13](13-class-model-live-pricing.md) | `class` parses, CRUD serves |
|
||||
| ✅ | 13b method execution | [13](13-class-model-live-pricing.md) | row-scoped txn per call; abort → 409 rollback |
|
||||
| ✅ | — `@table` + indexed DML | [13](13-class-model-live-pricing.md) | secondary indexes, `find_by`, `select Type{…}`, REST filters |
|
||||
| ✅ | C proving ground A–F | [exploration/c-runtime/00-plan.md](exploration/c-runtime/00-plan.md) | 859k reads/s, 618k durable commits/s; found the ack-ordering + fd-ABA bugs the Rust port avoided |
|
||||
|
||||
Ecommerce sample (verified 2026-06-13): `api.rest` 17/17 expected statuses pass.
|
||||
|
||||
---
|
||||
|
||||
## Pending
|
||||
|
||||
### Language track — sequenced, on the critical path
|
||||
|
||||
| # | Item | Plan |
|
||||
| --- | --- | --- |
|
||||
| 5 | Haxe-parity language surface — **`?T` forced handling first**, then switch expressions, records, enum payloads, try/catch, statics, `using`, modules, `is`, `pub(read)`, `#if` | [plan 8](compiler/2026-08-01-haxe-parity-language.md) |
|
||||
| 6 | Program mode + systems stdlib — `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` | [plan 9](../superpowers/plans/2026-08-01-program-mode-stdlib.md) |
|
||||
| 7 | log-watcher proof — the sample compiles and detects a silent death live | [plan 10](../superpowers/plans/2026-08-01-log-watcher-sample.md) |
|
||||
| 8 | Shard-actor runtime | [plan 4](../superpowers/plans/2026-08-01-shard-actor-vm-runtime.md) |
|
||||
| 9 | Database engine binding | [plan 5](../superpowers/plans/2026-08-01-db-engine-binding.md) |
|
||||
| 10 | HTTP service layer | [plan 6](../superpowers/plans/2026-08-01-http-service-layer.md) |
|
||||
| 11 | Fibers | vision §3, [blue-green exploration](exploration/blue-green-vm/00-vision.md) |
|
||||
| 12 | Blue-green deploy | [spec](../superpowers/specs/2026-08-03-blue-green-vm-design.md) — plan authored after iterations 9–10 |
|
||||
|
||||
### Language track — parked until after iteration 12
|
||||
|
||||
Recorded 2026-08-08 by scope directive; nothing here lands before the
|
||||
log-watcher proof.
|
||||
|
||||
- `WO-W201` `@gc`-suggestion refinement beyond the self-reference heuristic
|
||||
- `WO-E225` broadened to `ref`/`multi`/`map` element types and fn signatures
|
||||
- ADT container roster adoption (Stack, Queue, Set, Tree, Graph, …) — see the
|
||||
roster in [`compiler/nullable-types-implementation.md`](compiler/nullable-types-implementation.md)
|
||||
- Web framework as a `.wo` library; UI (`##ui` SSR + live patches);
|
||||
script-based destructive migrations; MCP/agent wrapper over the management plane
|
||||
|
||||
### Rust runtime track — not advancing while the language track runs
|
||||
|
||||
| Status | Phase | Doc | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| ⬜ | 05 hand-rolled JSON | [05](05-hand-rolled-json.md) | removes serde/serde_json |
|
||||
| ⬜ | 06 bespoke error type | [06](06-bespoke-error.md) | removes anyhow |
|
||||
| ⬜ | 07 inotify content watcher | [07](07-inotify-content-watcher.md) | `wo dev` hot reload |
|
||||
| ⬜ | 08 sendfile static assets | [08](08-sendfile-static-assets.md) | needed by the parked UI track |
|
||||
| ⬜ | 09d cross-shard subscriptions | [09](09-concurrency-scaleout.md) | LIVE fan-out; pairs with Stage 3 |
|
||||
| ⬜ | 09e cross-shard transactions (2PC) | [09](09-concurrency-scaleout.md) | needed by `fn checkout` spanning shards |
|
||||
| ⬜ | 09f observability & reshard | [09](09-concurrency-scaleout.md) | per-shard metrics, `WO_RESHARD` |
|
||||
| ⬜ | 10–12 storage completion | [10](10-storage-foundations.md), [11](11-wal-and-recovery.md), [12](12-engine-disk-cutover.md) | snapshots, compaction, WAL rotation, mmap arena engine |
|
||||
| ⬜ | 13c LIVE pricing push · Stage 3 wire layer · 13e at scale | [13](13-class-model-live-pricing.md) | replaces the 501 stub |
|
||||
| ⬜ | 15a–15e MCP over streamable HTTP | [15](15-mcp-streamable-http.md) | 15e needs 13c + 09d |
|
||||
| ⬜ | 16c–16f typed columns, lossless resync, restore, SCRAM | [16](16-postgres-mirror.md) | |
|
||||
|
||||
### Frontend — parked
|
||||
|
||||
| Status | Phase | Doc |
|
||||
| --- | --- | --- |
|
||||
| ⏸ | 13d pricing UI | [13](13-class-model-live-pricing.md) |
|
||||
| ⏸ | 14 MVC UI implementation (14a–f) | [14](14-mvc-ui-implementation.md) |
|
||||
| ⏸ | UI exploration track | [exploration/ui/00-overview.md](exploration/ui/00-overview.md) |
|
||||
|
||||
---
|
||||
|
||||
## Discarded
|
||||
|
||||
Settled rejections with their reasons live in [`discarded.md`](discarded.md) —
|
||||
inheritance, `abstract` newtypes, `Money`/`SKU`/`Float`, `Dynamic`/`cast`/
|
||||
`macro`/`extern`, AOT-to-C, Menhir, shared mutable engine state, external
|
||||
deployer daemon, destructive migrations in v1, and more. Argue against the
|
||||
recorded reason rather than re-opening an entry as new.
|
||||
|
||||
## Learnings
|
||||
|
||||
What attempts taught, shipped or not, in [`learnings.md`](learnings.md) —
|
||||
plumbed-is-not-enforced, vacuously-passing goldens, exit-0-with-wrong-output,
|
||||
the malloc-path ASan trick, deferred checks that never reach the runtime,
|
||||
validate-once-at-the-boundary, and reference-implement-in-C-first.
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# 05 — Hand-Rolled JSON
|
||||
|
||||
> **Kanban: ⬜ not started** — Track 1 (runtime foundations), next in the dependency-removal sequence. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started** — Track 1 (runtime foundations), next in the dependency-removal sequence. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./04-cutover-remove-tokio-axum.md`](./04-cutover-remove-tokio-axum.md), [`../../prototypes/wo-db/src/value.hpp`](../../prototypes/wo-db/src/value.hpp).
|
||||
**Context sources:** [`./04-cutover-remove-tokio-axum.md`](./done/04-cutover-remove-tokio-axum.md), [`../../prototypes/wo-db/src/value.hpp`](../../prototypes/wo-db/src/value.hpp).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 06 — Bespoke Error Type
|
||||
|
||||
> **Kanban: ⬜ not started** — Track 1 (runtime foundations). Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started** — Track 1 (runtime foundations). Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./05-hand-rolled-json.md`](./05-hand-rolled-json.md), [`../01-problem.md`](../01-problem.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# 07 — `inotify` Content Watcher
|
||||
|
||||
> **Kanban: ⬜ not started** — Track 1 (runtime foundations). Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started** — Track 1 (runtime foundations). Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./02-event-loop-epoll.md`](./02-event-loop-epoll.md), [`./linux/00-linux.md`](./linux/00-linux.md) § File Watching, [`../02-recovery.md`](../02-recovery.md) § No AWS Infrastructure.
|
||||
**Context sources:** [`./02-event-loop-epoll.md`](./done/02-event-loop-epoll.md), [`./linux/00-linux.md`](./exploration/linux/00-linux.md) § File Watching, [`../02-recovery.md`](../02-recovery.md) § No AWS Infrastructure.
|
||||
|
||||
## Goal
|
||||
|
||||
First Stage-3 capability. When a `.wo` source file under the active project directory changes, `inotify` fires on the phase-02 event loop and the runtime hot-reloads the affected schema — parser re-run, catalog refreshed, live routes updated in-place. Maps to [`00-linux.md`](./linux/00-linux.md)'s "watch the content directory for file creates, modifications, and deletes. Triggers re-indexing and subscriber notification when articles change. **Replaces the S3 + Lambda event pipeline entirely.**"
|
||||
First Stage-3 capability. When a `.wo` source file under the active project directory changes, `inotify` fires on the phase-02 event loop and the runtime hot-reloads the affected schema — parser re-run, catalog refreshed, live routes updated in-place. Maps to [`00-linux.md`](./exploration/linux/00-linux.md)'s "watch the content directory for file creates, modifications, and deletes. Triggers re-indexing and subscriber notification when articles change. **Replaces the S3 + Lambda event pipeline entirely.**"
|
||||
|
||||
Also the first real second consumer of the phase-02 `EventLoop` beyond the HTTP listener — validates the abstraction under cross-feature load.
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ Also the first real second consumer of the phase-02 `EventLoop` beyond the HTTP
|
|||
| File | Responsibility | Port source |
|
||||
| --- | --- | --- |
|
||||
| `mod.rs` | Re-exports `Watcher`, `WatchEvent` | — |
|
||||
| `inotify.rs` | Raw wrappers: `init()`, `add_watch(path, mask)`, `read_events() -> Vec<RawEvent>`. Registers on the `EventLoop`. | [`reference/crates/wo-watch/src/lib.rs`](../../reference/crates/wo-watch/src/lib.rs) (280 LOC) — v1 already does exactly this |
|
||||
| `inotify.rs` | Raw wrappers: `init()`, `add_watch(path, mask)`, `read_events() -> Vec<RawEvent>`. Registers on the `EventLoop`. | [`reference/crates/wo-watch/src/lib.rs`](../../.dev/reference/crates/wo-watch/src/lib.rs) (280 LOC) — v1 already does exactly this |
|
||||
| `recursive.rs` | Walks the project root, calls `add_watch` for every directory matching `types/\|ui/\|logic/\|tests/` or containing `*.wo` | ~80 new LOC |
|
||||
| `debounce.rs` | Coalesces bursts per-watch-descriptor, fires a `TimerFd` for the 150 ms settle window | ~100 new LOC |
|
||||
| `reload.rs` | On debounced fire: re-discover, re-parse, re-compile, `ArcSwap::store(new_catalog)` | ~80 new LOC |
|
||||
|
|
@ -87,16 +87,16 @@ for event in loop_.wait_once(None)? {
|
|||
# wait 200 ms
|
||||
# observe: `curl :8080/api/articles` response shape reflects new field (no restart)
|
||||
```
|
||||
4. **`[wo]` log lines** match the spec in [00-linux.md](./linux/00-linux.md) — one line per debounced change, showing the relative path and event kind.
|
||||
4. **`[wo]` log lines** match the spec in [00-linux.md](./exploration/linux/00-linux.md) — one line per debounced change, showing the relative path and event kind.
|
||||
5. **All 14 `rt` unit tests still pass.** `reference/rest/blog.rest` 20-assertion battery still green.
|
||||
6. **No fd leak** — `ls -la /proc/$PID/fd` before and after ten consecutive edits shows the same count.
|
||||
|
||||
## Non-scope
|
||||
|
||||
- **No cross-platform fallback.** `kqueue` and `ReadDirectoryChangesW` are not on the roadmap. Linux only.
|
||||
- **No `fanotify`.** [00-linux.md](./linux/00-linux.md) lists it as "useful if watching needs to span mount points" — writeonce projects live in one directory tree; `inotify` is enough.
|
||||
- **No `fanotify`.** [00-linux.md](./exploration/linux/00-linux.md) lists it as "useful if watching needs to span mount points" — writeonce projects live in one directory tree; `inotify` is enough.
|
||||
- **No incremental reparse.** Full recompile per settled change. If a real project hits the full-recompile wall, phase 09+ can add a dependency-graph-aware rebuilder.
|
||||
- **No subscription push.** Phase 07 only detects and reloads. Notifying connected clients (the `register! { #{blog-title} => notify(fd) }` model in [00-linux.md](./linux/00-linux.md)) is phase 09 once `sub` activates.
|
||||
- **No subscription push.** Phase 07 only detects and reloads. Notifying connected clients (the `register! { #{blog-title} => notify(fd) }` model in [00-linux.md](./exploration/linux/00-linux.md)) is phase 09 once `sub` activates.
|
||||
|
||||
## Verification
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# 08 — `sendfile` Zero-Copy Static Serving
|
||||
|
||||
> **Kanban: ⬜ not started** — Track 1 (runtime foundations); also a prerequisite of the parked UI track. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started** — Track 1 (runtime foundations); also a prerequisite of the parked UI track. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./03-hand-rolled-http.md`](./03-hand-rolled-http.md), [`./linux/00-linux.md`](./linux/00-linux.md) § Efficient File Serving, [`../02-recovery.md`](../02-recovery.md).
|
||||
**Context sources:** [`./03-hand-rolled-http.md`](./done/03-hand-rolled-http.md), [`./linux/00-linux.md`](./exploration/linux/00-linux.md) § Efficient File Serving, [`../02-recovery.md`](../02-recovery.md).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ Serve static file bytes — eventually `##ui`-emitted HTML + CSS + JS bundle, to
|
|||
1. **`sendfile(2)` only.** Not `splice`, not `vmsplice`. `sendfile` handles "file fd → socket fd" exactly, which is 100% of the use case here. `splice`-through-pipe is ~30% more code for cases we don't have (non-regular-file sources).
|
||||
2. **`GET /static/...` is the only route mounted.** Hard-coded for Stage 3. When `##ui` arrives in phase 6+ it'll emit bundles into this path; when typed SDK codegen arrives in phase 5 the generated JS client goes here too.
|
||||
3. **No path traversal.** Canonicalise the requested path; reject anything that escapes the configured static root. Standard directory-traversal defence — `..` segments already stripped by the HTTP request parser from phase 03, but the static resolver double-checks with a realpath comparison.
|
||||
4. **`open + fstat + sendfile` chain.** No `mmap`. `mmap` wins for repeated reads of the same file (where the page cache warming pays off), but `sendfile` is strictly faster for one-shot delivery since the kernel manages the page cache itself. [00-linux.md](./linux/00-linux.md) lists both; the runtime's static-asset pattern is one-shot, pick `sendfile`.
|
||||
4. **`open + fstat + sendfile` chain.** No `mmap`. `mmap` wins for repeated reads of the same file (where the page cache warming pays off), but `sendfile` is strictly faster for one-shot delivery since the kernel manages the page cache itself. [00-linux.md](./exploration/linux/00-linux.md) lists both; the runtime's static-asset pattern is one-shot, pick `sendfile`.
|
||||
5. **`EAGAIN` backoff through the event loop.** If `sendfile` returns partial bytes (send buffer full), re-arm `EPOLLOUT` for the socket and resume when the kernel signals writable. Matches v1 wo-serve's flow.
|
||||
6. **MIME by extension table.** Compact `match` on `.html`/`.css`/`.js`/`.json`/`.svg`/`.png`/`.jpg`/`.woff2`/`.wasm` covers every asset the SSR layer will emit. Unknown extensions default to `application/octet-stream`.
|
||||
|
||||
|
|
@ -24,9 +24,9 @@ Serve static file bytes — eventually `##ui`-emitted HTML + CSS + JS bundle, to
|
|||
| File | Responsibility | Port source |
|
||||
| --- | --- | --- |
|
||||
| `mod.rs` | Re-exports `StaticHandler`, `resolve` | — |
|
||||
| `sendfile.rs` | Raw `sendfile(2)` wrapper + non-blocking `send_all` that co-operates with `EPOLLOUT` | [`reference/crates/wo-serve/src/sendfile.rs`](../../reference/crates/wo-serve/src/sendfile.rs) (109 LOC) |
|
||||
| `resolve.rs` | Path canonicalisation + traversal defence + file existence check | [`reference/crates/wo-serve/src/resolve.rs`](../../reference/crates/wo-serve/src/resolve.rs) (80 LOC) |
|
||||
| `mime.rs` | Extension → `Content-Type` table | [`reference/crates/wo-serve/src/mime.rs`](../../reference/crates/wo-serve/src/mime.rs) (44 LOC) |
|
||||
| `sendfile.rs` | Raw `sendfile(2)` wrapper + non-blocking `send_all` that co-operates with `EPOLLOUT` | [`reference/crates/wo-serve/src/sendfile.rs`](../../.dev/reference/crates/wo-serve/src/sendfile.rs) (109 LOC) |
|
||||
| `resolve.rs` | Path canonicalisation + traversal defence + file existence check | [`reference/crates/wo-serve/src/resolve.rs`](../../.dev/reference/crates/wo-serve/src/resolve.rs) (80 LOC) |
|
||||
| `mime.rs` | Extension → `Content-Type` table | [`reference/crates/wo-serve/src/mime.rs`](../../.dev/reference/crates/wo-serve/src/mime.rs) (44 LOC) |
|
||||
| `handler.rs` | `StaticHandler` — integrates the three with phase-03's `Response` builder; returns 404 / 403 / 200 as appropriate | ~120 new LOC |
|
||||
|
||||
Total: ~350 LOC (233 ported + ~120 new).
|
||||
|
|
@ -103,8 +103,8 @@ cd reference/crates && cargo build && cargo test # v1 untouched
|
|||
|
||||
## After this phase
|
||||
|
||||
The runtime covers every kernel primitive listed in [`00-linux.md`](./linux/00-linux.md) except `io_uring`, `mmap`, `fallocate`, and `memfd_create` — which all belong to the storage engine (phase 3 of the database series), not the runtime per se.
|
||||
The runtime covers every kernel primitive listed in [`00-linux.md`](./exploration/linux/00-linux.md) except `io_uring`, `mmap`, `fallocate`, and `memfd_create` — which all belong to the storage engine (phase 3 of the database series), not the runtime per se.
|
||||
|
||||
Next natural phase: **`09-native-subscriptions.md`** — the `register! { #{blog-title} => notify(fd) }` model from [00-linux.md](./linux/00-linux.md). Takes the `inotify` watcher from phase 07 and wires it into a subscription table that dispatches delta writes directly to subscriber sockets over the phase-03 HTTP connection. That replaces the Stage-3 `501` stub the `/api/<type>/live` endpoint currently returns.
|
||||
Next natural phase: **`09-native-subscriptions.md`** — the `register! { #{blog-title} => notify(fd) }` model from [00-linux.md](./exploration/linux/00-linux.md). Takes the `inotify` watcher from phase 07 and wires it into a subscription table that dispatches delta writes directly to subscriber sockets over the phase-03 HTTP connection. That replaces the Stage-3 `501` stub the `/api/<type>/live` endpoint currently returns.
|
||||
|
||||
After that phase, `crates/rt/` is feature-complete for Stages 1–3 of the runtime, with exactly one external dependency.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# 09 — Scale-out: thread-per-core for 10k concurrent users
|
||||
|
||||
> **Kanban: 🔄 in progress** — 09a/09b/09c ✅ shipped (+ keep-alive and io_uring group-commit follow-ups, measured in the shipped notes below); 09d/09e/09f ⬜ not started. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: 🔄 in progress** — 09a/09b/09c ✅ shipped (+ keep-alive and io_uring group-commit follow-ups, measured in the shipped notes below); 09d/09e/09f ⬜ not started. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./08-sendfile-static-assets.md`](./08-sendfile-static-assets.md) (last single-threaded phase), [`./assembly/02-writeonce-stance.md`](./assembly/02-writeonce-stance.md) (the "single-threaded" policy we're now refining), [`../runtime/database/02-wo-language.md#concurrency-model`](../runtime/database/02-wo-language.md#concurrency-model) (original concurrency stance), [`docs/examples/ecommerce/`](../examples/ecommerce/) (the target workload), [`./linux/`](./linux/) (kernel primitives), [`reference/go/src/runtime/`](../../reference/go/src/runtime/) (precedent for a runtime that scales across threads).
|
||||
**Context sources:** [`./08-sendfile-static-assets.md`](./08-sendfile-static-assets.md) (last single-threaded phase), [`./assembly/02-writeonce-stance.md`](./exploration/assembly/02-writeonce-stance.md) (the "single-threaded" policy we're now refining), [`../runtime/database/02-wo-language.md#concurrency-model`](../runtime/database/02-wo-language.md#concurrency-model) (original concurrency stance), [`docs/examples/ecommerce/`](../examples/ecommerce/) (the target workload), [`./linux/`](./exploration/linux/) (kernel primitives), [`reference/go/src/runtime/`](../../.dev/reference/go/src/runtime/) (precedent for a runtime that scales across threads).
|
||||
|
||||
## Context
|
||||
|
||||
|
|
@ -18,23 +18,23 @@ Serve the ecommerce sample at 10,000 concurrent websocket subscribers + 1,000 ch
|
|||
|
||||
## Design decisions (locked)
|
||||
|
||||
1. **Thread-per-core, not M:N.** `N` OS threads pinned to `N` cores via `sched_setaffinity(cpu_set_t)`. Each thread runs its own event loop (the [phase-02 `runtime/` module](./02-event-loop-epoll.md)) plus a local shard of engine state. Pinned for the thread's lifetime; a connection accepted on thread K stays on thread K forever. Precedent: Seastar / ScyllaDB / Redis Cluster.
|
||||
2. **Shared-nothing state.** No cross-thread mutable access to the catalog, engine rows, or subscription registry. Communication is message-passing over single-producer-single-consumer ring buffers (crossbeam-style, built on `std::sync::atomic`, per [`./assembly/02-writeonce-stance.md`](./assembly/02-writeonce-stance.md) — still no asm). If thread A needs to touch data owned by thread B, it sends a message; B processes it on its own tick.
|
||||
1. **Thread-per-core, not M:N.** `N` OS threads pinned to `N` cores via `sched_setaffinity(cpu_set_t)`. Each thread runs its own event loop (the [phase-02 `runtime/` module](./done/02-event-loop-epoll.md)) plus a local shard of engine state. Pinned for the thread's lifetime; a connection accepted on thread K stays on thread K forever. Precedent: Seastar / ScyllaDB / Redis Cluster.
|
||||
2. **Shared-nothing state.** No cross-thread mutable access to the catalog, engine rows, or subscription registry. Communication is message-passing over single-producer-single-consumer ring buffers (crossbeam-style, built on `std::sync::atomic`, per [`./assembly/02-writeonce-stance.md`](./exploration/assembly/02-writeonce-stance.md) — still no asm). If thread A needs to touch data owned by thread B, it sends a message; B processes it on its own tick.
|
||||
3. **SO_REUSEPORT for listener-side load balancing.** Every thread binds a socket with `SO_REUSEPORT` on the same `:8080` — the kernel distributes incoming SYNs across the `N` listener sockets with consistent hashing on the connection 4-tuple. No user-space accept-thread bottleneck. Linux ≥ 3.9 is fine; ≥ 4.5 adds `BPF` filters for custom routing if we ever need session-affinity.
|
||||
4. **Per-thread io_uring ring.** Each thread gets its own `io_uring_setup` ring with `IORING_SETUP_SINGLE_ISSUER` + `IORING_SETUP_SQPOLL` ([per `./linux/07-io_uring.md`](./linux/07-io_uring.md)). No ring sharing across threads — simpler ordering, no contention.
|
||||
4. **Per-thread io_uring ring.** Each thread gets its own `io_uring_setup` ring with `IORING_SETUP_SINGLE_ISSUER` + `IORING_SETUP_SQPOLL` ([per `./linux/07-io_uring.md`](./exploration/linux/07-io_uring.md)). No ring sharing across threads — simpler ordering, no contention.
|
||||
5. **Shard key: customer id (modulo N).** The ecommerce schema is customer-centric — one customer's orders + purchase edges + cart live on the same shard. Cross-customer queries (admin `list orders`) fan out; same-customer operations (checkout) are local. Blog shard key would be `author.id` for the same reason.
|
||||
6. **Cross-shard transactions via 2PC.** A checkout that updates inventory on shard A and customer balance on shard B uses two-phase commit between the two engine threads. Phase 4's transaction coordinator (from [`../runtime/database/02-wo-language.md`](../runtime/database/02-wo-language.md) § Cross-Paradigm Transaction Coordinator) already handles this pattern for sql+doc+graph inside one process; it generalises cleanly to cross-thread.
|
||||
7. **No Go-style goroutines.** Connections are not tasks that migrate. Each connection's state machine runs on its owning thread's event loop, just as it does in the single-threaded model — the difference is there are now `N` event loops running concurrently.
|
||||
|
||||
## What we copy from Go, what we don't
|
||||
|
||||
Read [`reference/go/src/runtime/netpoll_epoll.go`](../../reference/go/src/runtime/netpoll_epoll.go) and [`reference/go/src/runtime/proc.go`](../../reference/go/src/runtime/proc.go) for the shape; copy the **ideas** about fd-to-loop mapping and atomic-counter-based wake-up. Do **not** copy:
|
||||
Read [`reference/go/src/runtime/netpoll_epoll.go`](../../.dev/reference/go/src/runtime/netpoll_epoll.go) and [`reference/go/src/runtime/proc.go`](../../.dev/reference/go/src/runtime/proc.go) for the shape; copy the **ideas** about fd-to-loop mapping and atomic-counter-based wake-up. Do **not** copy:
|
||||
|
||||
| Go feature | Why writeonce skips it |
|
||||
| --- | --- |
|
||||
| Goroutines (M:N scheduling, work stealing) | Goroutines pay context-switch + GC-scan costs the thread-per-core model avoids. Scylla benchmarks consistently beat Go-style runtimes at the same hardware. |
|
||||
| Shared heap + GC | No heap GC — Rust ownership. Data is partitioned across threads, not shared with locks. |
|
||||
| `gogo` / `mcall` / `systemstack` asm | No scheduler-controlled stack switching. See [`./assembly/02-writeonce-stance.md`](./assembly/02-writeonce-stance.md). |
|
||||
| `gogo` / `mcall` / `systemstack` asm | No scheduler-controlled stack switching. See [`./assembly/02-writeonce-stance.md`](./exploration/assembly/02-writeonce-stance.md). |
|
||||
| `cgo` boundary | Rust is the only language. `libc` is already ABI-compatible via `extern "C"`. |
|
||||
| `asyncPreempt` preemption | Handlers run to completion on their owning thread. Back-pressure comes from bounded per-thread queues, not preemption. |
|
||||
|
||||
|
|
@ -43,8 +43,8 @@ And what we **do** copy:
|
|||
| Go pattern | Writeonce translation |
|
||||
| --- | --- |
|
||||
| Per-P netpoller (the `pp.pollDesc` model) | Per-thread `EventLoop` (the phase-02 `runtime::EventLoop`) |
|
||||
| `netpollBreak` (fd wake-up via sendto) | Per-thread `eventfd` — one fd per thread, write to it to wake a sleeping `epoll_wait`. See [`./linux/02-eventfd.md`](./linux/02-eventfd.md). |
|
||||
| `findrunnable` (what to do when idle) | Per-thread idle-state: drain in-process message queues, run compaction, run periodic timers (from [`./linux/03-timerfd.md`](./linux/03-timerfd.md)). |
|
||||
| `netpollBreak` (fd wake-up via sendto) | Per-thread `eventfd` — one fd per thread, write to it to wake a sleeping `epoll_wait`. See [`./linux/02-eventfd.md`](./exploration/linux/02-eventfd.md). |
|
||||
| `findrunnable` (what to do when idle) | Per-thread idle-state: drain in-process message queues, run compaction, run periodic timers (from [`./linux/03-timerfd.md`](./exploration/linux/03-timerfd.md)). |
|
||||
| `runtime.GOMAXPROCS` | `WO_THREADS` env var (defaults to `std::thread::available_parallelism()`). |
|
||||
|
||||
## Linux primitives this phase leans on (beyond the phase-02/03/08 set)
|
||||
|
|
@ -53,13 +53,13 @@ Reference cards already exist for most; this phase adds the ones that are cross-
|
|||
|
||||
| Primitive | Use | Reference |
|
||||
| --- | --- | --- |
|
||||
| `SO_REUSEPORT` | N listener sockets on the same port; kernel load-balances accepts | [`reference/linux/net/core/sock_reuseport.c`](../../reference/linux/net/core/sock_reuseport.c) — worth adding `linux/12-so-reuseport.md` |
|
||||
| `sched_setaffinity` + `cpu_set_t` | Pin each thread to its core | [`reference/linux/kernel/sched/core.c`](../../reference/linux/kernel/sched/core.c) |
|
||||
| `futex(2)` | Fallback cross-thread wait if per-thread eventfd wake-up isn't enough | [`reference/linux/kernel/futex/`](../../reference/linux/kernel/futex/) — worth `linux/13-futex.md` |
|
||||
| `membarrier(2)` | Process-wide memory barrier when a rebalance migrates state between threads | [`reference/linux/kernel/sched/membarrier.c`](../../reference/linux/kernel/sched/membarrier.c) |
|
||||
| `io_uring` with `IORING_SETUP_SINGLE_ISSUER` | One ring per thread, pinned | [`./linux/07-io_uring.md`](./linux/07-io_uring.md) |
|
||||
| `eventfd` per thread | Cross-thread wake-up — thread A writes to thread B's eventfd to deliver a message | [`./linux/02-eventfd.md`](./linux/02-eventfd.md) |
|
||||
| `mmap(MAP_HUGETLB)` | Per-thread arena allocator backed by 2 MB pages for cache locality | [`./linux/08-mmap.md`](./linux/08-mmap.md) |
|
||||
| `SO_REUSEPORT` | N listener sockets on the same port; kernel load-balances accepts | [`reference/linux/net/core/sock_reuseport.c`](../../.dev/reference/linux/net/core/sock_reuseport.c) — worth adding `linux/12-so-reuseport.md` |
|
||||
| `sched_setaffinity` + `cpu_set_t` | Pin each thread to its core | [`reference/linux/kernel/sched/core.c`](../../.dev/reference/linux/kernel/sched/core.c) |
|
||||
| `futex(2)` | Fallback cross-thread wait if per-thread eventfd wake-up isn't enough | [`reference/linux/kernel/futex/`](../../.dev/reference/linux/kernel/futex/) — worth `linux/13-futex.md` |
|
||||
| `membarrier(2)` | Process-wide memory barrier when a rebalance migrates state between threads | [`reference/linux/kernel/sched/membarrier.c`](../../.dev/reference/linux/kernel/sched/membarrier.c) |
|
||||
| `io_uring` with `IORING_SETUP_SINGLE_ISSUER` | One ring per thread, pinned | [`./linux/07-io_uring.md`](./exploration/linux/07-io_uring.md) |
|
||||
| `eventfd` per thread | Cross-thread wake-up — thread A writes to thread B's eventfd to deliver a message | [`./linux/02-eventfd.md`](./exploration/linux/02-eventfd.md) |
|
||||
| `mmap(MAP_HUGETLB)` | Per-thread arena allocator backed by 2 MB pages for cache locality | [`./linux/08-mmap.md`](./exploration/linux/08-mmap.md) |
|
||||
|
||||
## Sub-phase sequence
|
||||
|
||||
|
|
@ -130,9 +130,9 @@ If the "single core per process, shard across processes" argument ([Redis Cluste
|
|||
|
||||
- [`./exploration/c-runtime/00-plan.md`](./exploration/c-runtime/00-plan.md) — the C prototype's phased evolution (threads → arena → io_uring → WAL → recovery); the executable proving ground for 09a's thread-per-core skeleton and 09c's per-shard WAL before the Rust work starts.
|
||||
- [`./08-sendfile-static-assets.md`](./08-sendfile-static-assets.md) — last prerequisite phase; feature-complete single-threaded runtime.
|
||||
- [`./assembly/02-writeonce-stance.md`](./assembly/02-writeonce-stance.md) — updated to reference this phase's thread-per-core model; still no asm.
|
||||
- [`./assembly/02-writeonce-stance.md`](./exploration/assembly/02-writeonce-stance.md) — updated to reference this phase's thread-per-core model; still no asm.
|
||||
- [`../runtime/database/02-wo-language.md#concurrency-model`](../runtime/database/02-wo-language.md#concurrency-model) — the stance this plan refines.
|
||||
- [`reference/go/src/runtime/proc.go`](../../reference/go/src/runtime/proc.go) — Go's scheduler, for contrast.
|
||||
- [`reference/go/src/runtime/netpoll_epoll.go`](../../reference/go/src/runtime/netpoll_epoll.go) — per-P netpoller, the idea we borrow.
|
||||
- [`reference/linux/net/core/sock_reuseport.c`](../../reference/linux/net/core/sock_reuseport.c) — kernel load balancer.
|
||||
- [`reference/linux/kernel/sched/core.c`](../../reference/linux/kernel/sched/core.c) — affinity syscalls.
|
||||
- [`reference/go/src/runtime/proc.go`](../../.dev/reference/go/src/runtime/proc.go) — Go's scheduler, for contrast.
|
||||
- [`reference/go/src/runtime/netpoll_epoll.go`](../../.dev/reference/go/src/runtime/netpoll_epoll.go) — per-P netpoller, the idea we borrow.
|
||||
- [`reference/linux/net/core/sock_reuseport.c`](../../.dev/reference/linux/net/core/sock_reuseport.c) — kernel load balancer.
|
||||
- [`reference/linux/kernel/sched/core.c`](../../.dev/reference/linux/kernel/sched/core.c) — affinity syscalls.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# 10 — Storage Foundations: on-disk row codec + segment append path
|
||||
|
||||
> **Kanban: ⬜ not started (scope reduced)** — WAL framing/fallocate/CRC landed early via plan 09c; the `@table(name:, index:)` storage-config surface and in-RAM secondary indexes (`Engine::find_by`) landed via the plan-13 follow-up (spec: [`02-wo-language.md § Type-Level Annotations`](../runtime/database/02-wo-language.md)) — this plan inherits the surface and gives indexes their on-disk form. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started (scope reduced)** — WAL framing/fallocate/CRC landed early via plan 09c; the `@table(name:, index:)` storage-config surface and in-RAM secondary indexes (`Engine::find_by`) landed via the plan-13 follow-up (spec: [`02-wo-language.md § Type-Level Annotations`](../runtime/database/02-wo-language.md)) — this plan inherits the surface and gives indexes their on-disk form. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./done/04-cutover-remove-tokio-axum.md`](./done/04-cutover-remove-tokio-axum.md), [`../runtime/database/03-inmemory-engine.md`](../runtime/database/03-inmemory-engine.md), [`../runtime/database/07-wo-seg-migration.md`](../runtime/database/07-wo-seg-migration.md), [`./exploration/postgresql/smgr-and-md.md`](./exploration/postgresql/smgr-and-md.md), [`./exploration/postgresql/page-format.md`](./exploration/postgresql/page-format.md), [`./exploration/linux/12-pwrite-fsync.md`](./exploration/linux/12-pwrite-fsync.md), [`./exploration/linux/09-fallocate.md`](./exploration/linux/09-fallocate.md), [`reference/crates/wo-seg/src/`](../../reference/crates/wo-seg/src/).
|
||||
**Context sources:** [`./done/04-cutover-remove-tokio-axum.md`](./done/04-cutover-remove-tokio-axum.md), [`../runtime/database/03-inmemory-engine.md`](../runtime/database/03-inmemory-engine.md), [`../runtime/database/07-wo-seg-migration.md`](../runtime/database/07-wo-seg-migration.md), [`./exploration/postgresql/smgr-and-md.md`](./exploration/postgresql/smgr-and-md.md), [`./exploration/postgresql/page-format.md`](./exploration/postgresql/page-format.md), [`./exploration/linux/12-pwrite-fsync.md`](./exploration/linux/12-pwrite-fsync.md), [`./exploration/linux/09-fallocate.md`](./exploration/linux/09-fallocate.md), [`reference/crates/wo-seg/src/`](../../.dev/reference/crates/wo-seg/src/).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ Lays the codec + filesystem layout that phase 11 (WAL + recovery) and phase 12 (
|
|||
| `codec.rs` | `trait RowCodec { fn encode(&self, row: &Row, buf: &mut Vec<u8>); fn decode(&self, bytes: &[u8]) -> Result<Row>; }` + `JsonCodec` impl backed by today's `serde_json`. | ~50 |
|
||||
| `seg.rs` | `SegStore { dir: PathBuf, fds: HashMap<String, RawFd>, tails: HashMap<String, u64> }`. `open(dir)`, `append(ty, &Row) -> Result<u64-offset>`, `read(ty, offset) -> Result<Row>` (used by phase 11 recovery, not by the engine yet). | ~250 |
|
||||
|
||||
Total: ~560 LOC. The framing math + fallocate + pwrite plumbing is ported from [`reference/crates/wo-seg/src/{writer.rs,reader.rs,header.rs}`](../../reference/crates/wo-seg/src/) with the CRC trailer added.
|
||||
Total: ~560 LOC. The framing math + fallocate + pwrite plumbing is ported from [`reference/crates/wo-seg/src/{writer.rs,reader.rs,header.rs}`](../../.dev/reference/crates/wo-seg/src/) with the CRC trailer added.
|
||||
|
||||
### File layout written under `<wo_run_dir>/`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 11 — WAL + crash recovery
|
||||
|
||||
> **Kanban: ⬜ not started (scope reduced)** — replay + ack-after-fsync + group commit landed via 09c and its follow-ups; remaining here: snapshots (`.data`), compaction, WAL rotation. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started (scope reduced)** — replay + ack-after-fsync + group commit landed via 09c and its follow-ups; remaining here: snapshots (`.data`), compaction, WAL rotation. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./10-storage-foundations.md`](./10-storage-foundations.md), [`../runtime/database/02-wo-language.md#concurrency-model`](../runtime/database/02-wo-language.md#concurrency-model), [`../runtime/database/03-inmemory-engine.md`](../runtime/database/03-inmemory-engine.md), [`./exploration/postgresql/wal.md`](./exploration/postgresql/wal.md), [`./exploration/postgresql/buffer-and-checkpoint.md`](./exploration/postgresql/buffer-and-checkpoint.md), [`./exploration/linux/12-pwrite-fsync.md`](./exploration/linux/12-pwrite-fsync.md), [`../02-recovery.md`](../02-recovery.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 12 — Engine cutover: rows live on disk
|
||||
|
||||
> **Kanban: ⬜ not started** — the C prototype's phase B (mmap arena) is the proving ground. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started** — the C prototype's phase B (mmap arena) is the proving ground. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./10-storage-foundations.md`](./10-storage-foundations.md), [`./11-wal-and-recovery.md`](./11-wal-and-recovery.md), [`../runtime/database/03-inmemory-engine.md`](../runtime/database/03-inmemory-engine.md), [`../runtime/database/07-wo-seg-migration.md`](../runtime/database/07-wo-seg-migration.md), [`./exploration/postgresql/buffer-and-checkpoint.md`](./exploration/postgresql/buffer-and-checkpoint.md), [`./exploration/postgresql/page-format.md`](./exploration/postgresql/page-format.md), [`./exploration/linux/12-pwrite-fsync.md`](./exploration/linux/12-pwrite-fsync.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 13 — Class model + live pricing: state and methods, no inheritance
|
||||
|
||||
> **Kanban: 🔄 in progress** — 13a ✅ shipped; 13b ✅ shipped (methods execute over RPC); 13c (LIVE push) is next; 13d ⏸ parked (frontend); 13e ⬜. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: 🔄 in progress** — 13a ✅ shipped; 13b ✅ shipped (methods execute over RPC); 13c (LIVE push) is next; 13d ⏸ parked (frontend); 13e ⬜. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`../runtime/database/02-wo-language.md`](../runtime/database/02-wo-language.md) (schema layer, § Schema-Layer DML brace disambiguation, § Cross-Paradigm Transaction Coordinator), [`../runtime/database/04-client-api.md`](../runtime/database/04-client-api.md) (subscription engine), [`./09-concurrency-scaleout.md`](./09-concurrency-scaleout.md) (thread-per-core scale-out), [`./exploration/ui/00-overview.md`](./exploration/ui/00-overview.md) + [`./exploration/ui/01-htmlx-format-spec.md`](./exploration/ui/01-htmlx-format-spec.md) (live UI), [`../examples/pricing/`](../examples/pricing/) (the demo this phase makes real), [`../examples/ecommerce/shared/logic/checkout.wo`](../examples/ecommerce/shared/logic/checkout.wo) (the existing `fn … in txn snapshot` signature style methods reuse).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# 14 — MVC UI implementation: model = class, view = htmlx + scss, controller = .wo
|
||||
|
||||
> **Kanban: ⏸ parked (frontend)** — backend focus first; design stays current. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⏸ parked (frontend)** — backend focus first; design stays current. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`./exploration/ui/08-mvc-structure.md`](./exploration/ui/08-mvc-structure.md) (the design this plan implements), [`./exploration/ui/01-htmlx-format-spec.md`](./exploration/ui/01-htmlx-format-spec.md) / [`02-ui-compiler.md`](./exploration/ui/02-ui-compiler.md) / [`03-client-runtime.md`](./exploration/ui/03-client-runtime.md) (the three UI-track pieces this plan sequences, each with port sources and LOC budgets), [`./13-class-model-live-pricing.md`](./13-class-model-live-pricing.md) (the class methods controllers call: 13a/13b; the LIVE deltas views consume: 13c), [`../examples/pricing/ui/pricing/`](../examples/pricing/ui/pricing/) (the reference MVC triplet), [`reference/crates/wo-htmlx/`](../../reference/crates/wo-htmlx/) (the v1 template engine, primary port source).
|
||||
**Context sources:** [`./exploration/ui/08-mvc-structure.md`](./exploration/ui/08-mvc-structure.md) (the design this plan implements), [`./exploration/ui/01-htmlx-format-spec.md`](./exploration/ui/01-htmlx-format-spec.md) / [`02-ui-compiler.md`](./exploration/ui/02-ui-compiler.md) / [`03-client-runtime.md`](./exploration/ui/03-client-runtime.md) (the three UI-track pieces this plan sequences, each with port sources and LOC budgets), [`./13-class-model-live-pricing.md`](./13-class-model-live-pricing.md) (the class methods controllers call: 13a/13b; the LIVE deltas views consume: 13c), [`../examples/pricing/ui/pricing/`](../examples/pricing/ui/pricing/) (the reference MVC triplet), [`reference/crates/wo-htmlx/`](../../.dev/reference/crates/wo-htmlx/) (the v1 template engine, primary port source).
|
||||
|
||||
## Context
|
||||
|
||||
|
|
@ -86,5 +86,5 @@ Execute [`exploration/ui/03-client-runtime.md`](./exploration/ui/03-client-runti
|
|||
- [`./exploration/ui/08-mvc-structure.md`](./exploration/ui/08-mvc-structure.md) — the design; its exit criteria are satisfied by 14c/14b/14f respectively.
|
||||
- [`./13-class-model-live-pricing.md`](./13-class-model-live-pricing.md) — 13a/13b gate 14e; 13c gates 14f; 13d's exit criterion is this plan's end-to-end target.
|
||||
- [`./exploration/ui/00-overview.md`](./exploration/ui/00-overview.md) — the UI track's master frame (per-app binaries, shared DB daemon) that 14d's asset/serving choices stay compatible with.
|
||||
- [`reference/crates/wo-htmlx/`](../../reference/crates/wo-htmlx/) — primary port source (585 LOC), per ui/01.
|
||||
- [`reference/crates/wo-htmlx/`](../../.dev/reference/crates/wo-htmlx/) — primary port source (585 LOC), per ui/01.
|
||||
- [`../examples/pricing/ui/pricing/`](../examples/pricing/ui/pricing/) — the reference triplet every sub-phase tests against.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# 15 — MCP over Streamable HTTP: every writeonce app is an MCP server
|
||||
|
||||
> **Kanban: ⬜ not started (Track 4 — Language & API)** — board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: ⬜ not started (Track 4 — Language & API)** — board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [MCP specification 2025-06-18 — Transports](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) (the normative Streamable HTTP contract this plan implements, verified 2026-07-12), [`reference/mcp-python-sdk/`](../../reference/README.md) (symlink to the official MCP Python SDK — grep `src/mcp/server/streamable_http.py` + `streamable_http_manager.py` for the reference server behaviour, `src/mcp/client/streamable_http.py` for what a conforming client expects; behaviour is ported, code is not), [`../runtime/database/04-client-api.md`](../runtime/database/04-client-api.md) (the wire-protocol design; its "REST + SSE gateway" row is what this plan makes concrete for agents), [`./13-class-model-live-pricing.md`](./13-class-model-live-pricing.md) (13b methods become MCP tools; 13c's subscription registry carries 15e), [`./09-concurrency-scaleout.md`](./09-concurrency-scaleout.md) (thread-per-core + shard bus the endpoint rides; 09d fan-out gates 15e), `crates/rt/src/server.rs` + `crates/rt/src/http/` (the keep-alive HTTP layer and router this lands in).
|
||||
**Context sources:** [MCP specification 2025-06-18 — Transports](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) (the normative Streamable HTTP contract this plan implements, verified 2026-07-12), [`reference/mcp-python-sdk/`](../../.dev/reference/README.md) (symlink to the official MCP Python SDK — grep `src/mcp/server/streamable_http.py` + `streamable_http_manager.py` for the reference server behaviour, `src/mcp/client/streamable_http.py` for what a conforming client expects; behaviour is ported, code is not), [`../runtime/database/04-client-api.md`](../runtime/database/04-client-api.md) (the wire-protocol design; its "REST + SSE gateway" row is what this plan makes concrete for agents), [`./13-class-model-live-pricing.md`](./13-class-model-live-pricing.md) (13b methods become MCP tools; 13c's subscription registry carries 15e), [`./09-concurrency-scaleout.md`](./09-concurrency-scaleout.md) (thread-per-core + shard bus the endpoint rides; 09d fan-out gates 15e), `crates/rt/src/server.rs` + `crates/rt/src/http/` (the keep-alive HTTP layer and router this lands in).
|
||||
|
||||
## Context
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ The rules the sub-phases implement, condensed from the spec — each MUST below
|
|||
- **Tool generation**: per exposed type×op → `<type>_list`, `<type>_get`, `<type>_create`, `<type>_update`, `<type>_delete`, with `inputSchema` (JSON Schema) derived from catalog field types (unions → `enum`, embedded structs → nested `object`) — same source of truth as `describe_routes`.
|
||||
- **`tools/call` dispatch** through the *same* handler paths REST uses: creates local, point ops `run_on(owner_of(id))`, lists fan out — no second data path. Engine/validation failures return `isError: true` inside the tool *result* (the MCP rule: execution errors are results, protocol errors are JSON-RPC errors). Mutations park on the WAL gate (decision 4).
|
||||
|
||||
**Exit:** scripted flow (checked in beside [`reference/rest/`](../../reference/rest/README.md)) against the blog sample passes: `initialize` → `202` for `initialized` → `tools/list` enumerates exactly the exposed ops → `article_create` → `article_list` shows the row; runs green with `WO_GROUP_COMMIT` on and off; `GET`→405, `DELETE`→405, bad version→400, disallowed Origin→403; unit tests in the `server.rs` style cover envelope errors and gate parking.
|
||||
**Exit:** scripted flow (checked in beside [`reference/rest/`](../../.dev/reference/rest/README.md)) against the blog sample passes: `initialize` → `202` for `initialized` → `tools/list` enumerates exactly the exposed ops → `article_create` → `article_list` shows the row; runs green with `WO_GROUP_COMMIT` on and off; `GET`→405, `DELETE`→405, bad version→400, disallowed Origin→403; unit tests in the `server.rs` style cover envelope errors and gate parking.
|
||||
|
||||
### `15b-resources.md` — the schema and rows become addressable
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 16 — PostgreSQL mirror: RAM-authoritative database, Postgres as the backup
|
||||
|
||||
> **Kanban: 🔄 in progress (Track 3 — Storage & durability)** — 16a ✅, 16b ✅ shipped; 16c–16f ⬜. Board: [00-kanban.md](00-kanban.md)
|
||||
> **Status: 🔄 in progress (Track 3 — Storage & durability)** — 16a ✅, 16b ✅ shipped; 16c–16f ⬜. Board: [00-status.md](../00-status.md)
|
||||
|
||||
**Context sources:** [`README.md` § persistent database](../../README.md) (the product goal this implements: *"reads and writes database to RAM, persist data to postgres SQL"*), [`../runtime/database/03-inmemory-engine.md`](../runtime/database/03-inmemory-engine.md) (RAM-resident doctrine: disk sits behind the read path, never in front), [`./09-concurrency-scaleout.md`](./09-concurrency-scaleout.md) (per-shard WAL + ack-after-fsync this rides behind), [`./13-class-model-live-pricing.md`](./13-class-model-live-pricing.md) (the Product/Price worked example; `@table(name: "prices")` names the mirrored table), [`../runtime/database/07-wo-seg-migration.md`](../runtime/database/07-wo-seg-migration.md) (the dual-write precedent), `reference/postgresql/` (research symlink — `src/include/libpq/` for the wire protocol), PostgreSQL docs *Frontend/Backend Protocol*.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
>
|
||||
> **Style rule (user convention):** concept, reason, and required behavior in words only; the executor writes the code.
|
||||
|
||||
**Goal:** Plan 8 — implement every **adopt** row of the systems-track spec's Haxe keyword verdict table: the language grows switch expressions, records, optionals, try/catch/throw, enum payloads, static members, using-extensions, modules, `is`, `pub(read)`, build flags, and interpolation — with the reject rows enforced as diagnostics. (`abstract` was an adopt row until 2026-08-10; it is now a reject row — see Task 7.)
|
||||
**Goal:** Plan 8 — implement every **adopt** row of the systems-track spec's Haxe keyword verdict table: the language grows boolean operators (`and`/`or`), switch expressions, records, optionals, try/catch, enum payloads, static members, using-extensions, modules, `pub(read)`, build flags, and interpolation — with the reject rows enforced as diagnostics. (`abstract` was an adopt row until 2026-08-10; it is now a reject row. `is` was cut the same day — 0 uses in the driving workload, parked post-iteration-12 — emptying this plan's old Task 7, which is deleted rather than deferred.)
|
||||
|
||||
**Architecture:** Plan 8 of the roadmap. Depends on OOP plans 1–3 (woc + wovm + corpus). Overwhelmingly compiler work in `compiler/src/`; the VM changes are exactly three, called out in their tasks: catch frames (try/catch), variant objects (enum payloads), and boxed optionals for scalars. Everything else lowers onto existing opcodes. The spec's verdict table (`docs/superpowers/specs/2026-08-01-systems-track-design.md` Part 1) is normative — this plan sequences it.
|
||||
|
||||
|
|
@ -35,19 +35,19 @@ docs/plan/oop-vm/00-wob-format.md grows with the three VM changes
|
|||
|
||||
### Task 1: Modules — `use` + directory-as-module
|
||||
|
||||
**Concept & reason:** foundation first: later tasks and the whole stdlib import through it. A `.wo` file's module is its directory; `use fs` (stdlib namespace) or `use shared/util` (project-relative) brings a module's public names into scope. Symbol resolution goes file → module → used modules → stdlib; collisions diagnose rather than shadow silently. Public means `pub`-marked (this task adds the `pub` marker for declarations; field accessors come in Task 8). Unused `use` warns. The stdlib namespaces resolve even though their members arrive in plan 9 — the resolver knows reserved namespace names now so plan 9 slots in without resolver changes.
|
||||
**Concept & reason:** foundation first: later tasks and the whole stdlib import through it. A `.wo` file's module is its directory; `use fs` (stdlib namespace) or `use shared/util` (project-relative) brings a module's public names into scope. Symbol resolution goes file → module → used modules → stdlib; collisions diagnose rather than shadow silently. Public means `pub`-marked (this task adds the `pub` marker for declarations; field accessors come in Task 7). Unused `use` warns. The stdlib namespaces resolve even though their members arrive in plan 9 — the resolver knows reserved namespace names now so plan 9 slots in without resolver changes.
|
||||
|
||||
- [ ] Failing fixtures: cross-module call via `use`; collision diagnostic; private-name-access diagnostic; unused-use warning golden.
|
||||
- [ ] Implement resolver + `pub`; corpus green; catalog entries.
|
||||
- [ ] Record commit draft: `feat(compiler): module system — directory-as-module, use resolution with collision/privacy diagnostics, pub marker, reserved stdlib namespaces.`
|
||||
|
||||
### Task 2: Small control surface — `break`/`continue`, `do…while`, interpolation, `const`
|
||||
### Task 2: Small control surface — `break`/`continue`, `do…while`, interpolation, `const`, `and`/`or`
|
||||
|
||||
**Concept & reason:** the low-risk parity gaps, batched because each is a lexer/parser/emit touch with no typing subtlety. Loop control lowers to jumps with correct drop-set handling at early exits (the owner pass already computes scope-end drops for `return`; `break`/`continue` reuse that machinery — the one non-trivial bit, and its fixture proves an owned value dropped on `break`). String interpolation desugars to concatenation at parse time. `const NAME = literal` declares compile-time values usable in expressions and `#if`-adjacent contexts; `inline`-function requests are rejected with the table's reason.
|
||||
**Concept & reason:** the low-risk parity gaps, batched because each is a lexer/parser/emit touch with no typing subtlety. Loop control lowers to jumps with correct drop-set handling at early exits (the owner pass already computes scope-end drops for `return`; `break`/`continue` reuse that machinery — the one non-trivial bit, and its fixture proves an owned value dropped on `break`). String interpolation desugars to concatenation at parse time. `const NAME = literal` declares compile-time values usable in expressions and `#if`-adjacent contexts; `inline`-function requests are rejected with the table's reason. `and`/`or` land here too: two new keywords (spelled as words, not `&&`/`||`), one new precedence level below comparison and above assignment (`or` binds loosest, then `and`, then comparison, then the arithmetic ladder), short-circuit evaluation, `Bool`-typed operands only — a non-`Bool` operand is a type error, no truthiness — lowering to compare-and-jump on existing opcodes (`JZ` plus a jump), no new opcode.
|
||||
|
||||
- [ ] Failing fixtures: loop-control goldens incl. the owned-drop-on-break ASan case; do-while; interpolation with expressions; const usage; `inline fn` must-fail.
|
||||
- [ ] Failing fixtures: loop-control goldens incl. the owned-drop-on-break ASan case; do-while; interpolation with expressions; const usage; `inline fn` must-fail; `and`/`or` precedence goldens (incl. `a == 1 and b == 2` parsing without parens), short-circuit behavior, non-`Bool` operand must-fail.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): break/continue/do-while with drop-correct early exits, string interpolation desugar, const values, inline-fn rejection.`
|
||||
- [ ] Record commit draft: `feat(compiler): break/continue/do-while with drop-correct early exits, string interpolation desugar, const values, inline-fn rejection, and/or boolean operators (new precedence level, short-circuit, Bool-only, compare-and-jump lowering).`
|
||||
|
||||
### Task 3: `switch` as expression
|
||||
|
||||
|
|
@ -65,13 +65,13 @@ docs/plan/oop-vm/00-wob-format.md grows with the three VM changes
|
|||
- [ ] Implement compiler + the variant-object VM piece; green; format doc updated.
|
||||
- [ ] Record commit draft: `feat: typedef records (structural, ?fields) + enum payload variants (tagged variant objects, switch destructuring); format doc variant convention.`
|
||||
|
||||
### Task 5: `try` / `catch` / `throw` over the trap system
|
||||
### Task 5: `try` / `catch` over the trap system
|
||||
|
||||
**Concept & reason:** the biggest VM change of the plan: **catch frames**. A `try` region registers a handler; a trap raised inside unwinds frames — running drop maps exactly as today — but stops at the nearest handler instead of the entry boundary, delivering the structured error record `{code, method, line, msg}` bound to the catch variable. `throw value` raises EXPLICIT with the value attached (the error record grows an optional payload slot — format doc + wob version note; coordinate like the plan-6 route-section bump). Uncaught behavior is byte-for-byte today's trap surface. Compiler side: `try expr catch (e) expr` expression form, both arms unified in type; the owner pass treats the catch arm as an alternate flow join.
|
||||
**Concept & reason:** the biggest VM change of the plan: **catch frames**. A `try` region registers a handler; a trap raised inside unwinds frames — running drop maps exactly as today — but stops at the nearest handler instead of the entry boundary, delivering the structured error record `{code, method, line, msg}` bound to the catch variable. Uncaught behavior is byte-for-byte today's trap surface. `throw` (explicit raise) is cut from this task — 0 uses in the driving workload, parked post-iteration-12 — so the error record carries no optional payload slot and there is no `.wob` version-note coordination to make. Compiler side: `try expr catch (e) expr` expression form, both arms unified in type; the owner pass treats the catch arm as an alternate flow join.
|
||||
|
||||
- [ ] Failing fixtures: caught trap yields fallback (div0 probe pattern); drop maps still fire for frames skipped by the unwind (ASan big-class proof — the load-bearing test); throw-with-value caught upstream; uncaught still exits 1 with the plan-6 shaped error; nested try picks the nearest handler.
|
||||
- [ ] Failing fixtures: caught trap yields fallback (div0 probe pattern); drop maps still fire for frames skipped by the unwind (ASan big-class proof — the load-bearing test); uncaught still exits 1 with the plan-6 shaped error; nested try picks the nearest handler.
|
||||
- [ ] Implement VM catch frames + compiler lowering; all prior trap fixtures re-run unchanged; green.
|
||||
- [ ] Record commit draft: `feat: try/catch/throw — VM catch frames (unwind stops at nearest handler, drop maps intact, error payload slot), expression-form catch with flow-join ownership; uncaught surface unchanged.`
|
||||
- [ ] Record commit draft: `feat: try/catch — VM catch frames (unwind stops at nearest handler, drop maps intact), expression-form catch with flow-join ownership; uncaught surface unchanged. throw cut (0 uses) — no error payload slot.`
|
||||
|
||||
### Task 6: `?T` optionals with forced handling
|
||||
|
||||
|
|
@ -83,15 +83,7 @@ docs/plan/oop-vm/00-wob-format.md grows with the three VM changes
|
|||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat: ?T optionals — null-narrowing control flow, forced handling diagnostics, zero-word heap nil + boxed scalar cells; record ?fields and future stdlib returns typed ?T.`
|
||||
|
||||
### Task 7: `is`
|
||||
|
||||
**Concept & reason:** runtime type test, compiler-only. `is`: runtime test on union values (variant membership — reads the Task-4 tag) and interface values (vtable membership — reuses the loader's satisfaction data); statically-decidable `is` diagnoses as always-true/false instead of compiling to a runtime check. `abstract` newtypes were this task's other half; dropped — see the systems-track verdict table (adopt → reject, 2026-08-10 money-sku-float-removal change): a distinct scalar type adds a conversion surface without buying safety this language needs, and the compiler's own Money/SKU stopgap allowlist proved the cost was real (compiler/src/types.ml). Domain scalars stay plain `Int`/`Text`.
|
||||
|
||||
- [ ] Failing fixtures: `is` on unions/interfaces; statically-known `is` must-fail.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): is on unions/interfaces with static-decidability diagnostic.`
|
||||
|
||||
### Task 8: `static` members, `using` extensions, `pub(read)` accessors
|
||||
### Task 7: `static` members, `using` extensions, `pub(read)` accessors
|
||||
|
||||
**Concept & reason:** the organization trio. Statics: `static fn`/`static const` on classes — namespaced calls (`Flock.held(path)`) with no instance, no `self`; lower as free fns with mangled names. Using: `using shared/textutil` makes that module's free fns whose first parameter matches a type callable as methods on it (`s.words()` for `words(s: Text)`) — resolution is compile-time only, no dispatch table, collisions with real methods diagnose (real method wins is a lie surface; error instead). Accessors: `pub(read) field` exports read access, writes stay owner-class-only — the Haxe `(default, null)` pattern; enforcement in the typechecker at field-write sites.
|
||||
|
||||
|
|
@ -99,7 +91,7 @@ docs/plan/oop-vm/00-wob-format.md grows with the three VM changes
|
|||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): static members (mangled free-fn lowering), using static-extensions (compile-time, collision-diagnosed), pub(read) accessor enforcement.`
|
||||
|
||||
### Task 9: `#if` build flags + reject-row enforcement + closeout
|
||||
### Task 8: `#if` build flags + reject-row enforcement + closeout
|
||||
|
||||
**Concept & reason:** last adoptions and the table's other half. Build flags: `woc -D name` defines flags; `#if name / #else / #end` sections include/exclude at the token stream level (flag names only, no expression language — the spec's limit); undefined flags are false; nesting allowed. Reject enforcement: the keywords that would otherwise parse get targeted diagnostics with the table's reasons — `extends`/`implements`/`super`/`override` on class declarations, `cast`, `Dynamic`/`untyped` as type/expression, `macro`, `extern`, `operator` — each cites the spec section. `abstract` joins this reject row too, but needs no diagnostic of its own: the keyword never lexes, so it is absent by construction, the same as `macro`/`extern`. Closeout: error catalog complete for the track, keyword table in the spec annotated with shipped status, `just oop-accept` runs the grown corpus, CLAUDE.md language notes synced.
|
||||
|
||||
|
|
@ -111,6 +103,6 @@ docs/plan/oop-vm/00-wob-format.md grows with the three VM changes
|
|||
|
||||
## Plan self-review notes
|
||||
|
||||
- **Spec coverage (Part 1 + success criterion 1):** every adopt row has a task (modules T1, control/const/interp T2, switch T3, typedef+enum T4, try/catch/throw T5, optionals T6, `is` T7, static/using/pub(read) T8, #if T9); every reject row enforced in T9 or absent by construction — `abstract` moved from adopt to reject on 2026-08-10, so T7 lost its other half. Criterion 1's "corpus coverage per row" is each task's fixture requirement.
|
||||
- **Spec coverage (Part 1 + success criterion 1):** every adopt row has a task (modules T1, control/const/interp/and-or T2, switch T3, typedef+enum T4, try/catch T5, optionals T6, static/using/pub(read) T7, #if T8); every reject row enforced in T8 or absent by construction — `abstract` moved from adopt to reject on 2026-08-10, and `is` was cut the same day (0 uses in the driving workload, parked post-iteration-12), so this plan's old Task 7 is deleted rather than deferred. Criterion 1's "corpus coverage per row" is each task's fixture requirement.
|
||||
- **VM changes fenced:** exactly three (catch frames, variant objects, boxed scalar optionals), each with a format-doc update in its task; everything else is lowering.
|
||||
- **Order rationale:** modules first (everything imports), data shapes before optionals (records carry ?fields), try/catch after switch (arms reuse unified-type machinery), rejects last when all parse paths exist to hang diagnostics on.
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ justfile oop-e2e, oop-accept recipes (Tasks
|
|||
|
||||
### Task 8: Acceptance gate + docs closeout
|
||||
|
||||
**Files:** modify `justfile` (`oop-accept`), `CLAUDE.md`, `compiler/README.md`, `runtime/README.md`, `docs/plan/00-kanban.md`; the spec gets its criteria checked.
|
||||
**Files:** modify `justfile` (`oop-accept`), `CLAUDE.md`, `compiler/README.md`, `runtime/README.md`, `docs/00-status.md`; the spec gets its criteria checked.
|
||||
|
||||
**Concept & reason:** run milestone 1's definition of done as one command. `just oop-accept` executes, in order: compile-time measurement of the pricing subset (must be < 100 ms — criterion 1); the full conformance corpus under ASan including gc fixtures (criteria 2–4); the single-binary smoke (criterion 5); plus plan-1's `wovm-test` and plan-2's `woc-test` full gates. Docs closeout: CLAUDE.md gains the three-directory story (compiler/, runtime/, corpus) and the recipes; both READMEs cross-link; the kanban records the milestone. Anything failing here is a defect in an earlier task — this task adds no functionality, only the gate and the paper trail.
|
||||
|
||||
|
|
|
|||
|
|
@ -181,10 +181,10 @@ systems-track verdict table's `abstract` row flips **adopt → reject**
|
|||
scalar type adds a conversion surface without buying safety this language
|
||||
needs, and the compiler's own `Money`/`SKU` stopgap allowlist is the
|
||||
concrete proof the cost was real. Domain scalars are plain `Int`/`Text`.
|
||||
Haxe-parity Task 7 keeps only `is`. No allowlist has a future to be revived
|
||||
Haxe-parity's abstract+`is` task was deleted outright (`is` cut, 0 uses). No allowlist has a future to be revived
|
||||
into — re-adding `Float` requires float literals in the lexer *and* a float
|
||||
kind in `.wob` landing together; re-adding `abstract` requires the keyword
|
||||
itself to lex and parse, which plan 8 Task 9's reject-row enforcement now
|
||||
itself to lex and parse, which plan 8 Task 8's reject-row enforcement now
|
||||
actively blocks.
|
||||
|
||||
---
|
||||
|
|
@ -434,7 +434,7 @@ Selection rules when a later milestone adopts one:
|
|||
|---------|--------|
|
||||
| `builtin_scalars` | Task 6b: remove `"Money"`, `"SKU"`; add `"Float"`. This change (2026-08-10): remove `"Float"` too. Final list: `["Int"; "Bool"; "Text"; "Timestamp"; "Id"]`. |
|
||||
| Typechecker | Shipped: WO-W201 (`@gc` suggestion, self-reference-only heuristic) + WO-E225 (unknown type, bare class fields only). Still dead: ten reserved `WO-E2xx` codes — see "Dead-code register" above. |
|
||||
| `abstract` types | **Rejected**, not adopted. Verdict-table row flips adopt → reject; haxe-parity Task 7 keeps only `is`. No `Money`/`SKU`/any newtype re-declaration is coming. |
|
||||
| `abstract` types | **Rejected**, not adopted. Verdict-table row flips adopt → reject; haxe-parity's abstract+`is` task was deleted outright (`is` cut, 0 uses). No `Money`/`SKU`/any newtype re-declaration is coming. |
|
||||
| `?T` semantics | **Not implemented.** Plumbed through lexer/token/AST/parser/dump; typechecker enforcement (narrowing, forced handling, `WO-E211`–`WO-E213`) owned by haxe-parity Task 6 — the next work item. |
|
||||
| ADT roster | Globally accepted container ADTs recorded as the candidate pool for future native classes (section above); `multi`/`map`/`Text` mapped to List/Map/String |
|
||||
| Test fixtures | None added under `test/golden/types/` — Task 6b asserted behavior directly in `runner.ml` instead (see "What Task 6b actually tested") |
|
||||
|
|
@ -445,6 +445,6 @@ Selection rules when a later milestone adopts one:
|
|||
## References
|
||||
|
||||
- [Error catalog](../oop-vm/01-error-catalog.md) — every `WO-E`/`WO-W` code `woc` actually emits, plus the "Reserved, not yet emitted" section this doc's dead-code register expands on
|
||||
- [Haxe-Parity Language plan](2026-08-01-haxe-parity-language.md) — Task 6 owns `?T` forced handling (the handoff above); Task 7 is reduced to `is` only
|
||||
- [Haxe-Parity Language plan](2026-08-01-haxe-parity-language.md) — Task 6 owns `?T` forced handling (the handoff above); the abstract+`is` task is deleted (`is` cut, 0 uses)
|
||||
- [OOP Compiler VM Design](../../superpowers/specs/2026-08-01-oop-compiler-vm-design.md) - Section 3
|
||||
- [Systems Track Design](../../superpowers/specs/2026-08-01-systems-track-design.md) - Part 1; the `abstract` row (adopt → reject)
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ exists so a settled question is not re-proposed. If you want to revisit an
|
|||
entry, argue against the reason recorded here — do not re-open it as if it were
|
||||
new.
|
||||
|
||||
Status board: [`00-kanban.md`](00-kanban.md) · Doctrine: [`../00-principles.md`](../00-principles.md)
|
||||
Status board: [`00-status.md`](../00-status.md) · Doctrine: [`../00-principles.md`](../00-principles.md)
|
||||
|
||||
## Language surface
|
||||
|
||||
| Rejected | Date | Reason |
|
||||
| --- | --- | --- |
|
||||
| **Inheritance** — `extends`, `super`, `override`, `implements` | plan 13 / OOP spec | No hierarchies, ever. Is-a is a tagged union, has-a is composition, polymorphism is structural interfaces. Hierarchies fossilize early guesses and make dispatch, ownership, and diagnostics all harder. Principle 4. |
|
||||
| **`abstract` newtypes** (`abstract Money = Int`) | 2026-08-10 | Flipped **adopt → reject** in the systems-track verdict table. A distinct scalar type adds a conversion surface without buying safety this language needs; domain scalars stay plain `Int`/`Text`. The `Money`/`SKU` stopgap allowlist that stood in for the unbuilt feature proved the cost was real. Haxe-parity Task 7 keeps only `is`. |
|
||||
| **`abstract` newtypes** (`abstract Money = Int`) | 2026-08-10 | Flipped **adopt → reject** in the systems-track verdict table. A distinct scalar type adds a conversion surface without buying safety this language needs; domain scalars stay plain `Int`/`Text`. The `Money`/`SKU` stopgap allowlist that stood in for the unbuilt feature proved the cost was real. Haxe-parity's abstract+`is` task was deleted outright — `abstract` rejected here, `is` cut for zero uses in the driving workload. |
|
||||
| **`Money`, `SKU` as language types** | 2026-08-10 | Removed from `builtin_scalars` and from the abstract allowlist. `Money` → `Int` (minor units), `SKU` → `Text` everywhere. |
|
||||
| **`Float` as a builtin scalar** | 2026-08-10 | Phantom: it was in `builtin_scalars` and documented as f64, but `token.ml` has no float literal and `wob.h` has no float kind — `ratio: Float` typechecked while no `Float` value could ever be written or represented. Re-add only when literals **and** a wob float kind land together. |
|
||||
| **`Dynamic` / `untyped`** | systems-track spec | Static typing is the untagged-register VM's foundation. Typed `json.decode … as T -> ?T` covers the real use. Principle 13. |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 01 — Scaffolding the Crate Tree
|
||||
|
||||
**Context sources:** [`../../CLAUDE.md`](../../CLAUDE.md), [`../../README.md`](../../README.md), [`../../crates/README.md`](../../crates/README.md), [`../runtime/database.md`](../runtime/database.md), [`../runtime/database/07-wo-seg-migration.md`](../runtime/database/07-wo-seg-migration.md).
|
||||
**Context sources:** [`../../CLAUDE.md`](../../../CLAUDE.md), [`../../README.md`](../../../README.md), [`../../crates/README.md`](../../../crates/README.md), [`../runtime/database.md`](../../runtime/database.md), [`../runtime/database/07-wo-seg-migration.md`](../../runtime/database/07-wo-seg-migration.md).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -18,11 +18,11 @@ Lay out the full `crates/` directory tree that the 7-phase `.wo` runtime design
|
|||
4. **No `wo-` prefix.** New runtime crates are `ql`, `value`, `engine`, etc. — not `wo-ql`, `wo-value`. The prefix is redundant inside the project's own `wo` namespace and noisy in imports (`use ql::Parser` beats `use wo_ql::Parser`). The v1 crates in `reference/crates/` keep their `wo-` prefix — the distinct prefix makes the v1/v2 split visible at a glance.
|
||||
5. **Workspace membership: root `Cargo.toml` lists every new crate as a member.** `reference/crates` stays `exclude`-d (nested workspace, separate v1 code).
|
||||
|
||||
Rationale and alternatives considered: see [`../../CLAUDE.md`](../../CLAUDE.md) "What's in `rt` today vs. what the empty crates promise" and the recorded `AskUserQuestion` answers that preceded this plan.
|
||||
Rationale and alternatives considered: see [`../../CLAUDE.md`](../../../CLAUDE.md) "What's in `rt` today vs. what the empty crates promise" and the recorded `AskUserQuestion` answers that preceded this plan.
|
||||
|
||||
## Crate map
|
||||
|
||||
All names are stable — documented in [`../runtime/database/07-wo-seg-migration.md`](../runtime/database/07-wo-seg-migration.md) (Phase 2–5) and derived from [`../runtime/database/06-lowcode-fullstack.md`](../runtime/database/06-lowcode-fullstack.md) component tables (Phase 6).
|
||||
All names are stable — documented in [`../runtime/database/07-wo-seg-migration.md`](../../runtime/database/07-wo-seg-migration.md) (Phase 2–5) and derived from [`../runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md) component tables (Phase 6).
|
||||
|
||||
| Phase | Crate | One-line purpose |
|
||||
| --- | --- | --- |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 02 — Event Loop on `epoll`
|
||||
|
||||
**Context sources:** [`../01-problem.md`](../01-problem.md), [`../02-recovery.md`](../02-recovery.md), [`./linux/00-linux.md`](./linux/00-linux.md), [`./done/01-scafolding-crates.md`](./done/01-scafolding-crates.md).
|
||||
**Context sources:** [`../01-problem.md`](../../01-problem.md), [`../02-recovery.md`](../../02-recovery.md), [`./linux/00-linux.md`](../exploration/linux/00-linux.md), [`./done/01-scafolding-crates.md`](01-scafolding-crates.md).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -10,8 +10,8 @@ Nothing is removed in this phase. The module sits alongside the tokio-backed axu
|
|||
|
||||
## Design decisions (locked)
|
||||
|
||||
1. **`epoll`, not `io_uring`, on day one.** `epoll` is ubiquitous (Linux 2.6+), well-understood, and every primitive we need (eventfd, timerfd, signalfd, inotify, accepted sockets) already integrates with it via `epoll_ctl`. `io_uring` is a natural follow-on phase once the event-loop abstraction exists — [`00-linux.md`](./linux/00-linux.md) calls it out for that role.
|
||||
2. **Single-threaded, edge-triggered.** Matches [02-wo-language.md § Concurrency Model](../runtime/database/02-wo-language.md#concurrency-model). Every fd registered with `EPOLLET`; the loop reads until `EAGAIN`. No worker pool, no cross-thread state.
|
||||
1. **`epoll`, not `io_uring`, on day one.** `epoll` is ubiquitous (Linux 2.6+), well-understood, and every primitive we need (eventfd, timerfd, signalfd, inotify, accepted sockets) already integrates with it via `epoll_ctl`. `io_uring` is a natural follow-on phase once the event-loop abstraction exists — [`00-linux.md`](../exploration/linux/00-linux.md) calls it out for that role.
|
||||
2. **Single-threaded, edge-triggered.** Matches [02-wo-language.md § Concurrency Model](../../runtime/database/02-wo-language.md#concurrency-model). Every fd registered with `EPOLLET`; the loop reads until `EAGAIN`. No worker pool, no cross-thread state.
|
||||
3. **`libc` is the only new dependency.** `libc = "0.2"` added to `crates/rt/Cargo.toml`. No `nix`, no `mio`. Direct `unsafe extern "C"` calls against the kernel surface.
|
||||
4. **Module, not crate (yet).** Lives at `crates/rt/src/runtime/` so phase 03 can call into it cheaply. Extraction to the empty `crates/event/` sibling is deferred until a second caller appears outside `rt` — likely when [`sub`](../../crates/sub/) starts consuming the loop for subscription delivery.
|
||||
|
||||
|
|
@ -21,17 +21,17 @@ Nothing is removed in this phase. The module sits alongside the tokio-backed axu
|
|||
|
||||
| File | Responsibility | Port source |
|
||||
| --- | --- | --- |
|
||||
| `mod.rs` | Re-exports `EventLoop`, `Event`, `Interest`, `Token`, `EventFd`, `TimerFd`, `SignalFd` | [`reference/crates/wo-event/src/lib.rs`](../../reference/crates/wo-event/src/lib.rs) (9 LOC) |
|
||||
| `netpoll_epoll.rs` | `EventLoop { fd, events }` — `new()`, `register(raw_fd, interest, token)`, `wait_once(timeout) -> &[Event]`, `deregister(raw_fd)` | [`reference/crates/wo-event/src/epoll.rs`](../../reference/crates/wo-event/src/epoll.rs) (183 LOC); [`reference/go/src/runtime/netpoll_epoll.go`](../../reference/go/src/runtime/netpoll_epoll.go) for idiom |
|
||||
| `eventfd.rs` | `EventFd { fd }` — counter semaphore for cross-fd wake-up (subscription dispatch, shutdown signal) | [`reference/crates/wo-event/src/eventfd.rs`](../../reference/crates/wo-event/src/eventfd.rs) (66 LOC) |
|
||||
| `timerfd.rs` | `TimerFd { fd }` — oneshot + periodic timers as fds for the loop | [`reference/crates/wo-event/src/timerfd.rs`](../../reference/crates/wo-event/src/timerfd.rs) (91 LOC) |
|
||||
| `signalfd.rs` | `SignalFd { fd }` — SIGINT / SIGTERM / SIGHUP delivered as fd reads for graceful shutdown without a tokio signal handler | [`reference/crates/wo-event/src/signalfd.rs`](../../reference/crates/wo-event/src/signalfd.rs) (62 LOC) |
|
||||
| `mod.rs` | Re-exports `EventLoop`, `Event`, `Interest`, `Token`, `EventFd`, `TimerFd`, `SignalFd` | [`reference/crates/wo-event/src/lib.rs`](../../../.dev/reference/crates/wo-event/src/lib.rs) (9 LOC) |
|
||||
| `netpoll_epoll.rs` | `EventLoop { fd, events }` — `new()`, `register(raw_fd, interest, token)`, `wait_once(timeout) -> &[Event]`, `deregister(raw_fd)` | [`reference/crates/wo-event/src/epoll.rs`](../../../.dev/reference/crates/wo-event/src/epoll.rs) (183 LOC); [`reference/go/src/runtime/netpoll_epoll.go`](../../../.dev/reference/go/src/runtime/netpoll_epoll.go) for idiom |
|
||||
| `eventfd.rs` | `EventFd { fd }` — counter semaphore for cross-fd wake-up (subscription dispatch, shutdown signal) | [`reference/crates/wo-event/src/eventfd.rs`](../../../.dev/reference/crates/wo-event/src/eventfd.rs) (66 LOC) |
|
||||
| `timerfd.rs` | `TimerFd { fd }` — oneshot + periodic timers as fds for the loop | [`reference/crates/wo-event/src/timerfd.rs`](../../../.dev/reference/crates/wo-event/src/timerfd.rs) (91 LOC) |
|
||||
| `signalfd.rs` | `SignalFd { fd }` — SIGINT / SIGTERM / SIGHUP delivered as fd reads for graceful shutdown without a tokio signal handler | [`reference/crates/wo-event/src/signalfd.rs`](../../../.dev/reference/crates/wo-event/src/signalfd.rs) (62 LOC) |
|
||||
|
||||
Total: ~410 LOC lifted and adapted. The v1 code already compiles standalone in `reference/crates/wo-event/` and has unit tests; the port is near-verbatim plus namespace cleanups.
|
||||
|
||||
### Why `runtime/` not `event/`
|
||||
|
||||
Go's equivalent code lives at [`reference/go/src/runtime/netpoll_epoll.go`](../../reference/go/src/runtime/netpoll_epoll.go) alongside siblings like `netpoll_kqueue.go` (macOS/BSD), `netpoll_io_uring.go` (if/when Go adds it), and the shared `netpoll.go` interface. The directory name "runtime" signals that this is the layer beneath user code — scheduler / netpoll / syscall shims — and the filename prefix `netpoll_<flavour>` makes each implementation alternative visible at a glance. Adopting the same convention in writeonce makes porting ideas bidirectional: a reader who knows Go's layout can find the writeonce equivalent by trimming the `.go` extension and swapping it for `.rs`. When Phase 3's io_uring arrives it'll land as `netpoll_io_uring.rs` next to the epoll one; a cross-platform stub would be `netpoll.rs`. Module boundary and naming both match. See [`docs/plan/assembly/00-overview.md`](./assembly/00-overview.md) for why we stop short of mirroring Go's assembly conventions.
|
||||
Go's equivalent code lives at [`reference/go/src/runtime/netpoll_epoll.go`](../../../.dev/reference/go/src/runtime/netpoll_epoll.go) alongside siblings like `netpoll_kqueue.go` (macOS/BSD), `netpoll_io_uring.go` (if/when Go adds it), and the shared `netpoll.go` interface. The directory name "runtime" signals that this is the layer beneath user code — scheduler / netpoll / syscall shims — and the filename prefix `netpoll_<flavour>` makes each implementation alternative visible at a glance. Adopting the same convention in writeonce makes porting ideas bidirectional: a reader who knows Go's layout can find the writeonce equivalent by trimming the `.go` extension and swapping it for `.rs`. When Phase 3's io_uring arrives it'll land as `netpoll_io_uring.rs` next to the epoll one; a cross-platform stub would be `netpoll.rs`. Module boundary and naming both match. See [`docs/plan/assembly/00-overview.md`](../exploration/assembly/00-overview.md) for why we stop short of mirroring Go's assembly conventions.
|
||||
|
||||
### `Cargo.toml` change
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 03 — Hand-Rolled HTTP/1.1
|
||||
|
||||
**Context sources:** [`./02-event-loop-epoll.md`](./02-event-loop-epoll.md), [`./linux/00-linux.md`](./linux/00-linux.md), [`../02-recovery.md`](../02-recovery.md).
|
||||
**Context sources:** [`./02-event-loop-epoll.md`](./02-event-loop-epoll.md), [`./linux/00-linux.md`](../exploration/linux/00-linux.md), [`../02-recovery.md`](../../02-recovery.md).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -9,10 +9,10 @@ A non-blocking HTTP/1.1 server module that accepts connections, parses requests,
|
|||
## Design decisions (locked)
|
||||
|
||||
1. **HTTP/1.1 only, keep-alive supported.** HTTP/2 and HTTP/3 are not on the roadmap for Stage 2 — they need ALPN / TLS support we don't have a plan for yet. HTTP/1.1 covers every endpoint the blog + ecommerce samples exercise.
|
||||
2. **Per-connection state machine.** Each accepted socket fd is registered on the event loop with its own `Connection { state: Reading | Writing | Idle, parser, pending_response }`. Edge-triggered `EPOLLIN`/`EPOLLOUT` drive state transitions. Matches [v1 wo-http](../../reference/crates/wo-http/src/connection.rs)'s model verbatim.
|
||||
2. **Per-connection state machine.** Each accepted socket fd is registered on the event loop with its own `Connection { state: Reading | Writing | Idle, parser, pending_response }`. Edge-triggered `EPOLLIN`/`EPOLLOUT` drive state transitions. Matches [v1 wo-http](../../../.dev/reference/crates/wo-http/src/connection.rs)'s model verbatim.
|
||||
3. **Router is pattern-matched at registration.** `Router::new().route("/api/articles/:id", Method::GET, handler)` resolves to a trie at boot. Per-request dispatch is a single trie walk — no axum-style type-erased layers.
|
||||
4. **Handlers are `fn(&Request, &Engine) -> Response`.** Synchronous. The single-threaded event loop means a handler blocking is a bug; each handler must be a pure transformation over engine state.
|
||||
5. **Module, not crate (yet).** Lives at `crates/rt/src/http/` with the same "extract when a second consumer shows up" rule as phase 02. The eventual home is the empty [`crates/http/`](../../crates/http/) sibling — but not in this phase. Paired with [phase 02's `crates/rt/src/runtime/`](./02-event-loop-epoll.md) (Go-style naming — `netpoll_epoll.rs`, `eventfd.rs`, …) which this module depends on for the `EventLoop` + raw syscall shims. Go's `src/net/http/` and `src/runtime/` split is the layout precedent; see [`reference/go/src/net/http/`](../../reference/go/src/net/http/).
|
||||
5. **Module, not crate (yet).** Lives at `crates/rt/src/http/` with the same "extract when a second consumer shows up" rule as phase 02. The eventual home is the empty [`crates/http/`](../../crates/http/) sibling — but not in this phase. Paired with [phase 02's `crates/rt/src/runtime/`](./02-event-loop-epoll.md) (Go-style naming — `netpoll_epoll.rs`, `eventfd.rs`, …) which this module depends on for the `EventLoop` + raw syscall shims. Go's `src/net/http/` and `src/runtime/` split is the layout precedent; see [`reference/go/src/net/http/`](../../../.dev/reference/go/src/net/http/).
|
||||
|
||||
## Scope
|
||||
|
||||
|
|
@ -20,12 +20,12 @@ A non-blocking HTTP/1.1 server module that accepts connections, parses requests,
|
|||
|
||||
| File | Responsibility | Port source |
|
||||
| --- | --- | --- |
|
||||
| `mod.rs` | Re-exports `Listener`, `Connection`, `Request`, `Response`, `Router`, `Method`, `Status` | [`reference/crates/wo-http/src/lib.rs`](../../reference/crates/wo-http/src/lib.rs) (4 LOC) |
|
||||
| `listener.rs` | `Listener { fd }` wrapping `socket + bind + listen + accept4(SOCK_NONBLOCK \| SOCK_CLOEXEC)`; integrates with `EventLoop` | [`reference/crates/wo-http/src/listener.rs`](../../reference/crates/wo-http/src/listener.rs) (202 LOC) |
|
||||
| `connection.rs` | Per-fd state machine: drain request bytes, parse, dispatch, drain response bytes, keep-alive or close | [`reference/crates/wo-http/src/connection.rs`](../../reference/crates/wo-http/src/connection.rs) (202 LOC) |
|
||||
| `request.rs` | Incremental HTTP/1.1 request parser: request line, headers, optional body. `Content-Length` only (no chunked request bodies in Stage 2 — they don't appear in the samples) | [`reference/crates/wo-http/src/request.rs`](../../reference/crates/wo-http/src/request.rs) (158 LOC) |
|
||||
| `response.rs` | Response builder + writer: status line, headers, body (fixed or chunked) | [`reference/crates/wo-http/src/response.rs`](../../reference/crates/wo-http/src/response.rs) (110 LOC) |
|
||||
| `route.rs` | Trie-based router: static paths + `:param` segments. `Router::route(method, path, handler) -> Router` | [`reference/crates/wo-route/src/router.rs`](../../reference/crates/wo-route/src/router.rs) (127 LOC) + [`pattern.rs`](../../reference/crates/wo-route/src/pattern.rs) (146 LOC) |
|
||||
| `mod.rs` | Re-exports `Listener`, `Connection`, `Request`, `Response`, `Router`, `Method`, `Status` | [`reference/crates/wo-http/src/lib.rs`](../../../.dev/reference/crates/wo-http/src/lib.rs) (4 LOC) |
|
||||
| `listener.rs` | `Listener { fd }` wrapping `socket + bind + listen + accept4(SOCK_NONBLOCK \| SOCK_CLOEXEC)`; integrates with `EventLoop` | [`reference/crates/wo-http/src/listener.rs`](../../../.dev/reference/crates/wo-http/src/listener.rs) (202 LOC) |
|
||||
| `connection.rs` | Per-fd state machine: drain request bytes, parse, dispatch, drain response bytes, keep-alive or close | [`reference/crates/wo-http/src/connection.rs`](../../../.dev/reference/crates/wo-http/src/connection.rs) (202 LOC) |
|
||||
| `request.rs` | Incremental HTTP/1.1 request parser: request line, headers, optional body. `Content-Length` only (no chunked request bodies in Stage 2 — they don't appear in the samples) | [`reference/crates/wo-http/src/request.rs`](../../../.dev/reference/crates/wo-http/src/request.rs) (158 LOC) |
|
||||
| `response.rs` | Response builder + writer: status line, headers, body (fixed or chunked) | [`reference/crates/wo-http/src/response.rs`](../../../.dev/reference/crates/wo-http/src/response.rs) (110 LOC) |
|
||||
| `route.rs` | Trie-based router: static paths + `:param` segments. `Router::route(method, path, handler) -> Router` | [`reference/crates/wo-route/src/router.rs`](../../../.dev/reference/crates/wo-route/src/router.rs) (127 LOC) + [`pattern.rs`](../../../.dev/reference/crates/wo-route/src/pattern.rs) (146 LOC) |
|
||||
|
||||
Total: ~949 LOC ported. Most of it is mechanical adaptation from v1; the namespace + the `Interest` enum change from phase 02 are the only non-trivial edits.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# 04 — Cutover: Remove tokio, axum, tower
|
||||
|
||||
**Context sources:** [`./02-event-loop-epoll.md`](./02-event-loop-epoll.md), [`./03-hand-rolled-http.md`](./03-hand-rolled-http.md), [`../01-problem.md`](../01-problem.md).
|
||||
**Context sources:** [`./02-event-loop-epoll.md`](./02-event-loop-epoll.md), [`./03-hand-rolled-http.md`](./03-hand-rolled-http.md), [`../01-problem.md`](../../01-problem.md).
|
||||
|
||||
## Goal
|
||||
|
||||
Flip the `wo` binary off the tokio + axum stack and onto the phase-02 event loop + phase-03 HTTP server. Delete three dependencies from `crates/rt/Cargo.toml`. REST behaviour visible to [`reference/rest/blog.rest`](../../reference/rest/blog.rest) does not change — same status codes, same response bodies, same endpoint paths.
|
||||
Flip the `wo` binary off the tokio + axum stack and onto the phase-02 event loop + phase-03 HTTP server. Delete three dependencies from `crates/rt/Cargo.toml`. REST behaviour visible to [`reference/rest/blog.rest`](../../../.dev/reference/rest/blog.rest) does not change — same status codes, same response bodies, same endpoint paths.
|
||||
|
||||
This is the first phase where the dependency count goes *down*. Phases 02 and 03 were additive; this one is the switch.
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ This is the first phase where the dependency count goes *down*. Phases 02 and 03
|
|||
|
||||
1. **Atomic swap, single commit.** Don't run tokio and the new loop in parallel in production. Flip the binary's `main()` in one change. Phase 03 already gave us confidence the new stack works end-to-end via `http-smoke`.
|
||||
2. **Preserve the `Engine` trait surface.** `Arc<Mutex<Engine>>` stays exactly as `crates/rt/src/engine.rs` has it today. The routing layer in `crates/rt/src/server.rs` — the function that maps `service rest` blocks to axum `MethodRouter` — gets rewritten to emit phase-03 `Router::route(...)` calls instead. Same data flow, different transport.
|
||||
3. **No tokio — no async.** Handlers become synchronous `fn(&Request, &Engine) -> Response`. The single-threaded event loop [already assumes this](../runtime/database/02-wo-language.md#concurrency-model); removing `async fn` plumbing simplifies the code. `tokio::sync::Mutex` becomes `std::sync::Mutex` (fine in a single-threaded loop since lock contention is impossible).
|
||||
3. **No tokio — no async.** Handlers become synchronous `fn(&Request, &Engine) -> Response`. The single-threaded event loop [already assumes this](../../runtime/database/02-wo-language.md#concurrency-model); removing `async fn` plumbing simplifies the code. `tokio::sync::Mutex` becomes `std::sync::Mutex` (fine in a single-threaded loop since lock contention is impossible).
|
||||
4. **`signalfd` replaces `tokio::signal::ctrl_c()`.** Registered as another fd on the loop; reading a SIGINT cleanly exits the loop and closes outstanding connections.
|
||||
5. **`WO_LISTEN` env var semantics unchanged.** The `127.0.0.1:8080` default + the `WO_LISTEN=...` override stays exactly as today. Operators don't notice the change.
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ Twelve handlers total — one pair per `{list, get, create, update, delete}` ×
|
|||
|
||||
1. **`cargo build`** at root — compiles with four deps (not seven).
|
||||
2. **`cargo test --lib`** — all 14 existing `rt` unit tests still pass. A new test in `src/server.rs` exercises the router build from a compiled catalog (no HTTP, just static registration).
|
||||
3. **End-to-end REST smoke — the 20-assertion battery from [`reference/rest/blog.rest`](../../reference/rest/blog.rest)** must pass byte-identical to Stage 2 today. Script:
|
||||
3. **End-to-end REST smoke — the 20-assertion battery from [`reference/rest/blog.rest`](../../../.dev/reference/rest/blog.rest)** must pass byte-identical to Stage 2 today. Script:
|
||||
```bash
|
||||
WO_LISTEN=127.0.0.1:8765 cargo run --bin wo -- run docs/examples/blog &
|
||||
# ... curl each block, check expected status
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# 00 — The role of assembly in a runtime
|
||||
|
||||
Why does a runtime ship hand-written assembly at all? Three reasons — each one a place where a higher-level language literally cannot express the operation it needs, so the compiler is bypassed and machine instructions are written directly. Go's [`src/runtime/`](../../../reference/go/src/runtime/) is the canonical example; this doc names the three reasons and points at the Go files that embody each.
|
||||
Why does a runtime ship hand-written assembly at all? Three reasons — each one a place where a higher-level language literally cannot express the operation it needs, so the compiler is bypassed and machine instructions are written directly. Go's [`src/runtime/`](../../../../.dev/reference/go/src/runtime/) is the canonical example; this doc names the three reasons and points at the Go files that embody each.
|
||||
|
||||
## 1 — Operations that violate the language's own calling convention
|
||||
|
||||
The biggest category. The language's calling convention — how arguments are passed, who saves which registers, how the stack grows — is the contract every compiled function obeys. A few runtime operations *have* to break it because they ARE the mechanism by which control flow enters and exits that contract.
|
||||
|
||||
**Goroutine stack switching.** When Go's scheduler switches from one goroutine to another, it's literally rewriting the stack pointer mid-function — jumping from one goroutine's stack to another's. The language compiler can't emit this safely because every function assumes its stack is the one it got called on. See [`reference/go/src/runtime/asm_amd64.s`](../../../reference/go/src/runtime/asm_amd64.s) for `TEXT runtime·gogo(SB)`, `TEXT runtime·mcall(SB)`, `TEXT runtime·systemstack(SB)` — all unavoidable.
|
||||
**Goroutine stack switching.** When Go's scheduler switches from one goroutine to another, it's literally rewriting the stack pointer mid-function — jumping from one goroutine's stack to another's. The language compiler can't emit this safely because every function assumes its stack is the one it got called on. See [`reference/go/src/runtime/asm_amd64.s`](../../../../.dev/reference/go/src/runtime/asm_amd64.s) for `TEXT runtime·gogo(SB)`, `TEXT runtime·mcall(SB)`, `TEXT runtime·systemstack(SB)` — all unavoidable.
|
||||
|
||||
**Signal-handler entry.** When a signal arrives, the kernel drops the process onto an alternate stack with preserved registers. Returning to normal code means restoring everything the handler touched plus switching stacks back. Go's `runtime·sigtramp` in [`reference/go/src/runtime/sys_linux_amd64.s`](../../../reference/go/src/runtime/sys_linux_amd64.s) handles this.
|
||||
**Signal-handler entry.** When a signal arrives, the kernel drops the process onto an alternate stack with preserved registers. Returning to normal code means restoring everything the handler touched plus switching stacks back. Go's `runtime·sigtramp` in [`reference/go/src/runtime/sys_linux_amd64.s`](../../../../.dev/reference/go/src/runtime/sys_linux_amd64.s) handles this.
|
||||
|
||||
**Cgo boundary crossing.** Calling C from Go means switching to the OS thread's "real" stack (C expects contiguous stacks; Go uses segmented). Going back means the inverse. Entirely asm-driven.
|
||||
|
||||
|
|
@ -16,17 +16,17 @@ The biggest category. The language's calling convention — how arguments are pa
|
|||
|
||||
Atomics, memory barriers, and some hardware-accelerated primitives need specific instruction sequences. A compiler that sees `a = *b` can't know whether you wanted a relaxed load or an acquire fence without annotation — and the *right* instruction on x86 vs ARM vs RISC-V is different.
|
||||
|
||||
**Atomic CAS / load-acquire / store-release.** On x86 it's `LOCK CMPXCHG`; on ARM it's `LDXR` / `STXR` with a retry loop; on RISC-V it's `LR.W.AQ` / `SC.W.RL`. Go emits these from [`reference/go/src/runtime/atomic_amd64.s`](../../../reference/go/src/runtime/atomic_amd64.s) (and its per-arch siblings) because a portable compiler can't.
|
||||
**Atomic CAS / load-acquire / store-release.** On x86 it's `LOCK CMPXCHG`; on ARM it's `LDXR` / `STXR` with a retry loop; on RISC-V it's `LR.W.AQ` / `SC.W.RL`. Go emits these from [`reference/go/src/runtime/atomic_amd64.s`](../../../.dev/reference/go/src/runtime/atomic_amd64.s) (and its per-arch siblings) because a portable compiler can't.
|
||||
|
||||
**Memory barriers.** `MFENCE`, `LFENCE`, `SFENCE` on x86; `DMB` / `DSB` / `ISB` on ARM. Used by Go's `publicationBarrier`, `procyield`, and friends. Per-arch asm files carry them.
|
||||
|
||||
**Optimised `memmove` / `memequal` / `memclr`.** The compiler knows how to emit `rep movsb`, but a runtime sometimes ships a *better* version than the compiler's — wider vector loads, prefetch hints, alignment-aware loops. Go ships its own in [`asm_amd64.s`](../../../reference/go/src/runtime/asm_amd64.s) using AVX/SSE paths.
|
||||
**Optimised `memmove` / `memequal` / `memclr`.** The compiler knows how to emit `rep movsb`, but a runtime sometimes ships a *better* version than the compiler's — wider vector loads, prefetch hints, alignment-aware loops. Go ships its own in [`asm_amd64.s`](../../../../.dev/reference/go/src/runtime/asm_amd64.s) using AVX/SSE paths.
|
||||
|
||||
## 3 — Syscall trampolines
|
||||
|
||||
Every raw syscall to the kernel is an asm stub. The kernel expects arguments in specific registers (on x86_64: `rdi`, `rsi`, `rdx`, `r10`, `r8`, `r9`, with the syscall number in `rax`), a `syscall` instruction, and return-value unpacking from `rax` (including `-errno` convention). A high-level language's calling convention doesn't match that layout — you need a thin asm wrapper per syscall.
|
||||
|
||||
See [`reference/go/src/runtime/sys_linux_amd64.s`](../../../reference/go/src/runtime/sys_linux_amd64.s) — 43 `TEXT` functions, one per syscall family: `runtime·write`, `runtime·read`, `runtime·futex`, `runtime·clone`, `runtime·rt_sigaction`, `runtime·rt_sigprocmask`, `runtime·rt_sigreturn`, `runtime·sched_yield`, `runtime·mmap`, `runtime·munmap`, `runtime·madvise`, `runtime·epollcreate1`, `runtime·epollctl`, `runtime·epollwait`, etc.
|
||||
See [`reference/go/src/runtime/sys_linux_amd64.s`](../../../../.dev/reference/go/src/runtime/sys_linux_amd64.s) — 43 `TEXT` functions, one per syscall family: `runtime·write`, `runtime·read`, `runtime·futex`, `runtime·clone`, `runtime·rt_sigaction`, `runtime·rt_sigprocmask`, `runtime·rt_sigreturn`, `runtime·sched_yield`, `runtime·mmap`, `runtime·munmap`, `runtime·madvise`, `runtime·epollcreate1`, `runtime·epollctl`, `runtime·epollwait`, etc.
|
||||
|
||||
Go does these in asm because it cannot rely on libc — Go's scheduler needs to enter/exit syscalls at exactly controlled points (`runtime·entersyscall`, `runtime·exitsyscall`) so the M (OS thread) can be parked or reused without losing the goroutine. Going through `libc::write` would sidestep the scheduler's accounting.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
The Go runtime ships ~72 `TEXT` functions in `asm_amd64.s` alone, ~43 in `sys_linux_amd64.s`, and per-architecture variants of both for `386`, `arm`, `arm64`, `loong64`, `mips(64)x`, `ppc64x`, `riscv64`, `s390x`, `wasm`. This doc inventories them by purpose so a reader can map each Go asm concern to the writeonce equivalent (spoiler: usually "Rust stdlib does it"). Follow-on reading: [`02-writeonce-stance.md`](./02-writeonce-stance.md).
|
||||
|
||||
All paths are inside [`reference/go/src/runtime/`](../../../reference/go/src/runtime/).
|
||||
All paths are inside [`reference/go/src/runtime/`](../../../../.dev/reference/go/src/runtime/).
|
||||
|
||||
## Scheduler & stack switching — `asm_<arch>.s`
|
||||
|
||||
One file per arch, everything that has to break Go's calling convention. The x86_64 version lives at [`asm_amd64.s`](../../../reference/go/src/runtime/asm_amd64.s).
|
||||
One file per arch, everything that has to break Go's calling convention. The x86_64 version lives at [`asm_amd64.s`](../../../../.dev/reference/go/src/runtime/asm_amd64.s).
|
||||
|
||||
| Go symbol | What |
|
||||
| --- | --- |
|
||||
|
|
@ -26,7 +26,7 @@ One file per arch, everything that has to break Go's calling convention. The x86
|
|||
|
||||
## Atomics & barriers — `internal/runtime/atomic/atomic_<arch>.s`
|
||||
|
||||
Lives at [`internal/runtime/atomic/atomic_amd64.s`](../../../reference/go/src/internal/runtime/atomic/atomic_amd64.s) (and arch variants). Wrappers around arch-specific instructions:
|
||||
Lives at [`internal/runtime/atomic/atomic_amd64.s`](../../../../.dev/reference/go/src/internal/runtime/atomic/atomic_amd64.s) (and arch variants). Wrappers around arch-specific instructions:
|
||||
|
||||
| Go symbol | x86 instruction | Purpose |
|
||||
| --- | --- | --- |
|
||||
|
|
@ -41,7 +41,7 @@ Lives at [`internal/runtime/atomic/atomic_amd64.s`](../../../reference/go/src/in
|
|||
|
||||
## Syscall trampolines — `sys_<os>_<arch>.s`
|
||||
|
||||
On Linux-x86_64 that's [`sys_linux_amd64.s`](../../../reference/go/src/runtime/sys_linux_amd64.s) — 43 `TEXT` functions. Each is a short wrapper: move args into the kernel's register layout, execute `SYSCALL`, convert `rax` into a Go return value + error.
|
||||
On Linux-x86_64 that's [`sys_linux_amd64.s`](../../../../.dev/reference/go/src/runtime/sys_linux_amd64.s) — 43 `TEXT` functions. Each is a short wrapper: move args into the kernel's register layout, execute `SYSCALL`, convert `rax` into a Go return value + error.
|
||||
|
||||
| Go symbol | Linux syscall |
|
||||
| --- | --- |
|
||||
|
|
@ -70,7 +70,7 @@ On Linux-x86_64 that's [`sys_linux_amd64.s`](../../../reference/go/src/runtime/s
|
|||
|
||||
## Cgo bridge — `cgo_<os>_<arch>.s`
|
||||
|
||||
Files like [`cgo/asm_amd64.s`](../../../reference/go/src/runtime/cgo/asm_amd64.s). Machine-code marshalling between Go's register convention and C's SysV AMD64 ABI. Needed because Go's calling convention uses stack slots differently from C's register passing.
|
||||
Files like [`cgo/asm_amd64.s`](../../../../.dev/reference/go/src/runtime/cgo/asm_amd64.s). Machine-code marshalling between Go's register convention and C's SysV AMD64 ABI. Needed because Go's calling convention uses stack slots differently from C's register passing.
|
||||
|
||||
**Writeonce doesn't cross language boundaries** — Rust is the only language in the binary; `libc` is already in Rust's register convention via `extern "C"`. No cgo bridge needed.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Each Go asm category from [`01-go-runtime-asm.md`](./01-go-runtime-asm.md) maps
|
|||
|
||||
| Go asm need | What writeonce uses | Why it covers the gap |
|
||||
| --- | --- | --- |
|
||||
| Scheduler stack switching (`gogo`, `mcall`, `systemstack`) | — nothing — | Single-threaded event loop through phases 02–08 (see Phase 2 [Concurrency Model](../runtime/database/02-wo-language.md#concurrency-model)). [Phase 09](../09-concurrency-scaleout.md) introduces **thread-per-core** scaling for the 10k-user ecommerce workload — but still no Go-style stack switching: each thread runs its own event loop, connections are pinned for their lifetime, and cross-thread work is message-passing, not scheduler-stealing. No goroutines, no `g0`, even at scale. |
|
||||
| Scheduler stack switching (`gogo`, `mcall`, `systemstack`) | — nothing — | Single-threaded event loop through phases 02–08 (see Phase 2 [Concurrency Model](../../../runtime/database/02-wo-language.md#concurrency-model)). [Phase 09](../../09-concurrency-scaleout.md) introduces **thread-per-core** scaling for the 10k-user ecommerce workload — but still no Go-style stack switching: each thread runs its own event loop, connections are pinned for their lifetime, and cross-thread work is message-passing, not scheduler-stealing. No goroutines, no `g0`, even at scale. |
|
||||
| Preemption (`asyncPreempt`) | — nothing — | No preemption through phases 02–08. Phase 09's thread-per-core model keeps this property: handlers run to completion on whichever thread owns their connection. |
|
||||
| Atomic operations (`Load`, `Store`, `Cas`, `Xadd`, ...) | [`std::sync::atomic`](https://doc.rust-lang.org/std/sync/atomic/) | The compiler emits the right instruction per target — `LOCK CMPXCHG` on x86, `LDXR/STXR` on ARM, `LR.W/SC.W` on RISC-V. Ordering is in the type signature (`Ordering::Acquire`, `Release`, `SeqCst`). |
|
||||
| Memory barriers (`MFENCE` etc.) | [`std::sync::atomic::fence(Ordering)`](https://doc.rust-lang.org/std/sync/atomic/fn.fence.html) | One call, one fence, arch-neutral. |
|
||||
|
|
@ -72,4 +72,4 @@ No asm has been written under this policy yet. The expectation is it stays that
|
|||
- [`00-overview.md`](./00-overview.md) — why runtimes ever need asm at all (three categories).
|
||||
- [`01-go-runtime-asm.md`](./01-go-runtime-asm.md) — Go's asm inventory, by file.
|
||||
- [`../linux/04-signalfd.md`](../linux/04-signalfd.md) — the specific primitive that obviates Go's `sigtramp` asm.
|
||||
- [`../02-event-loop-epoll.md`](../02-event-loop-epoll.md) — phase 02, where the `runtime/` module actually lands.
|
||||
- [`../02-event-loop-epoll.md`](../../done/02-event-loop-epoll.md) — phase 02, where the `runtime/` module actually lands.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# wo-rt-c roadmap — multi-threaded io_uring RAM database runtime, in C
|
||||
|
||||
> **Kanban: ✅ done** — phases A–F all shipped with measured exit evidence below. Board: [../../00-kanban.md](../../00-kanban.md)
|
||||
> **Status: ✅ done** — phases A–F all shipped with measured exit evidence below. Board: [00-status.md](../../../00-status.md)
|
||||
|
||||
**Context sources:** [`prototypes/wo-rt-c/wo-rt.c`](../../../../prototypes/wo-rt-c/wo-rt.c) (phase 0 — the single-threaded epoll baseline), [`../../09-concurrency-scaleout.md`](../../09-concurrency-scaleout.md) (the thread-per-core doctrine every phase here miniaturizes), [`../../10-storage-foundations.md`](../../10-storage-foundations.md) / [`11-wal-and-recovery.md`](../../11-wal-and-recovery.md) / [`12-engine-disk-cutover.md`](../../12-engine-disk-cutover.md) (the storage track), kernel reference cards [`../linux/07-io_uring.md`](../linux/07-io_uring.md), [`08-mmap.md`](../linux/08-mmap.md), [`09-fallocate.md`](../linux/09-fallocate.md), [`12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md), [`02-eventfd.md`](../linux/02-eventfd.md).
|
||||
**Context sources:** [`prototypes/wo-rt-c/wo-rt.c`](../../../../runtime/wo-rt.c) (phase 0 — the single-threaded epoll baseline), [`../../09-concurrency-scaleout.md`](../../09-concurrency-scaleout.md) (the thread-per-core doctrine every phase here miniaturizes), [`../../10-storage-foundations.md`](../../10-storage-foundations.md) / [`11-wal-and-recovery.md`](../../11-wal-and-recovery.md) / [`12-engine-disk-cutover.md`](../../12-engine-disk-cutover.md) (the storage track), kernel reference cards [`../linux/07-io_uring.md`](../linux/07-io_uring.md), [`08-mmap.md`](../linux/08-mmap.md), [`09-fallocate.md`](../linux/09-fallocate.md), [`12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md), [`02-eventfd.md`](../linux/02-eventfd.md).
|
||||
|
||||
## Goal
|
||||
|
||||
Evolve the [`prototypes/wo-rt-c/`](../../../../prototypes/wo-rt-c/) prototype from a single-threaded epoll reference into a **multi-threaded runtime environment for writeonce applications**: thread-per-core io_uring event loops at million-scale read/write concurrency, the whole database resident in RAM (one mmap arena, addressed per shard — no duplication), **ACID** commits that dual-write RAM-first-then-disk, and a boot path that loads the hard drive's state back into RAM before serving. Still one C file's worth of honesty per concern, still **zero dependencies beyond libc** — raw io_uring syscalls, no liburing.
|
||||
Evolve the [`prototypes/wo-rt-c/`](../../../../runtime/) prototype from a single-threaded epoll reference into a **multi-threaded runtime environment for writeonce applications**: thread-per-core io_uring event loops at million-scale read/write concurrency, the whole database resident in RAM (one mmap arena, addressed per shard — no duplication), **ACID** commits that dual-write RAM-first-then-disk, and a boot path that loads the hard drive's state back into RAM before serving. Still one C file's worth of honesty per concern, still **zero dependencies beyond libc** — raw io_uring syscalls, no liburing.
|
||||
|
||||
Each phase is the executable proving ground for the matching Rust plan (09–12): get the syscall sequence right here in a few hundred lines, then port with confidence.
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ Boot, before any listener opens: each thread replays its own WAL into its arena
|
|||
|
||||
`setrlimit(RLIMIT_NOFILE)` raised at boot. A small C load client under `prototypes/wo-rt-c/bench/` (keep-alive, pipelined GETs, latency timestamps — `wrk` would be an external dep). Measure honestly on the dev box and commit the numbers to the prototype README: aggregate read req/s across cores (goal order 10⁶/s on 8–16 cores), concurrent open connections (goal order 10⁵–10⁶; ~8 KB/conn + fd limits are the ceiling), commits/s under group fsync, p99 read latency under write load. ACID scripts: torn-WAL injection (atomicity), single-shard interleaving probe (isolation), the phase-D crash test under load (durability). A `just rt-c-bench` recipe runs it all.
|
||||
|
||||
**Exit (met):** measured on a 20-core box (table in the [prototype README](../../../../prototypes/wo-rt-c/README.md)): **908,916 reads/s p99 154 µs and 643,250 fsync-acked commits/s p99 177 µs** on 8 shards — vs Go `net/http` on 20 cores at 495k/355k with ~8× worse p99 and no durability (.NET unavailable on the box); 10k idle connections, 0 errors; only 2xx counted (the client tracks status codes). **The crash-under-load test found two real durability bugs the phase-D test missed** — an ack-armed-before-fsync race in `conn_continue` (route parks the response *during* `try_process`; the pre-check missed it) and an fd-reuse ABA hazard in batch ack-parking (fixed with per-connection generation stamps). After both fixes, three `kill -9`-mid-bench rounds at ~1–2M commits each showed **WAL records ≥ acked, every round** (one exact). Isolation: 300 concurrent commits → 300 distinct interleaved ids. Geometry scaling via `-DSLOTS_PER_SHARD` (bitmap region generalized to multi-page); 512 MB arena verified mlocked.
|
||||
**Exit (met):** measured on a 20-core box (table in the [prototype README](../../../../runtime/README.md)): **908,916 reads/s p99 154 µs and 643,250 fsync-acked commits/s p99 177 µs** on 8 shards — vs Go `net/http` on 20 cores at 495k/355k with ~8× worse p99 and no durability (.NET unavailable on the box); 10k idle connections, 0 errors; only 2xx counted (the client tracks status codes). **The crash-under-load test found two real durability bugs the phase-D test missed** — an ack-armed-before-fsync race in `conn_continue` (route parks the response *during* `try_process`; the pre-check missed it) and an fd-reuse ABA hazard in batch ack-parking (fixed with per-connection generation stamps). After both fixes, three `kill -9`-mid-bench rounds at ~1–2M commits each showed **WAL records ≥ acked, every round** (one exact). Isolation: 300 concurrent commits → 300 distinct interleaved ids. Geometry scaling via `-DSLOTS_PER_SHARD` (bitmap region generalized to multi-page); 512 MB arena verified mlocked.
|
||||
|
||||
## Non-scope
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ Boot, before any listener opens: each thread replays its own WAL into its arena
|
|||
|
||||
- [`../../09-concurrency-scaleout.md`](../../09-concurrency-scaleout.md) — the doctrine; this prototype is its executable proving ground (A↔09a, C↔09 decision 4, D↔09c).
|
||||
- [`../../10-storage-foundations.md`](../../10-storage-foundations.md), [`11-wal-and-recovery.md`](../../11-wal-and-recovery.md), [`12-engine-disk-cutover.md`](../../12-engine-disk-cutover.md) — the storage track phases B/D/E miniaturize.
|
||||
- [`../../../../prototypes/wo-rt-c/README.md`](../../../../prototypes/wo-rt-c/README.md) — current state and module map (phase 0).
|
||||
- [`../../../../prototypes/wo-rt-c/README.md`](../../../../runtime/README.md) — current state and module map (phase 0).
|
||||
- [`./01-architecture.md`](./01-architecture.md) — the target architecture traced through one memory address at million-connection concurrency, plus improvement proposals (seqlock reads, registered buffers, SEND_ZC, SQPOLL) that slot into phases C/F.
|
||||
- [`./02-single-binary.md`](./02-single-binary.md) — the end goal: how the `wo build` single binary runs on this runtime environment (Go model, not JVM — the kernel is statically linked into every app; the embedding contract between compiler payload and runtime kernel).
|
||||
- [`../../../../prototypes/wo-db/`](../../../../prototypes/wo-db/) — the query-layer sibling; one day a phase-G could splice its engine on top of this runtime.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# wo-rt-c architecture — one memory address, two spaces, a million connections
|
||||
|
||||
This document defines the runtime's architecture by following **one memory address** through user space, kernel space, and hardware, under a million connections reading and writing it concurrently — then suggests improvements. Companion docs: [`00-plan.md`](./00-plan.md) (the phases that build this), [`README.md`](../../../../prototypes/wo-rt-c/README.md) (phase-0 module map).
|
||||
This document defines the runtime's architecture by following **one memory address** through user space, kernel space, and hardware, under a million connections reading and writing it concurrently — then suggests improvements. Companion docs: [`00-plan.md`](./00-plan.md) (the phases that build this), [`README.md`](../../../../runtime/README.md) (phase-0 module map).
|
||||
|
||||
## The cast: one address
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
## Linux Kernel Features
|
||||
|
||||
Kernel primitives that the writeonce binary can leverage, mapped to the architectural needs identified in [docs/01-problem.md](../../01-problem.md) and [docs/02-recovery.md](../../02-recovery.md).
|
||||
Kernel primitives that the writeonce binary can leverage, mapped to the architectural needs identified in [docs/01-problem.md](../../../01-problem.md) and [docs/02-recovery.md](../../../02-recovery.md).
|
||||
|
||||
### Per-primitive reference cards
|
||||
|
||||
Each primitive has its own numbered file with the kernel source path (into [`reference/linux/`](../../../reference/linux/)), Rust FFI signature via `libc`, a minimal direct-syscall example, and the v1 port source. Use these when implementing the phase docs under [`docs/plan/`](../).
|
||||
Each primitive has its own numbered file with the kernel source path (into [`reference/linux/`](../../../../.dev/reference/linux/)), Rust FFI signature via `libc`, a minimal direct-syscall example, and the v1 port source. Use these when implementing the phase docs under [`docs/plan/`](../).
|
||||
|
||||
| # | Primitive | Used by |
|
||||
| --- | --- | --- |
|
||||
|
|
@ -143,4 +143,4 @@ Each pattern resolves to a set of inotify watch descriptors. When the watched se
|
|||
|
||||
## Related: the assembly policy
|
||||
|
||||
Every primitive above is reached via `libc::<syscall>` or `libc::syscall(SYS_*, ...)` — no custom assembly. The reasoning lives in [`../assembly/`](../assembly/) — three files covering why runtimes use asm at all ([`00-overview.md`](../assembly/00-overview.md)), what Go's [`reference/go/src/runtime/*.s`](../../../reference/go/src/runtime/) actually contains ([`01-go-runtime-asm.md`](../assembly/01-go-runtime-asm.md)), and the writeonce policy that all of it is replaced by Rust stdlib + libc ([`02-writeonce-stance.md`](../assembly/02-writeonce-stance.md)).
|
||||
Every primitive above is reached via `libc::<syscall>` or `libc::syscall(SYS_*, ...)` — no custom assembly. The reasoning lives in [`../assembly/`](../assembly/) — three files covering why runtimes use asm at all ([`00-overview.md`](../assembly/00-overview.md)), what Go's [`reference/go/src/runtime/*.s`](../../../../.dev/reference/go/src/runtime/) actually contains ([`01-go-runtime-asm.md`](../assembly/01-go-runtime-asm.md)), and the writeonce policy that all of it is replaced by Rust stdlib + libc ([`02-writeonce-stance.md`](../assembly/02-writeonce-stance.md)).
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ Event-driven I/O multiplexing. One `epoll_fd` watches many fds for readiness; `e
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/eventpoll.c`](../../../reference/linux/fs/eventpoll.c) | All three syscalls (`epoll_create1`, `epoll_ctl`, `epoll_wait`) live here. Grep for `SYSCALL_DEFINE`. |
|
||||
| [`reference/linux/include/uapi/linux/eventpoll.h`](../../../reference/linux/include/uapi/linux/eventpoll.h) | `struct epoll_event`, `EPOLL_*` flags, the userspace-facing ABI. |
|
||||
| [`reference/linux/fs/eventpoll.c`](../../../../.dev/reference/linux/fs/eventpoll.c) | All three syscalls (`epoll_create1`, `epoll_ctl`, `epoll_wait`) live here. Grep for `SYSCALL_DEFINE`. |
|
||||
| [`reference/linux/include/uapi/linux/eventpoll.h`](../../../../.dev/reference/linux/include/uapi/linux/eventpoll.h) | `struct epoll_event`, `EPOLL_*` flags, the userspace-facing ABI. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -68,8 +68,8 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
Every runtime phase that touches I/O: [`02-event-loop-epoll.md`](../02-event-loop-epoll.md), [`03-hand-rolled-http.md`](../03-hand-rolled-http.md), [`07-inotify-content-watcher.md`](../07-inotify-content-watcher.md), [`08-sendfile-static-assets.md`](../08-sendfile-static-assets.md).
|
||||
Every runtime phase that touches I/O: [`02-event-loop-epoll.md`](../../done/02-event-loop-epoll.md), [`03-hand-rolled-http.md`](../../done/03-hand-rolled-http.md), [`07-inotify-content-watcher.md`](../../07-inotify-content-watcher.md), [`08-sendfile-static-assets.md`](../../08-sendfile-static-assets.md).
|
||||
|
||||
## v1 port source
|
||||
|
||||
[`reference/crates/wo-event/src/epoll.rs`](../../../reference/crates/wo-event/src/epoll.rs) (183 LOC) — already wraps all three syscalls with a safe `EventLoop { register, deregister, wait_once }` facade.
|
||||
[`reference/crates/wo-event/src/epoll.rs`](../../../../.dev/reference/crates/wo-event/src/epoll.rs) (183 LOC) — already wraps all three syscalls with a safe `EventLoop { register, deregister, wait_once }` facade.
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ Counter as a file descriptor. `write(fd, &n, 8)` adds `n` to the counter; `read(
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/eventfd.c`](../../../reference/linux/fs/eventfd.c) | `SYSCALL_DEFINE2(eventfd, ...)`, `struct eventfd_ctx`, read/write handlers. |
|
||||
| [`reference/linux/include/uapi/linux/eventfd.h`](../../../reference/linux/include/uapi/linux/eventfd.h) | `EFD_*` flags. |
|
||||
| [`reference/linux/fs/eventfd.c`](../../../../.dev/reference/linux/fs/eventfd.c) | `SYSCALL_DEFINE2(eventfd, ...)`, `struct eventfd_ctx`, read/write handlers. |
|
||||
| [`reference/linux/include/uapi/linux/eventfd.h`](../../../../.dev/reference/linux/include/uapi/linux/eventfd.h) | `EFD_*` flags. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -59,8 +59,8 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
[`02-event-loop-epoll.md`](../02-event-loop-epoll.md) — wake the loop for shutdown or internal work. Future `sub` crate ([`09-native-subscriptions`], not yet planned) uses it to signal that a subscriber queue has drained.
|
||||
[`02-event-loop-epoll.md`](../../done/02-event-loop-epoll.md) — wake the loop for shutdown or internal work. Future `sub` crate ([`09-native-subscriptions`], not yet planned) uses it to signal that a subscriber queue has drained.
|
||||
|
||||
## v1 port source
|
||||
|
||||
[`reference/crates/wo-event/src/eventfd.rs`](../../../reference/crates/wo-event/src/eventfd.rs) (66 LOC) — `EventFd { new, write, read, as_raw_fd }`.
|
||||
[`reference/crates/wo-event/src/eventfd.rs`](../../../../.dev/reference/crates/wo-event/src/eventfd.rs) (66 LOC) — `EventFd { new, write, read, as_raw_fd }`.
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ Timers as file descriptors. Set an expiry with `timerfd_settime`, `read` the fd
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/timerfd.c`](../../../reference/linux/fs/timerfd.c) | All three syscalls (`timerfd_create`, `timerfd_settime`, `timerfd_gettime`). |
|
||||
| [`reference/linux/include/uapi/linux/timerfd.h`](../../../reference/linux/include/uapi/linux/timerfd.h) | `TFD_*` flags. |
|
||||
| [`reference/linux/fs/timerfd.c`](../../../../.dev/reference/linux/fs/timerfd.c) | All three syscalls (`timerfd_create`, `timerfd_settime`, `timerfd_gettime`). |
|
||||
| [`reference/linux/include/uapi/linux/timerfd.h`](../../../../.dev/reference/linux/include/uapi/linux/timerfd.h) | `TFD_*` flags. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -70,8 +70,8 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
[`02-event-loop-epoll.md`](../02-event-loop-epoll.md) — housekeeping timers. [`07-inotify-content-watcher.md`](../07-inotify-content-watcher.md) — 150 ms debounce window after an inotify burst. Future subscription phase — keepalive pings to long-lived connections.
|
||||
[`02-event-loop-epoll.md`](../../done/02-event-loop-epoll.md) — housekeeping timers. [`07-inotify-content-watcher.md`](../../07-inotify-content-watcher.md) — 150 ms debounce window after an inotify burst. Future subscription phase — keepalive pings to long-lived connections.
|
||||
|
||||
## v1 port source
|
||||
|
||||
[`reference/crates/wo-event/src/timerfd.rs`](../../../reference/crates/wo-event/src/timerfd.rs) (91 LOC) — `TimerFd { oneshot(dur), periodic(dur), disarm, read_expirations }`.
|
||||
[`reference/crates/wo-event/src/timerfd.rs`](../../../../.dev/reference/crates/wo-event/src/timerfd.rs) (91 LOC) — `TimerFd { oneshot(dur), periodic(dur), disarm, read_expirations }`.
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ Unix signals as file descriptors. `signalfd(fd, mask)` installs a mask on the pr
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/signalfd.c`](../../../reference/linux/fs/signalfd.c) | `SYSCALL_DEFINE4(signalfd4, ...)` + `signalfd_dequeue`. |
|
||||
| [`reference/linux/include/uapi/linux/signalfd.h`](../../../reference/linux/include/uapi/linux/signalfd.h) | `struct signalfd_siginfo`, `SFD_*` flags. |
|
||||
| [`reference/linux/kernel/signal.c`](../../../reference/linux/kernel/signal.c) | Background: `sigprocmask`, pending-signal dequeue. |
|
||||
| [`reference/linux/fs/signalfd.c`](../../../../.dev/reference/linux/fs/signalfd.c) | `SYSCALL_DEFINE4(signalfd4, ...)` + `signalfd_dequeue`. |
|
||||
| [`reference/linux/include/uapi/linux/signalfd.h`](../../../../.dev/reference/linux/include/uapi/linux/signalfd.h) | `struct signalfd_siginfo`, `SFD_*` flags. |
|
||||
| [`reference/linux/kernel/signal.c`](../../../../.dev/reference/linux/kernel/signal.c) | Background: `sigprocmask`, pending-signal dequeue. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -70,8 +70,8 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
[`04-cutover-remove-tokio-axum.md`](../04-cutover-remove-tokio-axum.md) — replaces `tokio::signal::ctrl_c()` for graceful shutdown. Every subsequent phase inherits this pattern.
|
||||
[`04-cutover-remove-tokio-axum.md`](../../done/04-cutover-remove-tokio-axum.md) — replaces `tokio::signal::ctrl_c()` for graceful shutdown. Every subsequent phase inherits this pattern.
|
||||
|
||||
## v1 port source
|
||||
|
||||
[`reference/crates/wo-event/src/signalfd.rs`](../../../reference/crates/wo-event/src/signalfd.rs) (62 LOC) — `SignalFd::new(&[SIGINT, SIGTERM]) -> SignalFd` with a safe `read_signo()` helper.
|
||||
[`reference/crates/wo-event/src/signalfd.rs`](../../../../.dev/reference/crates/wo-event/src/signalfd.rs) (62 LOC) — `SignalFd::new(&[SIGINT, SIGTERM]) -> SignalFd` with a safe `read_signo()` helper.
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ Filesystem event notifications as a file descriptor. `inotify_add_watch(dir, mas
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/notify/inotify/inotify_user.c`](../../../reference/linux/fs/notify/inotify/inotify_user.c) | `SYSCALL_DEFINE1(inotify_init1, ...)`, `SYSCALL_DEFINE3(inotify_add_watch, ...)`, `SYSCALL_DEFINE2(inotify_rm_watch, ...)`. |
|
||||
| [`reference/linux/fs/notify/inotify/inotify_fsnotify.c`](../../../reference/linux/fs/notify/inotify/inotify_fsnotify.c) | The fsnotify backend that feeds events into the fd. |
|
||||
| [`reference/linux/include/uapi/linux/inotify.h`](../../../reference/linux/include/uapi/linux/inotify.h) | `struct inotify_event`, `IN_*` masks. |
|
||||
| [`reference/linux/fs/notify/inotify/inotify_user.c`](../../../../.dev/reference/linux/fs/notify/inotify/inotify_user.c) | `SYSCALL_DEFINE1(inotify_init1, ...)`, `SYSCALL_DEFINE3(inotify_add_watch, ...)`, `SYSCALL_DEFINE2(inotify_rm_watch, ...)`. |
|
||||
| [`reference/linux/fs/notify/inotify/inotify_fsnotify.c`](../../../../.dev/reference/linux/fs/notify/inotify/inotify_fsnotify.c) | The fsnotify backend that feeds events into the fd. |
|
||||
| [`reference/linux/include/uapi/linux/inotify.h`](../../../../.dev/reference/linux/include/uapi/linux/inotify.h) | `struct inotify_event`, `IN_*` masks. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -81,8 +81,8 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
[`07-inotify-content-watcher.md`](../07-inotify-content-watcher.md) — the Stage-3 hot-reload feature. Future `sub` crate — the register-macro subscription model in [`00-linux.md § Database Subscription`](./00-linux.md#database-subscription).
|
||||
[`07-inotify-content-watcher.md`](../../07-inotify-content-watcher.md) — the Stage-3 hot-reload feature. Future `sub` crate — the register-macro subscription model in [`00-linux.md § Database Subscription`](./00-linux.md#database-subscription).
|
||||
|
||||
## v1 port source
|
||||
|
||||
[`reference/crates/wo-watch/src/lib.rs`](../../../reference/crates/wo-watch/src/lib.rs) (280 LOC) — already does recursive watch setup, event parsing, and path resolution via a `wd → PathBuf` map.
|
||||
[`reference/crates/wo-watch/src/lib.rs`](../../../../.dev/reference/crates/wo-watch/src/lib.rs) (280 LOC) — already does recursive watch setup, event parsing, and path resolution via a `wd → PathBuf` map.
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ Zero-copy transfer from a file fd to a socket fd. The kernel splices pages direc
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/read_write.c`](../../../reference/linux/fs/read_write.c) | `SYSCALL_DEFINE4(sendfile, ...)` and `SYSCALL_DEFINE4(sendfile64, ...)`. Modern glibc aliases the first to the second; the syscalls are distinguished by the offset type. |
|
||||
| [`reference/linux/fs/splice.c`](../../../reference/linux/fs/splice.c) | Internally `sendfile` delegates to `splice_direct_to_actor`. Related — see [07-splice.md](./07-splice.md) if you ever need the more general fd-to-fd pipe path. |
|
||||
| [`reference/linux/fs/read_write.c`](../../../../.dev/reference/linux/fs/read_write.c) | `SYSCALL_DEFINE4(sendfile, ...)` and `SYSCALL_DEFINE4(sendfile64, ...)`. Modern glibc aliases the first to the second; the syscalls are distinguished by the offset type. |
|
||||
| [`reference/linux/fs/splice.c`](../../../../.dev/reference/linux/fs/splice.c) | Internally `sendfile` delegates to `splice_direct_to_actor`. Related — see [07-splice.md](./07-splice.md) if you ever need the more general fd-to-fd pipe path. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -71,8 +71,8 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
[`08-sendfile-static-assets.md`](../08-sendfile-static-assets.md) — the `GET /static/...` handler. Future `##ui` SSR output bundles go through the same path.
|
||||
[`08-sendfile-static-assets.md`](../../08-sendfile-static-assets.md) — the `GET /static/...` handler. Future `##ui` SSR output bundles go through the same path.
|
||||
|
||||
## v1 port source
|
||||
|
||||
[`reference/crates/wo-serve/src/sendfile.rs`](../../../reference/crates/wo-serve/src/sendfile.rs) (109 LOC) — `send_file(sock, path) -> Result` wrapping the loop + `EAGAIN` handling.
|
||||
[`reference/crates/wo-serve/src/sendfile.rs`](../../../../.dev/reference/crates/wo-serve/src/sendfile.rs) (109 LOC) — `send_file(sock, path) -> Result` wrapping the loop + `EAGAIN` handling.
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
Ring-buffer based async I/O (Linux 5.1+, mature 5.11+). Two lock-free SPSC rings shared between userspace and kernel: submissions (SQEs) go in one, completions (CQEs) come out of the other. Batched, zero-syscall submission (with SQPOLL), zero-copy where the underlying op allows. Successor to `epoll` + `libaio` for the storage engine's WAL fsync path and — eventually — the HTTP server's accept/recv/send path.
|
||||
|
||||
**Not on the runtime's critical path in phases 02–08.** Phase 02 uses `epoll`. `io_uring` comes in during [Phase 3 — In-Memory Engine](../runtime/database/03-inmemory-engine.md) for the WAL's group-commit fsync loop. This card is the reference for that phase.
|
||||
**Not on the runtime's critical path in phases 02–08.** Phase 02 uses `epoll`. `io_uring` comes in during [Phase 3 — In-Memory Engine](../../../runtime/database/03-inmemory-engine.md) for the WAL's group-commit fsync loop. This card is the reference for that phase.
|
||||
|
||||
## Kernel source
|
||||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/io_uring/`](../../../reference/linux/io_uring/) | Whole subsystem. Start with `io_uring.c` (ring setup + submission/completion) and `fs.c` (fsync op). |
|
||||
| [`reference/linux/io_uring/io_uring.c`](../../../reference/linux/io_uring/io_uring.c) | `SYSCALL_DEFINE2(io_uring_setup, ...)`, `SYSCALL_DEFINE6(io_uring_enter, ...)`, `SYSCALL_DEFINE4(io_uring_register, ...)`. |
|
||||
| [`reference/linux/include/uapi/linux/io_uring.h`](../../../reference/linux/include/uapi/linux/io_uring.h) | `struct io_uring_sqe`, `io_uring_cqe`, `io_uring_params`, every `IORING_*` flag. |
|
||||
| [`reference/linux/io_uring/`](../../../../.dev/reference/linux/io_uring/) | Whole subsystem. Start with `io_uring.c` (ring setup + submission/completion) and `fs.c` (fsync op). |
|
||||
| [`reference/linux/io_uring/io_uring.c`](../../../../.dev/reference/linux/io_uring/io_uring.c) | `SYSCALL_DEFINE2(io_uring_setup, ...)`, `SYSCALL_DEFINE6(io_uring_enter, ...)`, `SYSCALL_DEFINE4(io_uring_register, ...)`. |
|
||||
| [`reference/linux/include/uapi/linux/io_uring.h`](../../../../.dev/reference/linux/include/uapi/linux/io_uring.h) | `struct io_uring_sqe`, `io_uring_cqe`, `io_uring_params`, every `IORING_*` flag. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ Full working code is ~200 LOC including error handling — see `liburing` source
|
|||
|
||||
## Used by
|
||||
|
||||
Phase 3 of the database series — see [`docs/runtime/database/03-inmemory-engine.md`](../runtime/database/03-inmemory-engine.md). Specifically the WAL fsync path: link `WRITE` → `FSYNC` SQEs, submit many per tick, reap completions to ack committed transactions. Also the natural upgrade target for the HTTP server once Phase 4 adds the native wire protocol.
|
||||
Phase 3 of the database series — see [`docs/runtime/database/03-inmemory-engine.md`](../../../runtime/database/03-inmemory-engine.md). Specifically the WAL fsync path: link `WRITE` → `FSYNC` SQEs, submit many per tick, reap completions to ack committed transactions. Also the natural upgrade target for the HTTP server once Phase 4 adds the native wire protocol.
|
||||
|
||||
## v1 port source
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ Central to Phase 3's storage engine: segment files are `mmap`ed read-only for O(
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/mm/mmap.c`](../../../reference/linux/mm/mmap.c) | VMA creation, `SYSCALL_DEFINE6(mmap, ...)`, `SYSCALL_DEFINE2(munmap, ...)`. |
|
||||
| [`reference/linux/mm/madvise.c`](../../../reference/linux/mm/madvise.c) | `SYSCALL_DEFINE3(madvise, ...)` + every `MADV_*` handler. |
|
||||
| [`reference/linux/include/uapi/linux/mman.h`](../../../reference/linux/include/uapi/linux/mman.h) | `MAP_*` flags, huge-page sizing macros. |
|
||||
| [`reference/linux/mm/mmap.c`](../../../../.dev/reference/linux/mm/mmap.c) | VMA creation, `SYSCALL_DEFINE6(mmap, ...)`, `SYSCALL_DEFINE2(munmap, ...)`. |
|
||||
| [`reference/linux/mm/madvise.c`](../../../../.dev/reference/linux/mm/madvise.c) | `SYSCALL_DEFINE3(madvise, ...)` + every `MADV_*` handler. |
|
||||
| [`reference/linux/include/uapi/linux/mman.h`](../../../../.dev/reference/linux/include/uapi/linux/mman.h) | `MAP_*` flags, huge-page sizing macros. |
|
||||
| POSIX `<sys/mman.h>` | The other half of the constants (`PROT_*`, `MADV_*`). Usually folded into `linux/mman.h` by libc. |
|
||||
|
||||
## Man pages
|
||||
|
|
@ -93,7 +93,7 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
Phase 3 of the database series — see [`docs/runtime/database/03-inmemory-engine.md`](../runtime/database/03-inmemory-engine.md) § Linux Tuning Checklist. The relational B+ tree, the LSM memtables' on-disk segments, and the document store's arenas all live behind `mmap`.
|
||||
Phase 3 of the database series — see [`docs/runtime/database/03-inmemory-engine.md`](../../../runtime/database/03-inmemory-engine.md) § Linux Tuning Checklist. The relational B+ tree, the LSM memtables' on-disk segments, and the document store's arenas all live behind `mmap`.
|
||||
|
||||
## v1 port source
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ Together they form the backbone of the storage engine's on-disk layout: segment
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/open.c`](../../../reference/linux/fs/open.c) | `SYSCALL_DEFINE4(fallocate, ...)`. The syscall delegates to `file->f_op->fallocate` — per-filesystem. |
|
||||
| [`reference/linux/fs/read_write.c`](../../../reference/linux/fs/read_write.c) | `SYSCALL_DEFINE4(pread64, ...)`, `SYSCALL_DEFINE4(pwrite64, ...)`, `SYSCALL_DEFINE6(pwritev2, ...)`. |
|
||||
| [`reference/linux/include/uapi/linux/falloc.h`](../../../reference/linux/include/uapi/linux/falloc.h) | `FALLOC_FL_*` flags. |
|
||||
| [`reference/linux/fs/open.c`](../../../../.dev/reference/linux/fs/open.c) | `SYSCALL_DEFINE4(fallocate, ...)`. The syscall delegates to `file->f_op->fallocate` — per-filesystem. |
|
||||
| [`reference/linux/fs/read_write.c`](../../../../.dev/reference/linux/fs/read_write.c) | `SYSCALL_DEFINE4(pread64, ...)`, `SYSCALL_DEFINE4(pwrite64, ...)`, `SYSCALL_DEFINE6(pwritev2, ...)`. |
|
||||
| [`reference/linux/include/uapi/linux/falloc.h`](../../../../.dev/reference/linux/include/uapi/linux/falloc.h) | `FALLOC_FL_*` flags. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ Not on the runtime's critical path today; useful when the runtime grows a superv
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/kernel/pid.c`](../../../reference/linux/kernel/pid.c) | `SYSCALL_DEFINE2(pidfd_open, ...)`, `pidfd_create`, `pidfd_pid`. |
|
||||
| [`reference/linux/kernel/signal.c`](../../../reference/linux/kernel/signal.c) | `SYSCALL_DEFINE4(pidfd_send_signal, ...)`. |
|
||||
| [`reference/linux/kernel/fork.c`](../../../reference/linux/kernel/fork.c) | `clone3` — the only way to get a pidfd atomically with spawn. |
|
||||
| [`reference/linux/include/uapi/linux/pidfd.h`](../../../reference/linux/include/uapi/linux/pidfd.h) | `PIDFD_*` flags. |
|
||||
| [`reference/linux/kernel/pid.c`](../../../../.dev/reference/linux/kernel/pid.c) | `SYSCALL_DEFINE2(pidfd_open, ...)`, `pidfd_create`, `pidfd_pid`. |
|
||||
| [`reference/linux/kernel/signal.c`](../../../../.dev/reference/linux/kernel/signal.c) | `SYSCALL_DEFINE4(pidfd_send_signal, ...)`. |
|
||||
| [`reference/linux/kernel/fork.c`](../../../../.dev/reference/linux/kernel/fork.c) | `clone3` — the only way to get a pidfd atomically with spawn. |
|
||||
| [`reference/linux/include/uapi/linux/pidfd.h`](../../../../.dev/reference/linux/include/uapi/linux/pidfd.h) | `PIDFD_*` flags. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ Useful for the storage engine's transient work: building an index in memory befo
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/mm/memfd.c`](../../../reference/linux/mm/memfd.c) | `SYSCALL_DEFINE2(memfd_create, ...)` + seal ops. |
|
||||
| [`reference/linux/include/uapi/linux/memfd.h`](../../../reference/linux/include/uapi/linux/memfd.h) | `MFD_*` flags. |
|
||||
| [`reference/linux/include/uapi/linux/fcntl.h`](../../../reference/linux/include/uapi/linux/fcntl.h) | `F_ADD_SEALS`, `F_GET_SEALS`, `F_SEAL_*` constants. Sealing is a `fcntl(F_ADD_SEALS, ...)` operation on the memfd. |
|
||||
| [`reference/linux/mm/memfd.c`](../../../../.dev/reference/linux/mm/memfd.c) | `SYSCALL_DEFINE2(memfd_create, ...)` + seal ops. |
|
||||
| [`reference/linux/include/uapi/linux/memfd.h`](../../../../.dev/reference/linux/include/uapi/linux/memfd.h) | `MFD_*` flags. |
|
||||
| [`reference/linux/include/uapi/linux/fcntl.h`](../../../../.dev/reference/linux/include/uapi/linux/fcntl.h) | `F_ADD_SEALS`, `F_GET_SEALS`, `F_SEAL_*` constants. Sealing is a `fcntl(F_ADD_SEALS, ...)` operation on the memfd. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ unsafe {
|
|||
|
||||
## Used by
|
||||
|
||||
Phase 3 of the database series — index-build-then-swap (mentioned in [03-inmemory-engine.md § Recovery](../runtime/database/03-inmemory-engine.md#recovery) as "build a .seg index in memory before atomically swapping it to disk"). Also any future IPC story with worker processes (Phase 6 full-stack with multiple render workers, say).
|
||||
Phase 3 of the database series — index-build-then-swap (mentioned in [03-inmemory-engine.md § Recovery](../../../runtime/database/03-inmemory-engine.md#recovery) as "build a .seg index in memory before atomically swapping it to disk"). Also any future IPC story with worker processes (Phase 6 full-stack with multiple render workers, say).
|
||||
|
||||
## v1 port source
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ The previous cards cover positional I/O ([`09-fallocate.md`](./09-fallocate.md))
|
|||
|
||||
| Postgres call | Wraps | Where |
|
||||
| --- | --- | --- |
|
||||
| `pg_pwrite()` | `pwrite64` | [`storage/file/fd.c`](../../../../reference/postgresql/src/backend/storage/file/fd.c) — every block-aligned write. |
|
||||
| `pg_fsync()` | `fsync` (or platform variant) | [`storage/file/fd.c`](../../../../reference/postgresql/src/backend/storage/file/fd.c) — wraps `wal_sync_method` GUC dispatch. |
|
||||
| `pg_pwrite()` | `pwrite64` | [`storage/file/fd.c`](../../../../.dev/reference/postgresql/src/backend/storage/file/fd.c) — every block-aligned write. |
|
||||
| `pg_fsync()` | `fsync` (or platform variant) | [`storage/file/fd.c`](../../../../.dev/reference/postgresql/src/backend/storage/file/fd.c) — wraps `wal_sync_method` GUC dispatch. |
|
||||
| `pg_fdatasync()` | `fdatasync` | Same. Selected when `wal_sync_method = fdatasync`. |
|
||||
| Async writeback | `sync_file_range` | [`access/transam/xlog.c`](../../../../reference/postgresql/src/backend/access/transam/xlog.c) — `issue_xlog_fsync` calls `sync_file_range(SYNC_FILE_RANGE_WRITE)` to start I/O on the WAL ahead of the durability barrier. |
|
||||
| Async writeback | `sync_file_range` | [`access/transam/xlog.c`](../../../../.dev/reference/postgresql/src/backend/access/transam/xlog.c) — `issue_xlog_fsync` calls `sync_file_range(SYNC_FILE_RANGE_WRITE)` to start I/O on the WAL ahead of the durability barrier. |
|
||||
|
||||
The Postgres GUC matrix (`wal_sync_method`) lets the operator pick between `fsync`, `fdatasync`, `open_sync`, `open_datasync`, `fsync_writethrough`. **Writeonce picks one** — `fdatasync` for the WAL, `fsync` for control files and segment rollovers — and ships it.
|
||||
|
||||
|
|
@ -26,10 +26,10 @@ The Postgres GUC matrix (`wal_sync_method`) lets the operator pick between `fsyn
|
|||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| [`reference/linux/fs/read_write.c`](../../../reference/linux/fs/read_write.c) | `SYSCALL_DEFINE4(pread64, ...)`, `SYSCALL_DEFINE4(pwrite64, ...)`, `SYSCALL_DEFINE6(pwritev2, ...)`. |
|
||||
| [`reference/linux/fs/sync.c`](../../../reference/linux/fs/sync.c) | `SYSCALL_DEFINE1(fsync, ...)`, `SYSCALL_DEFINE1(fdatasync, ...)`, `SYSCALL_DEFINE4(sync_file_range, ...)`. |
|
||||
| [`reference/linux/include/uapi/asm-generic/fcntl.h`](../../../reference/linux/include/uapi/asm-generic/fcntl.h) | `O_SYNC`, `O_DSYNC`, `O_DIRECT`. |
|
||||
| [`reference/linux/Documentation/filesystems/ext4/journal.rst`](../../../reference/linux/Documentation/filesystems/ext4/journal.rst) | What ext4's journal commits when `fsync` runs. Worth understanding what the kernel actually does on the durability path. |
|
||||
| [`reference/linux/fs/read_write.c`](../../../../.dev/reference/linux/fs/read_write.c) | `SYSCALL_DEFINE4(pread64, ...)`, `SYSCALL_DEFINE4(pwrite64, ...)`, `SYSCALL_DEFINE6(pwritev2, ...)`. |
|
||||
| [`reference/linux/fs/sync.c`](../../../../.dev/reference/linux/fs/sync.c) | `SYSCALL_DEFINE1(fsync, ...)`, `SYSCALL_DEFINE1(fdatasync, ...)`, `SYSCALL_DEFINE4(sync_file_range, ...)`. |
|
||||
| [`reference/linux/include/uapi/asm-generic/fcntl.h`](../../../../.dev/reference/linux/include/uapi/asm-generic/fcntl.h) | `O_SYNC`, `O_DSYNC`, `O_DIRECT`. |
|
||||
| [`reference/linux/Documentation/filesystems/ext4/journal.rst`](../../../../.dev/reference/linux/Documentation/filesystems/ext4/journal.rst) | What ext4's journal commits when `fsync` runs. Worth understanding what the kernel actually does on the durability path. |
|
||||
|
||||
## Man pages
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# PostgreSQL — storage subsystem reference
|
||||
|
||||
These cards exist to make the Postgres backend a useful **library of patterns** for writeonce's persistent-storage phases (10–12) without inviting a multi-process port. Each card pulls one subsystem out of [`reference/postgresql/src/backend/`](../../../../reference/postgresql/src/backend/) — paths into the Postgres tree, the underlying *idea*, and the writeonce translation.
|
||||
These cards exist to make the Postgres backend a useful **library of patterns** for writeonce's persistent-storage phases (10–12) without inviting a multi-process port. Each card pulls one subsystem out of [`reference/postgresql/src/backend/`](../../../../.dev/reference/postgresql/src/backend/) — paths into the Postgres tree, the underlying *idea*, and the writeonce translation.
|
||||
|
||||
The symlink is user-specific:
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ The symlink is user-specific:
|
|||
ln -s /home/shoney/projects/postgresql reference/postgresql
|
||||
```
|
||||
|
||||
Gitignored — see [`.gitignore`](../../../../.gitignore). Pair it with [`reference/linux`](../../../../reference/linux) and [`reference/go`](../../../../reference/go) if not already linked.
|
||||
Gitignored — see [`.gitignore`](../../../../.gitignore). Pair it with [`reference/linux`](../../../../.dev/reference/linux) and [`reference/go`](../../../../.dev/reference/go) if not already linked.
|
||||
|
||||
## Per-subsystem cards
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ No separate process. No shared-buffer pinning. No dynamic-shared-memory coordina
|
|||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| [`storage/buffer/bufmgr.c`](../../../../reference/postgresql/src/backend/storage/buffer/bufmgr.c) | Page cache front-door: `ReadBuffer`, `BufferGetPage`, `MarkBufferDirty`, `FlushBuffer`. Tracks dirty bit per buffer; pinning prevents eviction. |
|
||||
| [`storage/buffer/freelist.c`](../../../../reference/postgresql/src/backend/storage/buffer/freelist.c) | Clock-sweep eviction policy. Buffers with `usage_count = 0` and `pin_count = 0` are eviction candidates; usage decremented on every sweep pass, incremented on access. |
|
||||
| [`storage/buffer/buf_table.c`](../../../../reference/postgresql/src/backend/storage/buffer/buf_table.c) | Hash table from `(file, block)` → buffer slot. The lookup that `ReadBuffer` does. |
|
||||
| [`postmaster/checkpointer.c`](../../../../reference/postgresql/src/backend/postmaster/checkpointer.c) | The checkpointer process. Triggered by time (`checkpoint_timeout`), WAL volume (`max_wal_size`), or signal. Runs `BufferSync()` to flush dirty buffers, then `CreateCheckPoint()` to update the control file. |
|
||||
| [`postmaster/bgwriter.c`](../../../../reference/postgresql/src/backend/postmaster/bgwriter.c) | Continuously trickles dirty pages to disk between checkpoints. Smooths the I/O burst the checkpointer would cause. |
|
||||
| [`storage/buffer/README`](../../../../reference/postgresql/src/backend/storage/buffer/README) | Overview of the pinning, locking, and replacement policy. Worth reading. |
|
||||
| [`storage/buffer/bufmgr.c`](../../../../.dev/reference/postgresql/src/backend/storage/buffer/bufmgr.c) | Page cache front-door: `ReadBuffer`, `BufferGetPage`, `MarkBufferDirty`, `FlushBuffer`. Tracks dirty bit per buffer; pinning prevents eviction. |
|
||||
| [`storage/buffer/freelist.c`](../../../../.dev/reference/postgresql/src/backend/storage/buffer/freelist.c) | Clock-sweep eviction policy. Buffers with `usage_count = 0` and `pin_count = 0` are eviction candidates; usage decremented on every sweep pass, incremented on access. |
|
||||
| [`storage/buffer/buf_table.c`](../../../../.dev/reference/postgresql/src/backend/storage/buffer/buf_table.c) | Hash table from `(file, block)` → buffer slot. The lookup that `ReadBuffer` does. |
|
||||
| [`postmaster/checkpointer.c`](../../../../.dev/reference/postgresql/src/backend/postmaster/checkpointer.c) | The checkpointer process. Triggered by time (`checkpoint_timeout`), WAL volume (`max_wal_size`), or signal. Runs `BufferSync()` to flush dirty buffers, then `CreateCheckPoint()` to update the control file. |
|
||||
| [`postmaster/bgwriter.c`](../../../../.dev/reference/postgresql/src/backend/postmaster/bgwriter.c) | Continuously trickles dirty pages to disk between checkpoints. Smooths the I/O burst the checkpointer would cause. |
|
||||
| [`storage/buffer/README`](../../../../.dev/reference/postgresql/src/backend/storage/buffer/README) | Overview of the pinning, locking, and replacement policy. Worth reading. |
|
||||
|
||||
## The page-cache idea worth porting
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ Writeonce's phase 10 starts simpler — variable-length records, no pages. Phase
|
|||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| [`storage/page/bufpage.c`](../../../../reference/postgresql/src/backend/storage/page/bufpage.c) | Page initialization (`PageInit`), line-pointer manipulation, free-space accounting. |
|
||||
| [`storage/page/checksum.c`](../../../../reference/postgresql/src/backend/storage/page/checksum.c) | The page checksum algorithm — CRC32C-style with a Postgres-specific finalization. Optional, enabled at cluster init. |
|
||||
| [`storage/page/itemptr.c`](../../../../reference/postgresql/src/backend/storage/page/itemptr.c) | Item pointer (`ItemPointerData`) — `(block_number, offset_within_page)` 6-byte tuple address. The on-disk equivalent of writeonce's `(TypeName, SegmentOffset)`. |
|
||||
| [`include/storage/bufpage.h`](../../../../reference/postgresql/src/include/storage/bufpage.h) | The header-file definition. Read this first — it's the spec. |
|
||||
| [`storage/page/README`](../../../../reference/postgresql/src/backend/storage/page/README) | One-page overview of the slotted-page model and how checksums interact with WAL. |
|
||||
| [`storage/page/bufpage.c`](../../../../.dev/reference/postgresql/src/backend/storage/page/bufpage.c) | Page initialization (`PageInit`), line-pointer manipulation, free-space accounting. |
|
||||
| [`storage/page/checksum.c`](../../../../.dev/reference/postgresql/src/backend/storage/page/checksum.c) | The page checksum algorithm — CRC32C-style with a Postgres-specific finalization. Optional, enabled at cluster init. |
|
||||
| [`storage/page/itemptr.c`](../../../../.dev/reference/postgresql/src/backend/storage/page/itemptr.c) | Item pointer (`ItemPointerData`) — `(block_number, offset_within_page)` 6-byte tuple address. The on-disk equivalent of writeonce's `(TypeName, SegmentOffset)`. |
|
||||
| [`include/storage/bufpage.h`](../../../../.dev/reference/postgresql/src/include/storage/bufpage.h) | The header-file definition. Read this first — it's the spec. |
|
||||
| [`storage/page/README`](../../../../.dev/reference/postgresql/src/backend/storage/page/README) | One-page overview of the slotted-page model and how checksums interact with WAL. |
|
||||
|
||||
## The Postgres page header (24 bytes)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ The writeonce equivalent is **per-type segment files** (`data/<TypeName>.seg`).
|
|||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| [`storage/smgr/smgr.c`](../../../../reference/postgresql/src/backend/storage/smgr/smgr.c) | Front-door API. `smgropen`, `smgrread`, `smgrwrite`, `smgrextend`, `smgrdounlink`. Holds the `SMgrRelation` cache. |
|
||||
| [`storage/smgr/md.c`](../../../../reference/postgresql/src/backend/storage/smgr/md.c) | The actual implementation against the kernel. Manages `MdfdVec` (open file descriptor handles per segment number), opens missing segments lazily. |
|
||||
| [`storage/smgr/bulk_write.c`](../../../../reference/postgresql/src/backend/storage/smgr/bulk_write.c) | Optimized path for bulk-loading: writes directly to `smgrwrite` without going through shared buffers. Useful for `COPY` / `CREATE INDEX` + the recovery path's wal-replay-rebuilds-pages flow. |
|
||||
| [`storage/smgr/README`](../../../../reference/postgresql/src/backend/storage/smgr/README) | Brief but worth reading — explains the relfilenode → file naming convention and how `RELSEG_SIZE` interacts with 32-bit-fs-size historical limits. |
|
||||
| [`storage/smgr/smgr.c`](../../../../.dev/reference/postgresql/src/backend/storage/smgr/smgr.c) | Front-door API. `smgropen`, `smgrread`, `smgrwrite`, `smgrextend`, `smgrdounlink`. Holds the `SMgrRelation` cache. |
|
||||
| [`storage/smgr/md.c`](../../../../.dev/reference/postgresql/src/backend/storage/smgr/md.c) | The actual implementation against the kernel. Manages `MdfdVec` (open file descriptor handles per segment number), opens missing segments lazily. |
|
||||
| [`storage/smgr/bulk_write.c`](../../../../.dev/reference/postgresql/src/backend/storage/smgr/bulk_write.c) | Optimized path for bulk-loading: writes directly to `smgrwrite` without going through shared buffers. Useful for `COPY` / `CREATE INDEX` + the recovery path's wal-replay-rebuilds-pages flow. |
|
||||
| [`storage/smgr/README`](../../../../.dev/reference/postgresql/src/backend/storage/smgr/README) | Brief but worth reading — explains the relfilenode → file naming convention and how `RELSEG_SIZE` interacts with 32-bit-fs-size historical limits. |
|
||||
|
||||
## What `md.c` actually does
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ Writeonce mirrors the algorithm. The single-thread loop replaces multi-process c
|
|||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| [`access/transam/xlog.c`](../../../../reference/postgresql/src/backend/access/transam/xlog.c) | Top-level WAL machinery: insertion locks, segment rollover, flush coordination, control-file rendezvous. |
|
||||
| [`access/transam/xloginsert.c`](../../../../reference/postgresql/src/backend/access/transam/xloginsert.c) | Build a WAL record (header + payload + backup-block deltas) and place it into the in-memory WAL buffer. |
|
||||
| [`access/transam/xlogreader.c`](../../../../reference/postgresql/src/backend/access/transam/xlogreader.c) | Decode WAL records during recovery — pure parser, no I/O. Useful as the read-side spec. |
|
||||
| [`access/transam/xlogrecovery.c`](../../../../reference/postgresql/src/backend/access/transam/xlogrecovery.c) | The replay loop. Walks the WAL from the last-checkpoint LSN, replays each record into shared buffers, advances the redo pointer. |
|
||||
| [`postmaster/walwriter.c`](../../../../reference/postgresql/src/backend/postmaster/walwriter.c) | Background process that flushes the WAL buffer to disk asynchronously. Writeonce does this **inline in the loop tick**. |
|
||||
| [`access/transam/xlog.c`](../../../../.dev/reference/postgresql/src/backend/access/transam/xlog.c) | Top-level WAL machinery: insertion locks, segment rollover, flush coordination, control-file rendezvous. |
|
||||
| [`access/transam/xloginsert.c`](../../../../.dev/reference/postgresql/src/backend/access/transam/xloginsert.c) | Build a WAL record (header + payload + backup-block deltas) and place it into the in-memory WAL buffer. |
|
||||
| [`access/transam/xlogreader.c`](../../../../.dev/reference/postgresql/src/backend/access/transam/xlogreader.c) | Decode WAL records during recovery — pure parser, no I/O. Useful as the read-side spec. |
|
||||
| [`access/transam/xlogrecovery.c`](../../../../.dev/reference/postgresql/src/backend/access/transam/xlogrecovery.c) | The replay loop. Walks the WAL from the last-checkpoint LSN, replays each record into shared buffers, advances the redo pointer. |
|
||||
| [`postmaster/walwriter.c`](../../../../.dev/reference/postgresql/src/backend/postmaster/walwriter.c) | Background process that flushes the WAL buffer to disk asynchronously. Writeonce does this **inline in the loop tick**. |
|
||||
|
||||
## The five Postgres WAL ideas writeonce keeps
|
||||
|
||||
|
|
@ -57,9 +57,9 @@ Same effect as Postgres' group-commit fence (one `fsync` flushes many commits) w
|
|||
|
||||
## Pointers when implementing phase 11
|
||||
|
||||
- [`xloginsert.c:XLogInsert()`](../../../../reference/postgresql/src/backend/access/transam/xloginsert.c) — entry point for "insert this record into the WAL." Read the prologue + the LSN-assignment loop, ignore the buffer-juggling.
|
||||
- [`xlog.c:XLogFlush()`](../../../../reference/postgresql/src/backend/access/transam/xlog.c) — "make this LSN durable on disk." Read the early-out for "already flushed" and the group-commit waiter logic.
|
||||
- [`xlogrecovery.c:PerformWalRecovery()`](../../../../reference/postgresql/src/backend/access/transam/xlogrecovery.c) — the replay loop. Read the redo-pointer advance logic; ignore the multi-process startup signaling.
|
||||
- [`xloginsert.c:XLogInsert()`](../../../../.dev/reference/postgresql/src/backend/access/transam/xloginsert.c) — entry point for "insert this record into the WAL." Read the prologue + the LSN-assignment loop, ignore the buffer-juggling.
|
||||
- [`xlog.c:XLogFlush()`](../../../../.dev/reference/postgresql/src/backend/access/transam/xlog.c) — "make this LSN durable on disk." Read the early-out for "already flushed" and the group-commit waiter logic.
|
||||
- [`xlogrecovery.c:PerformWalRecovery()`](../../../../.dev/reference/postgresql/src/backend/access/transam/xlogrecovery.c) — the replay loop. Read the redo-pointer advance logic; ignore the multi-process startup signaling.
|
||||
|
||||
## Used by
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# UI track — `.htmlx` live templates + Angular-style monorepo
|
||||
|
||||
**Context sources:** [`docs/examples/ecommerce/ui/`](../../examples/ecommerce/ui/) (current `##ui` screens — storefront, order_tracker, admin_orders), [`docs/examples/ecommerce/types/`](../../examples/ecommerce/types/) + [`docs/examples/ecommerce/logic/`](../../examples/ecommerce/logic/) (the shared-schema + shared-fn anchor), [`reference/crates/wo-htmlx/`](../../../reference/crates/wo-htmlx/) (v1 template engine — `{{path}}`, `{{#each}}`, `{{> partial}}`, `data-bind` attributes), [`templates/`](../../../templates/) (v1 blog's concrete `.htmlx` usage), [`docs/runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md) (Phase 6's `##ui` + `##app` block spec).
|
||||
**Context sources:** [`docs/examples/ecommerce/ui/`](../../examples/ecommerce/ui/) (current `##ui` screens — storefront, order_tracker, admin_orders), [`docs/examples/ecommerce/types/`](../../examples/ecommerce/types/) + [`docs/examples/ecommerce/logic/`](../../examples/ecommerce/logic/) (the shared-schema + shared-fn anchor), [`reference/crates/wo-htmlx/`](../../../../.dev/reference/crates/wo-htmlx/) (v1 template engine — `{{path}}`, `{{#each}}`, `{{> partial}}`, `data-bind` attributes), [`templates/`](../../../../templates/) (v1 blog's concrete `.htmlx` usage), [`docs/runtime/database/06-lowcode-fullstack.md`](../../../runtime/database/06-lowcode-fullstack.md) (Phase 6's `##ui` + `##app` block spec).
|
||||
|
||||
## Context
|
||||
|
||||
Three threads converge into one plan:
|
||||
|
||||
1. **`##ui` needs a concrete output format.** Phase 6's spec says screens "compile to a render tree" served as SSR HTML with a thin client runtime, but the actual template format isn't named. The v1 `.htmlx` engine at [`reference/crates/wo-htmlx/`](../../../reference/crates/wo-htmlx/) already speaks `{{bindings}}`, `{{#each}}`, `{{> partials}}`, and `data-bind` attributes — it's 90% of what the new runtime needs and already has a working parser + renderer. Adopting it (and extending it with live-subscription semantics) is cheaper than inventing a new format.
|
||||
1. **`##ui` needs a concrete output format.** Phase 6's spec says screens "compile to a render tree" served as SSR HTML with a thin client runtime, but the actual template format isn't named. The v1 `.htmlx` engine at [`reference/crates/wo-htmlx/`](../../../../.dev/reference/crates/wo-htmlx/) already speaks `{{bindings}}`, `{{#each}}`, `{{> partials}}`, and `data-bind` attributes — it's 90% of what the new runtime needs and already has a working parser + renderer. Adopting it (and extending it with live-subscription semantics) is cheaper than inventing a new format.
|
||||
|
||||
2. **The samples want a home that matches how real frontends are organised.** The ecommerce sample today is one flat directory with `types/`, `logic/`, and `ui/` beside each other. A real deployment has *multiple apps* against the same data: a customer storefront, an admin dashboard, a fulfillment console, maybe a read-only analytics viewer. Each has its own routes, its own policies, its own ideal binary shape. Angular (via Nx / Angular CLI workspaces) solved this with `apps/*` + `libs/*` on top of a shared root config — writeonce adopts the same shape.
|
||||
|
||||
|
|
@ -61,11 +61,11 @@ Read before writing each sub-phase:
|
|||
|
||||
| Source | Why |
|
||||
| --- | --- |
|
||||
| [`reference/crates/wo-htmlx/src/parser.rs`](../../../reference/crates/wo-htmlx/src/parser.rs) + [`render.rs`](../../../reference/crates/wo-htmlx/src/render.rs) | The v1 template engine's exact surface — what parses, what renders, what the AST looks like. ~500 LOC total. |
|
||||
| [`templates/article.htmlx`](../../../templates/article.htmlx), [`templates/home.htmlx`](../../../templates/home.htmlx) | Concrete usage of the v1 format — how `{{path}}` and `data-bind` actually read in real templates. |
|
||||
| [`reference/crates/wo-htmlx/src/parser.rs`](../../../../.dev/reference/crates/wo-htmlx/src/parser.rs) + [`render.rs`](../../../../.dev/reference/crates/wo-htmlx/src/render.rs) | The v1 template engine's exact surface — what parses, what renders, what the AST looks like. ~500 LOC total. |
|
||||
| [`templates/article.htmlx`](../../../../templates/article.htmlx), [`templates/home.htmlx`](../../../../templates/home.htmlx) | Concrete usage of the v1 format — how `{{path}}` and `data-bind` actually read in real templates. |
|
||||
| [`docs/examples/ecommerce/ui/{storefront,order_tracker,admin_orders}.wo`](../../examples/ecommerce/ui/) | The `##ui` side — what the declarative DSL promises to produce. These screens are the target of the first compiler pass. |
|
||||
| [`docs/runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md) | Phase 6's full-stack block spec — `##ui`, `##app`, `##policy`, `##service`, `##logic` — already designed but not yet compiled. |
|
||||
| [`docs/runtime/database/04-client-api.md`](../../runtime/database/04-client-api.md) | Phase 4's wire protocol — what the per-app binary speaks to the shared DB over. |
|
||||
| [`docs/runtime/database/06-lowcode-fullstack.md`](../../../runtime/database/06-lowcode-fullstack.md) | Phase 6's full-stack block spec — `##ui`, `##app`, `##policy`, `##service`, `##logic` — already designed but not yet compiled. |
|
||||
| [`docs/runtime/database/04-client-api.md`](../../../runtime/database/04-client-api.md) | Phase 4's wire protocol — what the per-app binary speaks to the shared DB over. |
|
||||
| [Nx monorepo docs](https://nx.dev/concepts/more-concepts/why-monorepos) | Background on the apps/libs split pattern; shape of `nx.json`. |
|
||||
|
||||
## Target layout
|
||||
|
|
@ -204,10 +204,10 @@ After all seven sub-phases land:
|
|||
|
||||
## Cross-references
|
||||
|
||||
- [`../09-concurrency-scaleout.md`](../09-concurrency-scaleout.md) — when the shared DB daemon needs to handle 10k connections across multiple apps, that plan's thread-per-core model applies to the daemon process.
|
||||
- [`../09-concurrency-scaleout.md`](../../09-concurrency-scaleout.md) — when the shared DB daemon needs to handle 10k connections across multiple apps, that plan's thread-per-core model applies to the daemon process.
|
||||
- [`../assembly/02-writeonce-stance.md`](../assembly/02-writeonce-stance.md) — still no asm. The client runtime is vanilla JS, no WASM.
|
||||
- [`../../runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md) — Phase 6's full-stack block spec that this track implements.
|
||||
- [`../../runtime/database/04-client-api.md`](../../runtime/database/04-client-api.md) — the wire protocol per-app binaries speak to the shared DB over.
|
||||
- [`../../runtime/database/06-lowcode-fullstack.md`](../../../runtime/database/06-lowcode-fullstack.md) — Phase 6's full-stack block spec that this track implements.
|
||||
- [`../../runtime/database/04-client-api.md`](../../../runtime/database/04-client-api.md) — the wire protocol per-app binaries speak to the shared DB over.
|
||||
- [`../../examples/ecommerce/ui/admin_orders.wo`](../../examples/ecommerce/ui/admin_orders.wo) — the motivating workload: a live ops table bound to the order stream.
|
||||
- [`reference/crates/wo-htmlx/`](../../../reference/crates/wo-htmlx/) — the template engine ~90% of this track will reuse.
|
||||
- [`templates/`](../../../templates/) — v1 blog's actual `.htmlx` files; the format this track extends.
|
||||
- [`reference/crates/wo-htmlx/`](../../../../.dev/reference/crates/wo-htmlx/) — the template engine ~90% of this track will reuse.
|
||||
- [`templates/`](../../../../templates/) — v1 blog's actual `.htmlx` files; the format this track extends.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 01 — `.htmlx` format spec
|
||||
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "`.htmlx` with live subscriptions — target format" (L127–166), "Design decisions" (L28–37), [`reference/crates/wo-htmlx/`](../../../reference/crates/wo-htmlx/) (the v1 template engine that 90% of this phase ports), [`templates/article.htmlx`](../../../templates/article.htmlx) and [`templates/home.htmlx`](../../../templates/home.htmlx) (v1 concrete usage), [`docs/examples/ecommerce/shared/components/order-row.htmlx`](../../examples/ecommerce/shared/components/order-row.htmlx) (the live-binding workload this format must serve).
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "`.htmlx` with live subscriptions — target format" (L127–166), "Design decisions" (L28–37), [`reference/crates/wo-htmlx/`](../../../../.dev/reference/crates/wo-htmlx/) (the v1 template engine that 90% of this phase ports), [`templates/article.htmlx`](../../../../templates/article.htmlx) and [`templates/home.htmlx`](../../../../templates/home.htmlx) (v1 concrete usage), [`docs/examples/ecommerce/shared/components/order-row.htmlx`](../../../examples/ecommerce/shared/components/order-row.htmlx) (the live-binding workload this format must serve).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ Lock the exact `.htmlx` grammar — every v1 Mustache construct unchanged plus t
|
|||
|
||||
## Design decisions (locked)
|
||||
|
||||
1. **Mustache constructs carry through unchanged.** `{{path}}`, `{{#each xs as y}}…{{/each}}`, `{{#if cond}}…{{/if}}`, `{{#when cond}}…{{/when}}`, `{{> partial arg=val}}`. The v1 parser already handles all of these; the new parser inherits them verbatim. See [`reference/crates/wo-htmlx/src/parser.rs`](../../../reference/crates/wo-htmlx/src/parser.rs) (173 LOC) and the AST in [`ast.rs`](../../../reference/crates/wo-htmlx/src/ast.rs) (18 LOC).
|
||||
1. **Mustache constructs carry through unchanged.** `{{path}}`, `{{#each xs as y}}…{{/each}}`, `{{#if cond}}…{{/if}}`, `{{#when cond}}…{{/when}}`, `{{> partial arg=val}}`. The v1 parser already handles all of these; the new parser inherits them verbatim. See [`reference/crates/wo-htmlx/src/parser.rs`](../../../../.dev/reference/crates/wo-htmlx/src/parser.rs) (173 LOC) and the AST in [`ast.rs`](../../../../.dev/reference/crates/wo-htmlx/src/ast.rs) (18 LOC).
|
||||
2. **`<wo:live>` is a parsed structured node, not HTML passthrough.** The parser recognises the `<wo:` prefix, captures attributes (`source`, `key`, optional `sort`, `filter`), and recursively parses the body as a normal `.htmlx` subtree. No nesting in this phase — error at parse if a `<wo:live>` contains another `<wo:live>`.
|
||||
3. **`wo:bind="field"` is an HTML attribute, parsed but emitted verbatim.** SSR writes the attribute through; the consumer is the client runtime. The parser records each `(element, field)` pair into the manifest; nothing else changes about element rendering.
|
||||
4. **Helpers are a closed Rust enum.** v1 invocation forms (`{{relative ts}}`, `{{#if (eq for "ops")}}`, `{{> money amount=x}}`) carry through. The registered set is fixed for this phase: `relative`, `eq`, `markdown`, `code`, `money`, `tag-chips`, `pill`, `image`, `stock-badge`, `list`. No author extensibility.
|
||||
|
|
@ -20,12 +20,12 @@ Lock the exact `.htmlx` grammar — every v1 Mustache construct unchanged plus t
|
|||
|
||||
| File | Responsibility | Port source |
|
||||
| --- | --- | --- |
|
||||
| `mod.rs` | Re-exports `Template`, `Manifest`, `LiveSubscription`, `BindSite`, `ParseError`, `RenderError` | [`reference/crates/wo-htmlx/src/lib.rs`](../../../reference/crates/wo-htmlx/src/lib.rs) (11 LOC) |
|
||||
| `ast.rs` | Adds `Node::Live { attrs, body }` and `wo_bind: Option<String>` on element nodes | [`reference/crates/wo-htmlx/src/ast.rs`](../../../reference/crates/wo-htmlx/src/ast.rs) (18 LOC) — extend by ~50 LOC |
|
||||
| `parser.rs` | Adds `<wo:` prefix recognition + attribute capture; rest unchanged | [`reference/crates/wo-htmlx/src/parser.rs`](../../../reference/crates/wo-htmlx/src/parser.rs) (173 LOC) — extend by ~90 LOC |
|
||||
| `value.rs` | Path resolution against a context Value | [`reference/crates/wo-htmlx/src/value.rs`](../../../reference/crates/wo-htmlx/src/value.rs) (122 LOC) — copied verbatim |
|
||||
| `registry.rs` | Closed helper-fn registry | [`reference/crates/wo-htmlx/src/registry.rs`](../../../reference/crates/wo-htmlx/src/registry.rs) (121 LOC) — extend by ~60 LOC for new helpers |
|
||||
| `render.rs` | Emits HTML; wraps `<wo:live>` body in `<div data-wo-subscription="…">` for the runtime | [`reference/crates/wo-htmlx/src/render.rs`](../../../reference/crates/wo-htmlx/src/render.rs) (140 LOC) — extend by ~70 LOC |
|
||||
| `mod.rs` | Re-exports `Template`, `Manifest`, `LiveSubscription`, `BindSite`, `ParseError`, `RenderError` | [`reference/crates/wo-htmlx/src/lib.rs`](../../../../.dev/reference/crates/wo-htmlx/src/lib.rs) (11 LOC) |
|
||||
| `ast.rs` | Adds `Node::Live { attrs, body }` and `wo_bind: Option<String>` on element nodes | [`reference/crates/wo-htmlx/src/ast.rs`](../../../../.dev/reference/crates/wo-htmlx/src/ast.rs) (18 LOC) — extend by ~50 LOC |
|
||||
| `parser.rs` | Adds `<wo:` prefix recognition + attribute capture; rest unchanged | [`reference/crates/wo-htmlx/src/parser.rs`](../../../../.dev/reference/crates/wo-htmlx/src/parser.rs) (173 LOC) — extend by ~90 LOC |
|
||||
| `value.rs` | Path resolution against a context Value | [`reference/crates/wo-htmlx/src/value.rs`](../../../../.dev/reference/crates/wo-htmlx/src/value.rs) (122 LOC) — copied verbatim |
|
||||
| `registry.rs` | Closed helper-fn registry | [`reference/crates/wo-htmlx/src/registry.rs`](../../../../.dev/reference/crates/wo-htmlx/src/registry.rs) (121 LOC) — extend by ~60 LOC for new helpers |
|
||||
| `render.rs` | Emits HTML; wraps `<wo:live>` body in `<div data-wo-subscription="…">` for the runtime | [`reference/crates/wo-htmlx/src/render.rs`](../../../../.dev/reference/crates/wo-htmlx/src/render.rs) (140 LOC) — extend by ~70 LOC |
|
||||
| `manifest.rs` | Walks the AST, collects subscriptions + bind sites, serialises JSON | new (~150 LOC) |
|
||||
|
||||
Total: ~835 LOC (585 ported + ~250 new).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 02 — `##ui` → `.htmlx` compiler
|
||||
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Sub-phase sequence" (L172–180), "Design decisions" 1–6, [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) (the emission target), [`../../runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md) (the `##ui` block spec), [`docs/examples/blog/ui/article_list.wo`](../../examples/blog/ui/article_list.wo), [`docs/examples/blog/ui/article_detail.wo`](../../examples/blog/ui/article_detail.wo), [`docs/examples/ecommerce/apps/admin/ui/orders/orders.wo`](../../examples/ecommerce/apps/admin/ui/orders/orders.wo) (the test corpus), [`crates/rt/src/parser.rs:80–116`](../../../crates/rt/src/parser.rs) (the parse-and-discard call site to replace).
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Sub-phase sequence" (L172–180), "Design decisions" 1–6, [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) (the emission target), [`../../runtime/database/06-lowcode-fullstack.md`](../../../runtime/database/06-lowcode-fullstack.md) (the `##ui` block spec), [`docs/examples/blog/ui/article_list.wo`](../../../examples/blog/ui/article_list.wo), [`docs/examples/blog/ui/article_detail.wo`](../../../examples/blog/ui/article_detail.wo), [`docs/examples/ecommerce/apps/admin/ui/orders/orders.wo`](../../examples/ecommerce/apps/admin/ui/orders/orders.wo) (the test corpus), [`crates/rt/src/parser.rs:80–116`](../../../../crates/rt/src/parser.rs) (the parse-and-discard call site to replace).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 03 — Client runtime
|
||||
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "`.htmlx` with live subscriptions — target format" (L127–166) and decisions 1–2, [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) (the manifest schema this runtime consumes), [`reference/crates/wo-sub/src/lib.rs`](../../../reference/crates/wo-sub/src/lib.rs) (the v1 frame model the wire format mirrors), [`docs/examples/ecommerce/shared/components/order-row.htmlx`](../../examples/ecommerce/shared/components/order-row.htmlx) (the live workload the runtime must update without reload).
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "`.htmlx` with live subscriptions — target format" (L127–166) and decisions 1–2, [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) (the manifest schema this runtime consumes), [`reference/crates/wo-sub/src/lib.rs`](../../../../.dev/reference/crates/wo-sub/src/lib.rs) (the v1 frame model the wire format mirrors), [`docs/examples/ecommerce/shared/components/order-row.htmlx`](../../../examples/ecommerce/shared/components/order-row.htmlx) (the live workload the runtime must update without reload).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 04 — Workspace layout + `wo.toml` grammar
|
||||
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Target layout" (L73–117), "Design decisions" 4–5 (L33–34), [`docs/examples/ecommerce/wo.toml`](../../examples/ecommerce/wo.toml) (the workspace manifest already in the repo), [`docs/examples/ecommerce/apps/storefront/wo.toml`](../../examples/ecommerce/apps/storefront/wo.toml) (the per-app manifest already in the repo), [`docs/examples/blog/`](../../examples/blog/) (the degenerate single-app form).
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Target layout" (L73–117), "Design decisions" 4–5 (L33–34), [`docs/examples/ecommerce/wo.toml`](../../../examples/ecommerce/wo.toml) (the workspace manifest already in the repo), [`docs/examples/ecommerce/apps/storefront/wo.toml`](../../../examples/ecommerce/apps/storefront/wo.toml) (the per-app manifest already in the repo), [`docs/examples/blog/`](../../examples/blog/) (the degenerate single-app form).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ Lock the `wo.toml` grammar at both workspace and per-app scope, fill any structu
|
|||
|
||||
## Design decisions (locked)
|
||||
|
||||
1. **`wo.toml` is TOML.** Not `.wo`. The workspace + app manifests already exist in the repo using TOML; this phase formalises the schema and adds a parser. Anchored in [`docs/examples/ecommerce/wo.toml`](../../examples/ecommerce/wo.toml).
|
||||
1. **`wo.toml` is TOML.** Not `.wo`. The workspace + app manifests already exist in the repo using TOML; this phase formalises the schema and adds a parser. Anchored in [`docs/examples/ecommerce/wo.toml`](../../../examples/ecommerce/wo.toml).
|
||||
2. **Path-based shared dependencies, no registry.** `shared = ["../../shared/types", …]` resolves at parse time relative to the per-app `wo.toml`. No semver, no fetch. Anchored in [`./00-overview.md`](./00-overview.md) decision 4.
|
||||
3. **Workspace root vs. single app, by `kind`/`app_kind` field.** A `wo.toml` with `kind = "workspace"` triggers workspace loading and reads `[workspace]`. A `wo.toml` with `app_kind = "app"` is a leaf app. A `wo.toml` with neither is a degenerate single-app workspace (the blog example) — loaded as if it were `apps/<itself>`.
|
||||
4. **One screen per directory.** `apps/<X>/ui/<screen>/{<screen>.wo, <screen>.htmlx, <screen>.css}` is locked layout. The loader walks `apps/<X>/ui/*/` and registers each subdirectory as a screen. Anchored in [`./00-overview.md`](./00-overview.md) decision 5 + Target Layout (L98–103).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 05 — Per-app static binaries (`wo build apps/X`)
|
||||
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Goal" (L21–23), "Design decisions" 3–4 (L32–33), [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md), [`./02-ui-compiler.md`](./02-ui-compiler.md), [`./03-client-runtime.md`](./03-client-runtime.md), [`./04-workspace-layout.md`](./04-workspace-layout.md), [`./06-shared-db-daemon.md`](./06-shared-db-daemon.md) (the wire URL contract this phase consumes), [`docs/examples/ecommerce/apps/storefront/wo.toml`](../../examples/ecommerce/apps/storefront/wo.toml).
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Goal" (L21–23), "Design decisions" 3–4 (L32–33), [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md), [`./02-ui-compiler.md`](./02-ui-compiler.md), [`./03-client-runtime.md`](./03-client-runtime.md), [`./04-workspace-layout.md`](./04-workspace-layout.md), [`./06-shared-db-daemon.md`](./06-shared-db-daemon.md) (the wire URL contract this phase consumes), [`docs/examples/ecommerce/apps/storefront/wo.toml`](../../../examples/ecommerce/apps/storefront/wo.toml).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
1. **One Cargo build per app, dynamic Cargo project templating.** `wo build apps/<X>` materialises a Cargo project under `target/wo-build/<X>/`, fills `[bin] name = "<X>"`, copies/generates `app_config.rs`, and invokes `cargo build --release`. The resulting binary is copied to `target/wo/<X>`.
|
||||
2. **No per-app Rust source generation beyond config.** The same `crates/app` is linked into every app binary. The only generated Rust file is `app_config.rs` containing the route table, embedded templates, and embedded runtime asset. Avoids exploding cargo metadata across N apps.
|
||||
3. **`include_bytes!` bakes templates + runtime + CSS at compile time.** A Cargo `build.rs` writes `app_config.rs` enumerating every compiled `.htmlx`, every `.css` from `apps/<X>/ui/<screen>/<screen>.css` and `shared/components/*.css`, plus the runtime JS via `RUNTIME_JS` from phase 03.
|
||||
4. **Connection target precedence: `WO_DB` env > `[database].url` from manifest > error.** The app refuses to start if neither is set. Anchored in [`./00-overview.md`](./00-overview.md) "Goal" (L23) and [`docs/examples/ecommerce/apps/storefront/wo.toml`](../../examples/ecommerce/apps/storefront/wo.toml) L21–26.
|
||||
4. **Connection target precedence: `WO_DB` env > `[database].url` from manifest > error.** The app refuses to start if neither is set. Anchored in [`./00-overview.md`](./00-overview.md) "Goal" (L23) and [`docs/examples/ecommerce/apps/storefront/wo.toml`](../../../examples/ecommerce/apps/storefront/wo.toml) L21–26.
|
||||
5. **HTTP listener address comes from `[server] listen`.** Different from the database URL — the database URL is what this binary connects *to*; `[server].listen` is what the binary itself exposes to browsers. `WO_LISTEN` env var overrides for ops.
|
||||
|
||||
## Scope
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 06 — Shared database daemon (`wo db serve`)
|
||||
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Goal" (L23), "Design decisions" 3 (L32), "Non-scope" (L201–203), [`./03-client-runtime.md`](./03-client-runtime.md) (the wire frames this daemon emits), [`./05-per-app-binaries.md`](./05-per-app-binaries.md) (the apps that connect), [`reference/crates/wo-sub/src/lib.rs`](../../../reference/crates/wo-sub/src/lib.rs) (the v1 subscription registry, 470 LOC, that needs generalising past `ByTitle`/`ByTag`/`All`), [`../../runtime/database/04-client-api.md`](../../runtime/database/04-client-api.md) (the wire-protocol owner).
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) §§ "Goal" (L23), "Design decisions" 3 (L32), "Non-scope" (L201–203), [`./03-client-runtime.md`](./03-client-runtime.md) (the wire frames this daemon emits), [`./05-per-app-binaries.md`](./05-per-app-binaries.md) (the apps that connect), [`reference/crates/wo-sub/src/lib.rs`](../../../../.dev/reference/crates/wo-sub/src/lib.rs) (the v1 subscription registry, 470 LOC, that needs generalising past `ByTitle`/`ByTag`/`All`), [`../../runtime/database/04-client-api.md`](../../../runtime/database/04-client-api.md) (the wire-protocol owner).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ Stand up a headless daemon — `wo db serve` — that runs the engine + WAL + su
|
|||
|
||||
1. **Daemon = `crates/db` thin entrypoint + `crates/engine` + the wire acceptor.** No HTTP, no `.htmlx`, no `##ui`. The shared DB process knows nothing about the UI layer.
|
||||
2. **API-key table is in-memory, env-seeded.** On startup the daemon reads `WO_DB_KEY_<APP>=<hex>` for each app declared in the workspace and builds an `AuthTable: HashMap<ApiKey, Principal>`. A `--keys <file>` flag is accepted but treated as a future hook.
|
||||
3. **Generalise `wo-sub`** from `Subscription::ByTitle/ByTag/All` to `Subscription::ByPredicate(TypeRef, Expr, SortKey)`. The v1 variants stay as legacy aliases (`ByTitle(t)` ⇒ `ByPredicate(Article, sys_title == t, _)`) for the blog regression test. Anchored in [`reference/crates/wo-sub/src/lib.rs`](../../../reference/crates/wo-sub/src/lib.rs) L8–17.
|
||||
3. **Generalise `wo-sub`** from `Subscription::ByTitle/ByTag/All` to `Subscription::ByPredicate(TypeRef, Expr, SortKey)`. The v1 variants stay as legacy aliases (`ByTitle(t)` ⇒ `ByPredicate(Article, sys_title == t, _)`) for the blog regression test. Anchored in [`reference/crates/wo-sub/src/lib.rs`](../../../../.dev/reference/crates/wo-sub/src/lib.rs) L8–17.
|
||||
4. **Connection scope = `Principal { app, roles }` stored on the connection.** Every query evaluator reads it; phase 07 wires it into policy AND-composition.
|
||||
5. **One data dir, one engine, many connections.** Snapshot isolation by default (per `[database].isolation = "snapshot"` in the workspace `wo.toml`).
|
||||
6. **Foreground-only this phase.** No daemonisation, no PID file, no signal handling beyond `SIGTERM` graceful shutdown. A future ops doc can add `wo db daemonize`.
|
||||
|
|
@ -24,7 +24,7 @@ Stand up a headless daemon — `wo db serve` — that runs the engine + WAL + su
|
|||
| `crates/db/src/main.rs` | Entrypoint, arg parsing, env-key loading | new (~100 LOC) |
|
||||
| `crates/db/src/server.rs` | Wire-protocol acceptor (TCP listener + per-conn handler) | new (~250 LOC) |
|
||||
| `crates/db/src/auth.rs` | `AuthTable`, `Principal`, key handshake | new (~120 LOC) |
|
||||
| `crates/sub/src/lib.rs` | Generalised subscription manager | port [`reference/crates/wo-sub/src/lib.rs`](../../../reference/crates/wo-sub/src/lib.rs) (470 LOC) + ~150 new |
|
||||
| `crates/sub/src/lib.rs` | Generalised subscription manager | port [`reference/crates/wo-sub/src/lib.rs`](../../../../.dev/reference/crates/wo-sub/src/lib.rs) (470 LOC) + ~150 new |
|
||||
| `crates/sub/src/predicate.rs` | Predicate evaluation against a row (uses `crates/ql` if available, else minimal subset) | new (~150 LOC) |
|
||||
|
||||
Total: ~1240 LOC (470 ported + ~770 new).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 07 — Per-app policy composition
|
||||
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) decision 7 (L36) and "Goal" (L26), [`./04-workspace-layout.md`](./04-workspace-layout.md) (where app-scope policies live), [`./06-shared-db-daemon.md`](./06-shared-db-daemon.md) (where composition is evaluated), [`docs/examples/blog/types/article.wo`](../../examples/blog/types/article.wo) and the other type files (existing global `policy read/write` blocks), [`docs/examples/ecommerce/apps/admin/ui/orders/orders.wo`](../../examples/ecommerce/apps/admin/ui/orders/orders.wo) L18 (a `role: Admin | Ops` set expression).
|
||||
**Context sources:** [`./00-overview.md`](./00-overview.md) decision 7 (L36) and "Goal" (L26), [`./04-workspace-layout.md`](./04-workspace-layout.md) (where app-scope policies live), [`./06-shared-db-daemon.md`](./06-shared-db-daemon.md) (where composition is evaluated), [`docs/examples/blog/types/article.wo`](../../../examples/blog/types/article.wo) and the other type files (existing global `policy read/write` blocks), [`docs/examples/ecommerce/apps/admin/ui/orders/orders.wo`](../../examples/ecommerce/apps/admin/ui/orders/orders.wo) L18 (a `role: Admin | Ops` set expression).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 08 — MVC structure: model = class, view = htmlx + scss, controller = .wo
|
||||
|
||||
**Context sources:** [`reference/writeonce-app/src/app/`](../../../../reference/writeonce-app/src/app/) (the v1 Angular app whose component anatomy this formalizes), [`./00-overview.md`](./00-overview.md) ("Angular-component-style layout" — `home/{home.wo, home.htmlx, home.css}`), [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) (the view grammar: Mustache + `<wo:live>` + `wo:bind`), [`./02-ui-compiler.md`](./02-ui-compiler.md), [`./03-client-runtime.md`](./03-client-runtime.md), [`../../13-class-model-live-pricing.md`](../../13-class-model-live-pricing.md) (the class methods controllers call), [`../../../examples/pricing/ui/pricing/`](../../../examples/pricing/ui/pricing/) (the reference screen).
|
||||
**Context sources:** [`reference/writeonce-app/src/app/`](../../../../.dev/reference/writeonce-app/src/app/) (the v1 Angular app whose component anatomy this formalizes), [`./00-overview.md`](./00-overview.md) ("Angular-component-style layout" — `home/{home.wo, home.htmlx, home.css}`), [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) (the view grammar: Mustache + `<wo:live>` + `wo:bind`), [`./02-ui-compiler.md`](./02-ui-compiler.md), [`./03-client-runtime.md`](./03-client-runtime.md), [`../../13-class-model-live-pricing.md`](../../13-class-model-live-pricing.md) (the class methods controllers call), [`../../../examples/pricing/ui/pricing/`](../../../examples/pricing/ui/pricing/) (the reference screen).
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
What the work actually taught, independent of whether it shipped. Recorded so
|
||||
the same wall is not hit twice. Newest first within each section.
|
||||
|
||||
Status board: [`00-kanban.md`](00-kanban.md) · Rejections: [`discarded.md`](discarded.md)
|
||||
Status board: [`00-status.md`](../00-status.md) · Rejections: [`discarded.md`](discarded.md)
|
||||
|
||||
## Testing and verification
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ emits, as of plan 2 tasks 2–8. Code ranges are reserved per stage
|
|||
(`compiler/src/diag.ml`): `WO-E0xx` lexing, `WO-E1xx` parsing, `WO-E2xx`
|
||||
types, `WO-E3xx` ownership, `WO-W2xx` warnings from the types stage. This
|
||||
is an enumeration of codes already in use, not an archaeology dig — see
|
||||
"Completeness method" below for how that was verified, and "Reserved,
|
||||
not yet emitted" for codes the source declares but no check yet raises.
|
||||
"Completeness method" below for how that was verified, "Reserved,
|
||||
not yet emitted" for codes the source declares but no check yet raises,
|
||||
and "Unreachable by design" for the one code (WO-E205) that isn't merely
|
||||
unimplemented — it has no legal call site in the milestone grammar.
|
||||
One code (WO-E214) is emitted by the driver (`compiler/bin/main.ml`),
|
||||
not one of the four stage modules — a Task 8 review finding — see its
|
||||
row in the types table below for why it still uses that range.
|
||||
|
|
@ -44,12 +46,11 @@ half of the story ("moved here" / "borrowed here" / etc.).
|
|||
### Reserved, not yet emitted
|
||||
|
||||
`type_mismatch_code` (WO-E201), `bad_arity_code` (WO-E203),
|
||||
`unknown_fn_code` (WO-E204), `unsatisfied_interface_code` (WO-E205),
|
||||
`non_exhaustive_switch_code` (WO-E208), `invalid_builtin_code` (WO-E209),
|
||||
`module_not_imported_code` (WO-E210), `nullable_used_without_check_code`
|
||||
(WO-E211), `nullable_assign_mismatch_code` (WO-E212), and
|
||||
`missing_nil_check_code` (WO-E213) are declared in `types.ml` — the
|
||||
range is reserved — but as of Task 7 nothing in the front end ever
|
||||
`unknown_fn_code` (WO-E204), `non_exhaustive_switch_code` (WO-E208),
|
||||
`invalid_builtin_code` (WO-E209), `module_not_imported_code` (WO-E210),
|
||||
`nullable_used_without_check_code` (WO-E211), `nullable_assign_mismatch_code`
|
||||
(WO-E212), and `missing_nil_check_code` (WO-E213) are declared in `types.ml`
|
||||
— the range is reserved — but as of Task 7 nothing in the front end ever
|
||||
raises them; there is no call site and therefore no real example
|
||||
message to catalog. They read like placeholders for checks Task 6's own
|
||||
plan brief named (type mismatch, bad arity, unsatisfied interface, …)
|
||||
|
|
@ -58,6 +59,21 @@ conformance fixture (plan 3) or a future reader doesn't assume one of
|
|||
these codes is reachable today; move a code up into the table above in
|
||||
the same commit that wires its first real emission site.
|
||||
|
||||
### Unreachable by design
|
||||
|
||||
`unsatisfied_interface_code` (WO-E205) is declared in `types.ml` but does not
|
||||
belong in the list above — it is not a pending implementation, it is
|
||||
unreachable by design given the milestone grammar. Structural interface
|
||||
satisfaction has exactly one legal home: a site where a value is used at an
|
||||
interface-typed position (a field, parameter, or return typed as an
|
||||
interface). There is no `implements` keyword by doctrine — satisfaction is
|
||||
structural, checked where the value is used, not declared — and the
|
||||
milestone grammar declares no interfaces and exercises no interface-typed
|
||||
positions, so the check has nowhere to fire. This is not a gap in shipped
|
||||
work; it costs the milestone nothing. The check starts firing the moment a
|
||||
future milestone introduces an interface-typed position — no interim
|
||||
workaround is owed before then.
|
||||
|
||||
## WO-E3xx — ownership / MVS (Task 7, `compiler/src/owner.ml`)
|
||||
|
||||
Every ownership diagnostic carries a second site (the module doc's
|
||||
|
|
|
|||
|
|
@ -205,5 +205,5 @@ Each phase has its own exit criteria above. End-to-end verification for the whol
|
|||
- [04-client-api.md](./04-client-api.md) — wire protocol and `LIVE` subscriptions
|
||||
- [05-go-sdk.md](./05-go-sdk.md) — the Go SDK built from `.wo` types via `wo-gen`
|
||||
- [01-evaluation.md](./01-evaluation.md) — why writeonce built `wo-seg` in the first place, and why that choice still looks right for the blog even as the platform grows past it
|
||||
- [../05-datalayer.md](../05-datalayer.md) — current `.seg` + `.idx` implementation details
|
||||
- [../05-datalayer.md](../../05-datalayer.md) — current `.seg` + `.idx` implementation details
|
||||
- `prototypes/wo-db/` — the C++ prototype of the `.wo` engine, the reference implementation the Rust port follows
|
||||
|
|
|
|||
|
|
@ -72,16 +72,16 @@ tests/corpus/sample-logwatcher/ ported fixtures per task
|
|||
|
||||
### Task 5: `main.wo` + README + acceptance
|
||||
|
||||
**Concept & reason:** close the loop. `main.wo`: the three subcommands — `watch` (single-watcher poll loop), `run` (supervisor + optional config), `mcp` (config + env-fallback API key, required-field errors exit 1 with usage) — config decoded via `json.decode as` into the config record, usage text on anything else. The README mapping table: every `.wo` file, its `.hx` sibling, tabled divergences, and the **could-not-express column — acceptance demands it empty** (criterion 4). The live test (criterion 5): a scripted scenario starts the built sample in watch mode against a tempfile, feeds timestamped lines ending in an error, waits past the quiet period, asserts exactly one detection — the original's measured behavior, reproduced. `just oop-accept` gains the sample build + fixture groups + the live scenario; kanban and the systems spec get their shipped-status notes.
|
||||
**Concept & reason:** close the loop. `main.wo`: the three subcommands — `watch` (single-watcher poll loop), `run` (supervisor + optional config), `mcp` (config + env-fallback API key, required-field errors exit 1 with usage) — config decoded via `json.decode as` into the config record, usage text on anything else. The README mapping table: every `.wo` file, its `.hx` sibling, tabled divergences, and the **could-not-express column — acceptance demands it empty** (criterion 4). The live test (criterion 5): a scripted scenario starts the built sample in watch mode against a tempfile, feeds timestamped lines ending in an error, waits past the quiet period, asserts exactly one detection — the original's measured behavior, reproduced. Acceptance also gains a **diagnostic-count gate**: `woc docs/examples/log-watcher` emits 307 diagnostics today (167 `WO-E101` + 140 `WO-E207` across 7 files) — the pre-port baseline — and that count must fall monotonically from iteration 5 onward, reaching exactly 0 here. `just oop-accept` gains the sample build + fixture groups + the live scenario + the diagnostic-count check; kanban and the systems spec get their shipped-status notes.
|
||||
|
||||
- [ ] Write main.wo + README table; port config-loading fixtures (defaults, partial config, missing-key mcp errors).
|
||||
- [ ] Wire the live scenario + gate; run acceptance: criteria 4 and 5 checked against the spec.
|
||||
- [ ] Record commit draft: `docs(examples): log-watcher port complete — main.wo subcommands + typed config, README mapping table (could-not-express: empty), live silent-death scenario in oop-accept; systems-track criteria 4-5 checked.`
|
||||
- [ ] Wire the live scenario + gate; run acceptance: criteria 4 and 5 checked against the spec, plus the diagnostic-count gate (307 → 0).
|
||||
- [ ] Record commit draft: `docs(examples): log-watcher port complete — main.wo subcommands + typed config, README mapping table (could-not-express: empty), live silent-death scenario in oop-accept, diagnostic count 307 to 0; systems-track criteria 4-5 checked.`
|
||||
|
||||
---
|
||||
|
||||
## Plan self-review notes
|
||||
|
||||
- **Spec coverage (Part 4, criteria 4–5):** all five `.hx→.wo` mappings from the spec's table have tasks; the pure-core discipline, the README table, and both acceptance criteria are explicit task outputs. The minilog/sqlite tools exclusion matches the spec's out-of-scope list and is recorded as scoped-out, not inexpressible.
|
||||
- **Spec coverage (Part 4, criteria 4–5):** all five `.hx→.wo` mappings from the spec's table have tasks; the pure-core discipline, the README table, and both acceptance criteria are explicit task outputs. The minilog/sqlite tools exclusion matches the spec's out-of-scope list and is recorded as scoped-out, not inexpressible. Task 5 also carries the gap-closure amendment's diagnostic-count gate (307 → 0, recorded 2026-08-10).
|
||||
- **Feedback-loop honesty:** the no-new-features constraint plus stop-on-gap rule makes this plan the verification instrument for plans 8/9 — its failure mode is a defect report, not a workaround.
|
||||
- **Order rationale:** pure cores first (tail, cron) — testable without any daemon; probes/supervisor next (compose them); MCP after json/net are proven by earlier tasks; main last, wiring everything.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
>
|
||||
> **Style rule (user convention):** concept, reason, and required behavior in words only; the executor writes the code.
|
||||
|
||||
**Goal:** Plan 9 — `.wo` becomes a systems language: `fn main` programs with exit codes, and the five capability modules (`env`, `fs`, `proc`, `net`, `time`, plus typed `json`) as safe wovm builtins with RAII handles.
|
||||
**Goal:** Plan 9 — `.wo` becomes a systems language: `fn main` programs with exit codes, and the six stdlib modules (`env`, `fs`, `proc`, `net`, `time`, `json`) as safe wovm builtins with RAII handles, plus the 22 core builtins as always-in-scope bare globals.
|
||||
|
||||
**Architecture:** Plan 9 of the roadmap. Depends on plans 1–3 (toolchain) and plan 8 Task 1 (module resolver knows the stdlib namespaces) and Task 6 (`?T` — most stdlib returns are optional-typed). Runtime work is C builtin families in new `runtime/src/` modules; compiler work is thin (main detection, namespace binding). The spec's Part 2/3 tables are normative. Blocking discipline: program mode runs one shard where blocking builtins are legal; the same surface loop-integrates on server shards later (the shard-actor and HTTP plans own that side — this plan implements program mode only and keeps the builtin layer's seam clean for the other discipline).
|
||||
|
||||
|
|
@ -46,11 +46,11 @@ docs/plan/oop-vm/07-systems-stdlib.md per-function contracts: types, nil-vs-tr
|
|||
|
||||
### Task 2: `time` module
|
||||
|
||||
**Concept & reason:** smallest module, unblocks every poll-loop fixture after it. `time.now()` exists (wall ms); add `time.mono() -> Int` (monotonic ms, CLOCK_MONOTONIC — interval math must not jump with wall-clock changes) and `time.sleep(ms)` (nanosleep; in program mode it blocks the shard, which is the point; EINTR from the shutdown signal returns early — the daemon loop's exit path).
|
||||
**Concept & reason:** smallest module, unblocks every poll-loop fixture after it. `time.now()` exists (wall ms); add `time.sleep(ms)` (nanosleep; in program mode it blocks the shard, which is the point; EINTR from the shutdown signal returns early — the daemon loop's exit path), `time.iso(ms) -> Text` (a stable textual instant — JSONL detection timestamps and MCP response fields need one), and `time.local(ms) -> {year, month, day, hour, minute, dow}` (broken-out calendar fields, including day-of-week, for cron next-fire computation; the record rides plan 8 Task 4's `typedef` records, no new type machinery). `time.mono()` is cut — 0 uses in the driving workload; parked post-iteration-12, returning when a workload needs monotonic math.
|
||||
|
||||
- [ ] Failing fixtures: mono monotonicity across a sleep; sleep duration lower-bound; sleep cut short by SIGTERM with stopping() true after.
|
||||
- [ ] Failing fixtures: sleep duration lower-bound; sleep cut short by SIGTERM with stopping() true after; `iso`/`local` goldens run against a fixed injected clock (deterministic output, no wall-clock reads in the fixture), including a day-of-week boundary case.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(runtime): time module — mono (CLOCK_MONOTONIC ms), sleep (EINTR-aware, shutdown cuts it short); poll-loop idiom complete.`
|
||||
- [ ] Record commit draft: `feat(runtime): time module — sleep (EINTR-aware, shutdown cuts it short), iso (stable textual instant), local (calendar record incl. dow); mono cut (0 uses); poll-loop idiom complete.`
|
||||
|
||||
### Task 3: `fs` module
|
||||
|
||||
|
|
@ -84,7 +84,15 @@ docs/plan/oop-vm/07-systems-stdlib.md per-function contracts: types, nil-vs-tr
|
|||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat: typed json — decode-as against class-table kinds (?fields nil, unknown keys skip, mismatch = nil never trap), encode by kinds; undecodable targets diagnosed at compile time.`
|
||||
|
||||
### Task 7: Corpus battery + acceptance
|
||||
### Task 7: Core builtins + `print_err`
|
||||
|
||||
**Concept & reason:** the 22 bare-global builtins the sample calls at ~176 sites — `len`, `push`, `byte_at`, `starts_with`, `index_of`, `has`, `split`, `split_ws`, `join`, `parse_int`, `trim`, `slice`, `substr`, `pop`, `ends_with`, `last_index_of`, `to_lower`, `sort`, `char_of`, `shift`, `remove`, `reverse` — plus `print_err` (already named in Task 1's print family; it gets its builtin-id entry here). None of these are capability modules: no `use`, no namespace, always in scope, sharing the flat `WO_B_*` id space with `print`/`now`/`words`/`count`/`latest`. Each has a fixed-arity typed contract, resolved at compile time like every other builtin. The text/collection/map grouping is documentation only in the module doc, not a namespace. Out-of-range access — `byte_at`, `substr`, `slice`, `char_of`, `pop`/`shift`/`remove` past bounds — traps `T_BOUNDS` rather than returning a sentinel, matching the existing container builtins' contract. Higher-order builtins (`map`/`filter`/`reduce`) stay unadopted — the language has no function-value type (spec §1).
|
||||
|
||||
- [ ] Failing fixtures: one golden per builtin exercising its typed contract; a bounds-trap fixture (`T_BOUNDS`) for every builtin that takes an index, length, or removal argument; `print_err` lands on stderr.
|
||||
- [ ] Implement builtin dispatch entries; green.
|
||||
- [ ] Record commit draft: `feat(runtime): core builtins — 22 bare-global text/collection/map operations + print_err, flat WO_B_* ids, fixed-arity typed contracts, T_BOUNDS on out-of-range access (no sentinels).`
|
||||
|
||||
### Task 8: Corpus battery + acceptance
|
||||
|
||||
**Concept & reason:** criteria 2 and 3 of the spec, gated. The `sys/` corpus runs everything above end to end plus the cross-module fixtures that mimic log-watcher's composites: a tail-poll fixture (write to a tempfile between polls, assert offset math via read_at), a probe fixture (flock-style: proc.run against a held lock file — using the real flock binary when present, skipped cleanly otherwise), a mini serve-loop fixture (accept one request, respond, exit on stopping). `just oop-accept` gains the sys corpus; the module doc's contract table gets a shipped-status column; CLAUDE.md commands note program mode.
|
||||
|
||||
|
|
@ -95,6 +103,6 @@ docs/plan/oop-vm/07-systems-stdlib.md per-function contracts: types, nil-vs-tr
|
|||
|
||||
## Plan self-review notes
|
||||
|
||||
- **Spec coverage (Parts 2–3, criteria 2–3):** program mode T1, all five modules T2–T6 matching the spec tables exactly (one addition: `proc` result's `truncated` flag, recorded in the module doc), RAII proofs in T5 (fd battery) + ASan everywhere, corpus gate T7.
|
||||
- **Spec coverage (Parts 2–3, criteria 2–3):** program mode T1 (the `env` module, named as such, not left as loose prose), five further modules T2–T6 matching the spec's now-six-module Part 3 table exactly (one addition: `proc` result's `truncated` flag, recorded in the module doc), the 22 core builtins + `print_err` T7 (spec §1's core-builtins section), RAII proofs in T5 (fd battery) + ASan everywhere, corpus gate T8.
|
||||
- **Dependency honesty:** needs plan 8's modules (`use`), records, and `?T`; json (T6) shares the plan-6 codec with an either-order seam noted.
|
||||
- **Order rationale:** env/main first (nothing testable without an entry point), time second (fixtures need sleep/mono), fs/proc/net by increasing machinery, json last (needs records + codec), battery at the end.
|
||||
- **Order rationale:** env/main first (nothing testable without an entry point), time second (fixtures need sleep), fs/proc/net by increasing machinery, json next (needs records + codec), core builtins after (language primitives, independent of the other modules' machinery), battery at the end.
|
||||
|
|
|
|||
|
|
@ -40,18 +40,19 @@ Every Haxe keyword (plus the contextual ones), one verdict each: **have** (write
|
|||
| `switch` / `case` / `default` | **adopt** | expression-form switch, exhaustive over unions; `default` optional when exhaustive |
|
||||
| `typedef` | **adopt** | structural record aliases with optional fields (`?field`) — the `SupConfig`/`TailState` pattern |
|
||||
| `null` / `Null<T>` | **adopt** | `?T` optional types; forced handling before use (no nil deref trap possible); bare `null` only assignable to `?T` |
|
||||
| `try` / `catch` / `throw` | **adopt** | expression-form over the trap system: `catch` binds the structured error `{code, method, line, msg}`; `throw value` raises an EXPLICIT trap carrying the value; uncaught = existing trap surface |
|
||||
| `try` / `catch` | **adopt** | expression-form over the trap system: `catch` binds the structured error `{code, method, line, msg}`; uncaught = existing trap surface. `throw` (explicit raise) is **cut** — 0 uses in the driving workload; parked post-iteration-12 |
|
||||
| `break` / `continue` | **adopt** | loop control |
|
||||
| `do` (do-while) | **adopt** | parity, trivial |
|
||||
| `static` | **adopt** | class-level `fn`/`const` — namespaced functions without instances (`Flock.held`, `Pgrep.alive` pattern) |
|
||||
| `abstract` | **reject** | a distinct scalar type adds a conversion surface without buying safety this language needs; domain scalars are plain `Int`/`Text` |
|
||||
| `using` | **adopt** | static extension methods — doctrine-safe reuse (composition sugar, the inheritance substitute) |
|
||||
| `import` / `package` | **adopt** | `use` + directory-as-module; stdlib namespaces (`fs`, `proc`, `net`, `time`, `env`, `json`) |
|
||||
| `is` | **adopt** | runtime type test restricted to union variants and interface values; a compile error on statically-known types |
|
||||
| `is` | **cut** | runtime type test restricted to union variants and interface values; a compile error on statically-known types — 0 uses in the driving workload; parked post-iteration-12 |
|
||||
| `inline` | **adopt (values)** | `const` compile-time values; inline *functions* rejected — optimization is the compiler's job |
|
||||
| `public` / `private` | **adopt (as `pub`)** | default private; `pub` exports; property-accessor pattern `(default, null)` becomes `pub(read)` — public read, owner-only write |
|
||||
| `#if` / `#else` / `#end` | **adopt** | build-flag conditional compilation only (the `-D portable` pattern); flags from the build command, no expression language beyond flag names |
|
||||
| string interpolation `'${}'` | **adopt** | in string literals |
|
||||
| `&&` / `\|\|` | **adopt** | spelled `and`/`or` (words, not symbols) — the lexer has no `&` case at all (a bare `&` reports `WO-E001`), so words cost nothing to add as keywords and read better in the sample's conditional-heavy code; one new precedence level below comparison and above assignment (`or` binds loosest, then `and`, then comparison, then the arithmetic ladder); short-circuit; `Bool`-typed operands only, `Bool` result, no truthiness; lowers to compare-and-jump on existing opcodes (`JZ` plus a jump), no VM change |
|
||||
| `extends` | **reject** | no-inheritance doctrine (plan 13, OOP spec) — is-a via unions, has-a via composition |
|
||||
| `super` | **reject** | no hierarchy to call up |
|
||||
| `override` | **reject** | nothing to override |
|
||||
|
|
@ -76,16 +77,39 @@ Every Haxe keyword (plus the contextual ones), one verdict each: **have** (write
|
|||
|
||||
## Part 3 — Systems stdlib
|
||||
|
||||
Five builtin modules, scoped to what log-watcher's code actually uses. **Every handle (file, socket, process) is an owned object whose drop closes it** — MVS deterministic destruction is RAII: no close bookkeeping, no leaked fds by construction, and a handle sent nowhere dies at scope end.
|
||||
Six builtin modules, scoped to what log-watcher's code actually uses. **Every handle (file, socket, process) is an owned object whose drop closes it** — MVS deterministic destruction is RAII: no close bookkeeping, no leaked fds by construction, and a handle sent nowhere dies at scope end.
|
||||
|
||||
| Module | Surface | log-watcher use it covers |
|
||||
| --- | --- | --- |
|
||||
| `env` | `args()`; `get(name) -> ?Text`; `exit(code)`; `stopping() -> Bool` | CLI subcommand dispatch and exit-code propagation for `main`; `env.get` reads the MCP API key with an environment fallback (1 use); `env.stopping` drives the poll-loop shutdown check (4 uses) — the daemon idiom's exit condition. |
|
||||
| `fs` | `exists(path)`; `stat(path) -> ?{size, inode, mtime}`; `read_at(path, offset, max) -> Text`; `read_all(path, cap)`; `append(path, text)`; `list(dir) -> multi Text` | rotation detection needs the inode; bounded tail-chunk reads (never front-to-back scans); JSONL detection sink (open-append-close); cron.d directory scan. **No write/truncate/delete in v1** — the read-only posture is the default posture. |
|
||||
| `proc` | `run(cmd, args: multi Text) -> {code: Int, out: Text, err: Text}`, bounded capture | the `flock -n` exit-code probe and `pgrep -f`. Args-array only — no shell-string form, command injection unrepresentable. |
|
||||
| `net` | `listen(addr, port) -> Listener`; `accept(listener) -> Conn`; `read(conn, max) -> Text`; `write(conn, text)` | the hand-rolled MCP HTTP subset (127.0.0.1 accept loop, one request per connection). TCP only in v1. |
|
||||
| `time` | `now()` wall ms (exists); `mono()` monotonic ms; `sleep(ms)` | poll-interval math on a monotonic clock; the daemon sleep. |
|
||||
| `time` | `now()` wall ms (exists); `sleep(ms)`; `iso(ms) -> Text`; `local(ms) -> {year, month, day, hour, minute, dow}` | the daemon sleep; `iso` gives JSONL detection timestamps and MCP response fields a stable textual instant; `local` gives cron next-fire computation broken-out calendar fields, including day-of-week. `mono()` is **cut** — 0 uses in the driving workload; parked post-iteration-12. |
|
||||
| `json` | `json.decode(text) as RecordType -> ?RecordType`; `json.encode(value) -> Text` | config loading and JSON-RPC — **typed**, replacing Haxe's `Dynamic` idiom: missing optional fields are fine, shape mismatches yield nil, never a trap. The `as` here is the decode-target position only — a checked conversion returning `?T`, not a cast; it exists nowhere else (the `cast` rejection stands). Reuses the HTTP plan's C codec as builtins. |
|
||||
|
||||
### Core builtins
|
||||
|
||||
The sample calls **22 unqualified builtin names across ~176 sites**, none of
|
||||
them in any spec: `len` ×55, `push` ×17, `byte_at` ×11, `starts_with` ×9,
|
||||
`index_of` ×8, `has` ×7, `split` ×6, `split_ws` ×6, `join` ×5, `parse_int` ×5,
|
||||
`trim` ×4, `slice` ×4, `substr` ×4, `pop` ×3, `ends_with` ×2,
|
||||
`last_index_of` ×2, `to_lower` ×2, `sort` ×2, `char_of` ×1, `shift` ×1,
|
||||
`remove` ×1, `reverse` ×1. `print_err` joins this set — Part 2 already named
|
||||
it; this table never listed it.
|
||||
|
||||
These are **always in scope** — no `use` line, no namespace — the same status
|
||||
`print`, `print_int`, `now`, `words`, `count`, `latest` already have, and they
|
||||
share the same flat `WO_B_*` id space in the VM's builtin table as every other
|
||||
builtin. Grouping into text operations, collection operations, and map
|
||||
operations is documentation only, not namespaces. Each builtin has a
|
||||
fixed-arity typed contract, resolved at compile time like every other
|
||||
builtin; out-of-range indices **trap** (`T_BOUNDS`), never return a sentinel.
|
||||
|
||||
**Deliberately not adopted:** iteration/closure builtins (`map`, `filter`,
|
||||
`reduce`) — the language has no function-value type, and adding higher-order
|
||||
functions would require one.
|
||||
|
||||
## Part 4 — The sample workload
|
||||
|
||||
`docs/examples/log-watcher/` — the Haxe original re-expressed in `.wo`, file-for-file:
|
||||
|
|
@ -102,7 +126,7 @@ A README table records the mapping and what (if anything) each file could not ex
|
|||
|
||||
## Error handling
|
||||
|
||||
One system, two surfaces. Traps remain the runtime truth (OOP spec section 6). This track adds the language surface: `try expr catch (e) fallback-expr` — `e` is the structured error record; `throw value` raises EXPLICIT with the value attached. Optionals (`?T`) handle *expected* absence (missing file stat, failed decode, missing env var) — the stdlib returns nil for those, reserving traps/throw for genuine faults. The Haxe original's `try … catch (e:Dynamic) return false` probes become optional-returning calls — clearer than the original.
|
||||
One system, two surfaces. Traps remain the runtime truth (OOP spec section 6). This track adds the language surface: `try expr catch (e) fallback-expr` — `e` is the structured error record; uncaught faults still surface as traps. `throw` (explicit raise) is **cut** — 0 uses in the driving workload; parked post-iteration-12. Optionals (`?T`) handle *expected* absence (missing file stat, failed decode, missing env var) — the stdlib returns nil for those, reserving traps for genuine faults. The Haxe original's `try … catch (e:Dynamic) return false` probes become optional-returning calls — clearer than the original.
|
||||
|
||||
## Testing
|
||||
|
||||
|
|
@ -114,10 +138,10 @@ One system, two surfaces. Traps remain the runtime truth (OOP spec section 6). T
|
|||
|
||||
1. The keyword table is fully implemented: every **adopt** row parses, typechecks, and executes with corpus coverage; every **reject** row has a diagnostic or a documented absence.
|
||||
2. `fn main` program mode: `wo run` executes a CLI program; exit codes propagate; `woc build` produces a self-contained binary for it.
|
||||
3. All five stdlib modules pass their corpus fixtures; handle RAII is ASan-proven.
|
||||
3. All six stdlib modules pass their corpus fixtures; handle RAII is ASan-proven.
|
||||
4. `docs/examples/log-watcher/` compiles and its README mapping table has an empty "could not express" column.
|
||||
5. The sample's watch mode detects a silent death (error-final + quiet period) end to end on a real tempfile.
|
||||
|
||||
## Out of scope (named)
|
||||
|
||||
Threads/worker pools in program mode (the shard-actor track owns concurrency); UDP/TLS; `fs` mutation beyond append; signal callbacks; sqlite-equivalent embedded SQL over RAM (that is the DB engine's job — a future sample can wire MiniLog's idea to `select`); Haxe macro-based reflection idioms.
|
||||
Threads/worker pools in program mode (the shard-actor track owns concurrency); UDP/TLS; `fs` mutation beyond append; signal callbacks; sqlite-equivalent embedded SQL over RAM (that is the DB engine's job — a future sample can wire MiniLog's idea to `select`); Haxe macro-based reflection idioms. `throw` (explicit raise), `time.mono`, and `is` are also cut — 0 uses in the driving workload each; parked post-iteration-12.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
# log-watcher gap closure — spec amendments + plan reconciliation
|
||||
|
||||
**Date:** 2026-08-10
|
||||
**Status:** approved design, pre-implementation
|
||||
**Scope:** amending the systems-track spec with four surface gaps the log-watcher
|
||||
sample revealed, recording three scope cuts, correcting two false status claims,
|
||||
and retiring a parallel roadmap
|
||||
**Amends:** [`2026-08-01-systems-track-design.md`](2026-08-01-systems-track-design.md)
|
||||
(Part 1 verdict table, Part 3 stdlib table)
|
||||
**Supersedes:** `docs/00-code-review.md` (extracted, then reduced to a stub)
|
||||
**Companion specs:** [`2026-08-01-oop-compiler-vm-design.md`](2026-08-01-oop-compiler-vm-design.md)
|
||||
**Affected plans:** [plan 8](../../plan/compiler/2026-08-01-haxe-parity-language.md),
|
||||
[plan 9](../plans/2026-08-01-program-mode-stdlib.md),
|
||||
[plan 10](../plans/2026-08-01-log-watcher-sample.md)
|
||||
|
||||
## Motivation
|
||||
|
||||
`docs/00-code-review.md` was written as a gap analysis: what must exist before
|
||||
the log-watcher sample compiles. Verified against the code, most of its
|
||||
"missing" rows are correct — the front end genuinely cannot lex or parse roughly
|
||||
200 constructs the sample uses. But it also carried three false claims, omitted
|
||||
the single largest piece of work, and proposed a Phase 1–4 roadmap that competes
|
||||
with the approved story iterations.
|
||||
|
||||
Its real contribution is four surface gaps that **no approved spec ever named**.
|
||||
Those are the substance of this amendment. The competing roadmap is retired; the
|
||||
false claims are corrected at their sources.
|
||||
|
||||
Measured baseline, `woc docs/examples/log-watcher` over 7 files:
|
||||
**307 diagnostics** — 167 `WO-E101` (parse) + 140 `WO-E207` (unknown type).
|
||||
Iteration 7 closes when that number is zero.
|
||||
|
||||
## Decisions locked during brainstorming
|
||||
|
||||
| Question | Decision |
|
||||
| --- | --- |
|
||||
| Status of `docs/00-code-review.md` | **Scratch input.** Findings extracted here; the file becomes a stub pointing at `docs/00-status.md`. Its Phase 1–4 roadmap is retired — story iterations 4→5→6→7 remain the only sequence. |
|
||||
| Shape of the text/collection builtins | **Bare globals, no import.** `len`, `push`, `split_ws` … are always in scope, like the existing `print`/`now`/`count`/`latest`. Capability modules (`fs`, `proc`, `net`, `time`, `json`, `env`) stay `use`-imported and qualified. |
|
||||
| `throw` | **Cut** from the critical path — 0 uses in the sample. |
|
||||
| `time.mono` | **Cut** — 0 uses. |
|
||||
| `is` | **Cut** — 0 uses. Empties plan 8 Task 7, whose `abstract` half was rejected 2026-08-10, so the task is deleted rather than deferred. |
|
||||
| `#if` build flags | **Kept** in plan 8 Task 9 despite 0 uses. |
|
||||
| Emitter sequencing | **Unchanged: iteration 4 before iteration 5.** Plan 8's tasks state that features "lower onto existing opcodes" and name exactly three fenced VM changes — wording that presupposes an emitter. Growing the surface first would force a much larger emitter later. |
|
||||
|
||||
## 1. Amendment — core builtins (new Part 3 section)
|
||||
|
||||
The sample calls **22 unqualified builtin names across ~176 sites**, none of
|
||||
them in any spec: `len` ×55, `push` ×17, `byte_at` ×11, `starts_with` ×9,
|
||||
`index_of` ×8, `has` ×7, `split` ×6, `split_ws` ×6, `join` ×5, `parse_int` ×5,
|
||||
`trim` ×4, `slice` ×4, `substr` ×4, `pop` ×3, `ends_with` ×2,
|
||||
`last_index_of` ×2, `to_lower` ×2, `sort` ×2, `char_of` ×1, `shift` ×1,
|
||||
`remove` ×1, `reverse` ×1.
|
||||
|
||||
Part 3 gains a **core builtins** section, distinct from the capability modules:
|
||||
|
||||
- **Always in scope.** No `use` line, no namespace — the same status the
|
||||
existing `print`, `print_int`, `now`, `words`, `count`, `latest` builtins
|
||||
already have, and the same flat `WO_B_*` id space in the VM's builtin table.
|
||||
- **Rationale.** The sample committed to this spelling at 176 sites before the
|
||||
spec had an opinion, and "samples force the grammar" (principle 8) makes that
|
||||
binding. Qualifying them (`text.len`) would add an import line to every file
|
||||
and buy nothing: these are language primitives, not capabilities — they touch
|
||||
no syscall, need no audit, and cannot be refused.
|
||||
- **Grouping** (documentation only, not namespaces): text operations,
|
||||
collection operations, map operations.
|
||||
- **Contract per builtin** is fixed arity with typed parameters, resolved at
|
||||
compile time like every other builtin; out-of-range indices trap
|
||||
(`T_BOUNDS`), never return a sentinel.
|
||||
- `print_err` joins this set (Part 2 already named it; Part 3 never listed it).
|
||||
|
||||
**Deliberately not adopted:** iteration/closure builtins (`map`, `filter`,
|
||||
`reduce`) — the sample uses explicit loops throughout, and adding higher-order
|
||||
functions would require a function-value type the language does not have.
|
||||
|
||||
## 2. Amendment — `time` gains calendar surface
|
||||
|
||||
Part 3's `time` row lists `now()`, `mono()`, `sleep(ms)`. The sample also calls:
|
||||
|
||||
| Builtin | Sites | Why |
|
||||
| --- | --- | --- |
|
||||
| `time.iso(ms) -> Text` | 2 | JSONL detection timestamps and MCP response fields need a stable textual instant |
|
||||
| `time.local(ms) -> {year, month, day, hour, minute, dow}` | 1 | cron next-fire computation needs broken-out calendar fields, including day-of-week |
|
||||
|
||||
Both are added. `mono()` is **cut** (0 uses) and returns when a workload needs
|
||||
monotonic math. The record `time.local` returns is a plain value record, so it
|
||||
needs no new type machinery beyond the `typedef` records plan 8 Task 4 already
|
||||
lands.
|
||||
|
||||
## 3. Amendment — boolean operators (new verdict-table row)
|
||||
|
||||
The sample uses `and` at 35 sites and `or` at 20. Neither exists in the
|
||||
language, and — the reason this went unnoticed — **neither the verdict table nor
|
||||
any plan ever mentioned boolean operators at all**, in either spelling: there is
|
||||
no `&&`/`||` row, and Haxe's own operators were never enumerated.
|
||||
|
||||
The verdict table gains one row: **`and` / `or` — adopt**, spelled as words
|
||||
rather than `&&`/`||`.
|
||||
|
||||
- **Spelling rationale.** The sample chose words; the lexer has no `&` case at
|
||||
all (a bare `&` reports `WO-E001`), so words cost nothing to add as keywords,
|
||||
and they read better in the conditional-heavy code the sample is full of.
|
||||
- **Precedence.** One new level **below** comparison and above assignment:
|
||||
`or` binds loosest, then `and`, then comparison, then the existing arithmetic
|
||||
ladder. This makes `if a == 1 and b == 2` parse as intended without
|
||||
parentheses — the sample's dominant shape.
|
||||
- **Semantics.** Short-circuit, `Bool`-typed operands only, `Bool` result. No
|
||||
truthiness — a non-`Bool` operand is a type error, consistent with principle 13.
|
||||
- **Lowering.** Compare-and-jump on existing opcodes (`JZ` plus a jump), no new
|
||||
opcode and no VM change.
|
||||
|
||||
## 4. Amendment — `env` is the sixth module
|
||||
|
||||
Part 2 specifies `env.args()`, `env.get(name)`, `env.exit(code)`,
|
||||
`env.stopping()`; Part 3's table omits `env` while calling itself "five builtin
|
||||
modules". The sample uses `env.get` ×1 and `env.stopping` ×4. Part 3's table
|
||||
gains an `env` row and the count becomes six.
|
||||
|
||||
## 5. Corrections to false status claims
|
||||
|
||||
Both were asserted in the review doc; both are corrected at their real source
|
||||
rather than in the retired file.
|
||||
|
||||
**Structural interface satisfaction is not implemented — and is unreachable by
|
||||
design, not owed.** `WO-E205` is declared and never emitted. The review listed
|
||||
satisfaction checking as *implemented*, which is false; but calling it a gap in
|
||||
shipped work is equally wrong. Satisfaction is structural — there is no
|
||||
`implements` keyword by doctrine — so the check has exactly one home: sites
|
||||
where a value is used at an interface-typed position. The milestone grammar has
|
||||
no such positions (no interface-typed fields, parameters, or returns are
|
||||
exercised), so the check cannot fire yet and its absence costs nothing. The
|
||||
error catalog re-files `WO-E205` as **unreachable until interface-typed
|
||||
positions exist**, and `types.ml`'s module header stops claiming it produces a
|
||||
satisfaction set. The log-watcher sample declares no interfaces, so this stays
|
||||
off the critical path.
|
||||
|
||||
**`?T` is plumbed, not enforced.** The review listed "Nullable types `?T`" as
|
||||
implemented — the same conflation already corrected in
|
||||
`docs/plan/compiler/nullable-types-implementation.md`. No new action; noted here
|
||||
so the two documents agree.
|
||||
|
||||
## 6. What the review omitted — the emitter
|
||||
|
||||
The review is titled "Compilation Requirements" and its Phase 4 promises
|
||||
"end-to-end compile + run", but it never lists the bytecode emitter as work.
|
||||
There is no `emit.ml`, no byte-writing anywhere in the compiler, no `--emit` or
|
||||
`-o` flag, and `wob_kind_of_typ` exists but is never called. That is
|
||||
[plan 3](../../plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md) — story iteration 4,
|
||||
an entire slice — and it is the current NEXT PLAN.
|
||||
|
||||
Consequence for iteration 4's scope, to be stated on the board: the emitter
|
||||
proves the pipeline on the **milestone grammar only**. The sample's ~200
|
||||
unparseable constructs are iterations 5–6 work; iteration 4 must not be judged
|
||||
against the sample.
|
||||
|
||||
## 7. Plan updates
|
||||
|
||||
| Plan | Change |
|
||||
| --- | --- |
|
||||
| [plan 8](../../plan/compiler/2026-08-01-haxe-parity-language.md) | Task 2 gains `and`/`or` (keywords, new precedence level, compare-and-jump lowering, short-circuit, `Bool`-only). Task 5 loses `throw` — catch frames ship without the explicit-raise half, and the error-payload slot plus its `.wob` version note go with it. **Task 7 is deleted** (`is` cut, `abstract` rejected); later tasks renumber. The Goal line drops `is`. |
|
||||
| [plan 9](../plans/2026-08-01-program-mode-stdlib.md) | Gains a core-builtins task covering the 22 bare globals plus `print_err`. `time` task gains `iso` and `local`, loses `mono`. `env` is named as a module rather than loose Part-2 prose. |
|
||||
| [plan 3](../../plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md) | Unchanged. |
|
||||
| [plan 10](../plans/2026-08-01-log-watcher-sample.md) | Acceptance gains the diagnostic-count gate: 307 → 0. |
|
||||
| [`01-error-catalog.md`](../../plan/oop-vm/01-error-catalog.md) | `WO-E205` re-filed as unreachable-by-design with its reason; `WO-E208`/`E210`/`E211`–`E213` keep their existing reserved entries. |
|
||||
| [`docs/00-status.md`](../../00-status.md) | NEXT PLAN gains the milestone-grammar-only note; pending list gains the three cuts under the parked section. |
|
||||
| `docs/00-code-review.md` | Reduced to a stub: one paragraph saying its findings landed here and in the plans, pointing at `docs/00-status.md`. |
|
||||
|
||||
## Error handling
|
||||
|
||||
Nothing in this amendment adds an error-handling mechanism. `and`/`or` produce
|
||||
ordinary type errors on non-`Bool` operands (reusing `WO-E201` once that code is
|
||||
wired). Core builtins trap on out-of-range access rather than returning
|
||||
sentinels, matching the existing container builtins. Cutting `throw` leaves the
|
||||
uncaught-trap surface exactly as it is today.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Per amendment, corpus fixtures in the task that lands it:** `and`/`or` get
|
||||
precedence goldens (including `a == 1 and b == 2` without parens),
|
||||
short-circuit behavior, and a must-fail for a non-`Bool` operand. Each core
|
||||
builtin gets a golden exercising it plus a bounds-trap fixture where indices
|
||||
apply. `time.iso`/`time.local` get fixtures against a fixed injected clock so
|
||||
output is deterministic.
|
||||
- **The sample is the integration test.** `woc docs/examples/log-watcher` is run
|
||||
at the end of every iteration from 5 onward and its diagnostic count recorded;
|
||||
the number must fall monotonically from 307 and reach 0 at iteration 7.
|
||||
- **No new VM tests** from this amendment except the core builtins' own, since
|
||||
`and`/`or` add no opcode and `time`/`env` extend an existing module pattern.
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. The systems-track spec's Part 1 has a boolean-operator row and its Part 3
|
||||
lists six modules plus a core-builtins section covering all 22 names.
|
||||
2. Plans 8 and 9 reflect every addition and cut; plan 8 Task 7 is gone.
|
||||
3. `WO-E205` is documented as unreachable-by-design, and `types.ml`'s header no
|
||||
longer claims a satisfaction set is produced.
|
||||
4. `docs/00-code-review.md` is a stub; no second roadmap exists in the repo.
|
||||
5. The 307-diagnostic baseline is recorded in plan 10 as its acceptance gate.
|
||||
|
||||
## Out of scope
|
||||
|
||||
`throw`, `time.mono`, `is`, higher-order/iteration builtins, and interface
|
||||
satisfaction enforcement — each parked with its reason above. Implementing any
|
||||
amendment is the plans' job, not this spec's.
|
||||
Loading…
Reference in a new issue