From 138b393bd15c0e011b9af0ba7ae7d082c2a86cf1 Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Mon, 10 Aug 2026 14:11:24 +0200 Subject: [PATCH] docs: update plan docs (Phases 5-16) - 05: hand-rolled JSON - 06: bespoke error type - 07: inotify content watcher - 08: sendfile static assets - 09: concurrency scaleout - 10: storage foundations - 11: WAL and recovery - 12: engine disk cutover - 14: MVC UI implementation - 15: MCP streamable HTTP - 16: Postgres mirror --- docs/plan/05-hand-rolled-json.md | 4 ++-- docs/plan/06-bespoke-error.md | 6 +++--- docs/plan/07-inotify-content-watcher.md | 6 +++--- docs/plan/08-sendfile-static-assets.md | 12 ++++++------ docs/plan/09-concurrency-scaleout.md | 20 ++++++++++---------- docs/plan/10-storage-foundations.md | 8 ++++---- docs/plan/11-wal-and-recovery.md | 2 +- docs/plan/12-engine-disk-cutover.md | 2 +- docs/plan/14-mvc-ui-implementation.md | 6 +++--- docs/plan/15-mcp-streamable-http.md | 7 ++++--- docs/plan/16-postgres-mirror.md | 2 +- 11 files changed, 38 insertions(+), 37 deletions(-) diff --git a/docs/plan/05-hand-rolled-json.md b/docs/plan/05-hand-rolled-json.md index f3e4ca7..051ea31 100644 --- a/docs/plan/05-hand-rolled-json.md +++ b/docs/plan/05-hand-rolled-json.md @@ -91,7 +91,7 @@ Covers 95% of current `serde_json::json!(...)` uses in the codebase. For the oth - `emit_stable_key_order` — emitting a `BTreeMap`-backed object produces keys in sorted order (matters for `.rest` expected-body stability). - `parse_errors` — unterminated string, trailing comma, missing comma, unclosed object all return `ParseError` with line/col. 3. **All 14 existing `rt` tests pass** after the swap (the `engine::Engine` and `server::*` tests most affected). -4. **`reference/rest/blog.rest`** — 20 assertions all return the same HTTP status AND the same response body shape (may differ in key ordering if `BTreeMap` ordering differs from `serde_json`'s insertion order — document the shift). +4. **`.dev/reference/rest/blog.rest`** — 20 assertions all return the same HTTP status AND the same response body shape (may differ in key ordering if `BTreeMap` ordering differs from `serde_json`'s insertion order — document the shift). 5. **Dep audit.** `cargo tree -p rt --depth 1` shows zero `serde*` lines. ## Non-scope @@ -108,7 +108,7 @@ cargo build # three deps cargo test --lib json # new parser/emitter tests cargo test --lib # 14 existing tests still green # full .rest smoke — same script as phase 04 exit criterion 3 -cd reference/crates && cargo build && cargo test +cd .dev/reference/crates && cargo build && cargo test ``` ## After this phase diff --git a/docs/plan/06-bespoke-error.md b/docs/plan/06-bespoke-error.md index 3ff8d96..3e7ea9b 100644 --- a/docs/plan/06-bespoke-error.md +++ b/docs/plan/06-bespoke-error.md @@ -94,8 +94,8 @@ Find: `rg 'anyhow::|anyhow!|bail!|\.context\(' crates/rt/src | wc -l` — expect 1. **`cargo build`** — compiles with exactly one external dep. 2. **`cargo test --lib`** — all 14 existing `rt` tests pass. A new test in `error.rs` exercises `From`, `with_context`, and `Display` formatting. 3. **No `anyhow::` references anywhere in the repo.** `rg 'anyhow' crates/ docs/` returns zero hits (docs updated by this phase too). -4. **`reference/rest/blog.rest`** — 20 assertions still pass. Error paths (404, 400) still produce the same response body format (plain-text error message from the handler's `.to_string()`). -5. **`cd reference/crates && cargo build && cargo test`** unchanged. V1 doesn't use `anyhow` — nothing to touch there. +4. **`.dev/reference/rest/blog.rest`** — 20 assertions still pass. Error paths (404, 400) still produce the same response body format (plain-text error message from the handler's `.to_string()`). +5. **`cd .dev/reference/crates && cargo build && cargo test`** unchanged. V1 doesn't use `anyhow` — nothing to touch there. 6. **`cargo tree -p rt --depth 1`** lists only `libc` as an external dep (plus transitive ones brought in by libc itself, all of which are kernel-facing). ## Non-scope @@ -111,7 +111,7 @@ cargo build # one external dep cargo test --lib # 14 + error.rs test green rg 'anyhow' crates/ docs/ # zero hits # full .rest smoke — same script as phase 04 -cd reference/crates && cargo build && cargo test # v1 untouched +cd .dev/reference/crates && cargo build && cargo test # v1 untouched cat crates/rt/Cargo.toml | grep -A 20 '\[dependencies\]' # libc is the only line ``` diff --git a/docs/plan/07-inotify-content-watcher.md b/docs/plan/07-inotify-content-watcher.md index 481b653..666871f 100644 --- a/docs/plan/07-inotify-content-watcher.md +++ b/docs/plan/07-inotify-content-watcher.md @@ -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`. 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`. Registers on the `EventLoop`. | [`.dev/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 | @@ -88,7 +88,7 @@ for event in loop_.wait_once(None)? { # 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. -5. **All 14 `rt` unit tests still pass.** `reference/rest/blog.rest` 20-assertion battery still green. +5. **All 14 `rt` unit tests still pass.** `.dev/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 @@ -116,7 +116,7 @@ curl -s http://127.0.0.1:8080/api/articles # server did not restart; catalog kill $PID git checkout docs/examples/blog/types/article.wo # undo the edit -cd reference/crates && cargo build && cargo test # v1 untouched +cd .dev/reference/crates && cargo build && cargo test # v1 untouched ``` ## After this phase diff --git a/docs/plan/08-sendfile-static-assets.md b/docs/plan/08-sendfile-static-assets.md index 5aa7921..8903a14 100644 --- a/docs/plan/08-sendfile-static-assets.md +++ b/docs/plan/08-sendfile-static-assets.md @@ -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` | [`.dev/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 | [`.dev/reference/crates/wo-serve/src/resolve.rs`](../../.dev/reference/crates/wo-serve/src/resolve.rs) (80 LOC) | +| `mime.rs` | Extension → `Content-Type` table | [`.dev/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). @@ -74,7 +74,7 @@ The `Response` returned by `handler.serve()` owns the open `File` fd. The phase- ``` 4. **Path traversal attempts fail closed.** `curl :8080/static/../Cargo.toml` returns 403. `curl :8080/static/nonexistent.png` returns 404. 5. **`EAGAIN` handling.** A test that rate-limits the socket sendbuf to force a partial write exercises the `EPOLLOUT` re-arm path; the full payload still arrives. -6. **All 14 `rt` tests** + phase-02/03/04/05/06/07 additions pass. `reference/rest/blog.rest` 20 assertions still green (no regressions on the JSON endpoints). +6. **All 14 `rt` tests** + phase-02/03/04/05/06/07 additions pass. `.dev/reference/rest/blog.rest` 20 assertions still green (no regressions on the JSON endpoints). ## Non-scope @@ -96,9 +96,9 @@ cargo run --bin wo -- run docs/examples/blog & # ... (full script from exit criterion 3) # .rest smoke unchanged -# full 20-assertion battery against reference/rest/blog.rest +# full 20-assertion battery against .dev/reference/rest/blog.rest -cd reference/crates && cargo build && cargo test # v1 untouched +cd .dev/reference/crates && cargo build && cargo test # v1 untouched ``` ## After this phase diff --git a/docs/plan/09-concurrency-scaleout.md b/docs/plan/09-concurrency-scaleout.md index eed28bd..8d803e0 100644 --- a/docs/plan/09-concurrency-scaleout.md +++ b/docs/plan/09-concurrency-scaleout.md @@ -2,7 +2,7 @@ > **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) -**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`](./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), [`.dev/reference/go/src/runtime/`](../../.dev/reference/go/src/runtime/) (precedent for a runtime that scales across threads). ## Context @@ -28,7 +28,7 @@ Serve the ecommerce sample at 10,000 concurrent websocket subscribers + 1,000 ch ## 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 [`.dev/reference/go/src/runtime/netpoll_epoll.go`](../../.dev/reference/go/src/runtime/netpoll_epoll.go) and [`.dev/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 | | --- | --- | @@ -53,10 +53,10 @@ 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) | +| `SO_REUSEPORT` | N listener sockets on the same port; kernel load-balances accepts | [`.dev/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 | [`.dev/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 | [`.dev/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 | [`.dev/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`](./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) | @@ -132,7 +132,7 @@ If the "single core per process, shard across processes" argument ([Redis Cluste - [`./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. - [`../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. +- [`.dev/reference/go/src/runtime/proc.go`](../../.dev/reference/go/src/runtime/proc.go) — Go's scheduler, for contrast. +- [`.dev/reference/go/src/runtime/netpoll_epoll.go`](../../.dev/reference/go/src/runtime/netpoll_epoll.go) — per-P netpoller, the idea we borrow. +- [`.dev/reference/linux/net/core/sock_reuseport.c`](../../.dev/reference/linux/net/core/sock_reuseport.c) — kernel load balancer. +- [`.dev/reference/linux/kernel/sched/core.c`](../../.dev/reference/linux/kernel/sched/core.c) — affinity syscalls. diff --git a/docs/plan/10-storage-foundations.md b/docs/plan/10-storage-foundations.md index 4b0f696..fb9cea4 100644 --- a/docs/plan/10-storage-foundations.md +++ b/docs/plan/10-storage-foundations.md @@ -2,7 +2,7 @@ > **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) -**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), [`.dev/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); fn decode(&self, bytes: &[u8]) -> Result; }` + `JsonCodec` impl backed by today's `serde_json`. | ~50 | | `seg.rs` | `SegStore { dir: PathBuf, fds: HashMap, tails: HashMap }`. `open(dir)`, `append(ty, &Row) -> Result`, `read(ty, offset) -> Result` (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 [`.dev/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 `/` @@ -102,7 +102,7 @@ The root workspace member list also activates: `crates/db` joins `crates/rt` as - `seg_append_writes_to_disk` — `append` then re-`open` reads the same row back. - `seg_grows_when_full` — appending past the initial 1 MiB triggers a fallocate-grow without losing existing records. 3. **End-to-end** — `cargo run --bin wo -- run docs/examples/blog`, `curl -X POST /api/articles` with a body, then `xxd docs/examples/blog/data/Article.seg | head -3` — output shows the magic length prefix and the JSON payload. -4. **`reference/rest/blog.rest`** — 20-assertion battery still passes byte-identically. +4. **`.dev/reference/rest/blog.rest`** — 20-assertion battery still passes byte-identically. 5. **Restart leaves the segment on disk but ignores it.** `wo run`, write 5 rows, ctrl-C, `wo run` again, `GET /api/articles` returns `[]`. The segment file still exists. Phase 11 will start replaying it. ## Non-scope @@ -140,7 +140,7 @@ sleep 1 curl -s http://127.0.0.1:8080/api/articles # expect [] kill -INT $PID -cd reference/crates && cargo build && cargo test # v1 untouched +cd .dev/reference/crates && cargo build && cargo test # v1 untouched ``` ## After this phase diff --git a/docs/plan/11-wal-and-recovery.md b/docs/plan/11-wal-and-recovery.md index 264af7d..ea1f925 100644 --- a/docs/plan/11-wal-and-recovery.md +++ b/docs/plan/11-wal-and-recovery.md @@ -203,7 +203,7 @@ kill -INT $PID strace -e fdatasync,fsync,rename -f -p $(pgrep -f 'target/debug/wo run') 2>&1 | head -20 # v1 untouched -cd reference/crates && cargo build && cargo test +cd .dev/reference/crates && cargo build && cargo test ``` ## After this phase diff --git a/docs/plan/12-engine-disk-cutover.md b/docs/plan/12-engine-disk-cutover.md index db801ea..63c1d42 100644 --- a/docs/plan/12-engine-disk-cutover.md +++ b/docs/plan/12-engine-disk-cutover.md @@ -165,7 +165,7 @@ curl -s 'http://127.0.0.1:8080/api/articles' | python3 -c 'import json,sys;print # expect: 10000 kill -INT $PID -cd reference/crates && cargo build && cargo test # v1 untouched +cd .dev/reference/crates && cargo build && cargo test # v1 untouched ``` ## After this phase diff --git a/docs/plan/14-mvc-ui-implementation.md b/docs/plan/14-mvc-ui-implementation.md index 4ef7817..65d5ee9 100644 --- a/docs/plan/14-mvc-ui-implementation.md +++ b/docs/plan/14-mvc-ui-implementation.md @@ -2,7 +2,7 @@ > **Kanban: ⏸ parked (frontend)** — backend focus first; design stays current. Board: [00-kanban.md](00-kanban.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), [`.dev/reference/crates/wo-htmlx/`](../../.dev/reference/crates/wo-htmlx/) (the v1 template engine, primary port source). ## Context @@ -29,7 +29,7 @@ Phases 05/06 (hand-rolled JSON / bespoke error) are orthogonal: `crates/ui` adop ### `14a-htmlx-engine.md` — port the view engine into `crates/ui` -Execute [`exploration/ui/01-htmlx-format-spec.md`](./exploration/ui/01-htmlx-format-spec.md) as written: port `reference/crates/wo-htmlx` (585 LOC — `parser.rs`, `ast.rs`, `value.rs`, `registry.rs`, `render.rs` carried over per its table) into `crates/ui/src/htmlx/`, extend with `` structured nodes, `wo:bind` capture, and the `data-wo-manifest` JSON emitter (~250 LOC new). One addition beyond the 01 spec, from the MVC design: `` records whether `source` is a bare name (controller model binding, resolved in 14d) or an inline query — a one-field change to `LiveSubscription`. +Execute [`exploration/ui/01-htmlx-format-spec.md`](./exploration/ui/01-htmlx-format-spec.md) as written: port `.dev/reference/crates/wo-htmlx` (585 LOC — `parser.rs`, `ast.rs`, `value.rs`, `registry.rs`, `render.rs` carried over per its table) into `crates/ui/src/htmlx/`, extend with `` structured nodes, `wo:bind` capture, and the `data-wo-manifest` JSON emitter (~250 LOC new). One addition beyond the 01 spec, from the MVC design: `` records whether `source` is a bare name (controller model binding, resolved in 14d) or an inline query — a one-field change to `LiveSubscription`. **Exit:** the 01 spec's criteria — `cargo build -p ui` green, golden parse+render for every `.htmlx` under `docs/examples/{blog,ecommerce}` **plus** [`pricing/ui/pricing/pricing.htmlx`](../examples/pricing/ui/pricing/pricing.htmlx), manifest matches the 01 schema. ### `14b-scss-subset.md` — the stylesheet compiler @@ -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. +- [`.dev/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. diff --git a/docs/plan/15-mcp-streamable-http.md b/docs/plan/15-mcp-streamable-http.md index 040d853..46e3701 100644 --- a/docs/plan/15-mcp-streamable-http.md +++ b/docs/plan/15-mcp-streamable-http.md @@ -2,7 +2,7 @@ > **Kanban: ⬜ not started (Track 4 — Language & API)** — board: [00-kanban.md](00-kanban.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), [`.dev/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 → `_list`, `_get`, `_create`, `_update`, `_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 [`.dev/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 @@ -98,7 +98,7 @@ The rules the sub-phases implement, condensed from the spec — each MUST below | Check | Target | How | | --- | --- | --- | -| Spec conformance | T1–T8 matrix green (status codes, headers, content types) | scripted curl flow checked in beside `reference/rest/` | +| Spec conformance | T1–T8 matrix green (status codes, headers, content types) | scripted curl flow checked in beside `.dev/reference/rest/` | | Interop | MCP Inspector connects, lists tools/resources, calls a tool | manual check, noted per release | | Parity | `tools/call _get` ≡ `GET /api//:id` byte-for-byte on the row payload | unit test | | Durability | mutation results never precede their fsync CQE (`WO_GROUP_COMMIT` on) | gate test in `server.rs` style | @@ -122,3 +122,4 @@ The rules the sub-phases implement, condensed from the spec — each MUST below - [`./05-hand-rolled-json.md`](05-hand-rolled-json.md) — removes this plan's `serde_json` use when it lands. - [`./07-inotify-content-watcher.md`](07-inotify-content-watcher.md) — a future `notifications/tools/list_changed` on hot reload would pair with it (not scheduled). - [MCP specification 2025-06-18](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) — the normative transport text summarized in T1–T8. +- [`../examples/mcp-think/`](../examples/mcp-think/README.md) — the consumer-side counterpart: a working stdio MCP server (local model via Ollama) that Claude calls today; useful as a live MCP client/server reference while building 15a. diff --git a/docs/plan/16-postgres-mirror.md b/docs/plan/16-postgres-mirror.md index f5e6694..fc578a3 100644 --- a/docs/plan/16-postgres-mirror.md +++ b/docs/plan/16-postgres-mirror.md @@ -2,7 +2,7 @@ > **Kanban: 🔄 in progress (Track 3 — Storage & durability)** — 16a ✅, 16b ✅ shipped; 16c–16f ⬜. Board: [00-kanban.md](00-kanban.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*. +**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), `.dev/reference/postgresql/` (research symlink — `src/include/libpq/` for the wire protocol), PostgreSQL docs *Frontend/Backend Protocol*. ## Context