From c93915a3cd514d50c9001b9679856788bb2196fb Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Tue, 21 Apr 2026 02:48:45 +0200 Subject: [PATCH] Pivot to the .wo language runtime: design docs, phase plans --- .gitignore | 28 +- CLAUDE.md | 138 ++++++ README.md | 110 +++-- crates/README.md | 54 +++ docs/examples/blog/README.md | 175 +++++++ docs/examples/ecommerce/README.md | 251 ++++++++++ .../ai-agents-content-management.md | 27 +- docs/plan/02-event-loop-epoll.md | 95 ++++ docs/plan/03-hand-rolled-http.md | 100 ++++ docs/plan/04-cutover-remove-tokio-axum.md | 119 +++++ docs/plan/05-hand-rolled-json.md | 114 +++++ docs/plan/06-bespoke-error.md | 120 +++++ docs/plan/07-inotify-content-watcher.md | 124 +++++ docs/plan/08-sendfile-static-assets.md | 108 +++++ docs/plan/done/01-scafolding-crates.md | 68 +++ docs/{ => plan/linux}/00-linux.md | 22 +- docs/plan/linux/01-epoll.md | 75 +++ docs/plan/linux/02-eventfd.md | 66 +++ docs/plan/linux/03-timerfd.md | 77 +++ docs/plan/linux/04-signalfd.md | 77 +++ docs/plan/linux/05-inotify.md | 88 ++++ docs/plan/linux/06-sendfile.md | 78 +++ docs/plan/linux/07-io_uring.md | 97 ++++ docs/plan/linux/08-mmap.md | 100 ++++ docs/plan/linux/09-fallocate.md | 96 ++++ docs/plan/linux/10-pidfd.md | 93 ++++ docs/plan/linux/11-memfd_create.md | 102 ++++ docs/runtime/database.md | 65 +++ docs/runtime/database/01-evaluation.md | 272 +++++++++++ docs/runtime/database/02-wo-language.md | 435 +++++++++++++++++ docs/runtime/database/03-inmemory-engine.md | 205 ++++++++ docs/runtime/database/04-client-api.md | 279 +++++++++++ docs/runtime/database/05-go-sdk.md | 371 ++++++++++++++ docs/runtime/database/06-lowcode-fullstack.md | 389 +++++++++++++++ docs/runtime/database/07-wo-seg-migration.md | 209 ++++++++ docs/runtime/fibers.md | 451 ++++++++++++++++++ docs/runtime/wo-language.md | 249 ++++++++++ prototypes/wo-db/README.md | 140 ++++++ reference/README.md | 35 ++ reference/rest/README.md | 92 ++++ 40 files changed, 5731 insertions(+), 63 deletions(-) create mode 100644 CLAUDE.md create mode 100644 crates/README.md create mode 100644 docs/examples/blog/README.md create mode 100644 docs/examples/ecommerce/README.md create mode 100644 docs/plan/02-event-loop-epoll.md create mode 100644 docs/plan/03-hand-rolled-http.md create mode 100644 docs/plan/04-cutover-remove-tokio-axum.md create mode 100644 docs/plan/05-hand-rolled-json.md create mode 100644 docs/plan/06-bespoke-error.md create mode 100644 docs/plan/07-inotify-content-watcher.md create mode 100644 docs/plan/08-sendfile-static-assets.md create mode 100644 docs/plan/done/01-scafolding-crates.md rename docs/{ => plan/linux}/00-linux.md (83%) create mode 100644 docs/plan/linux/01-epoll.md create mode 100644 docs/plan/linux/02-eventfd.md create mode 100644 docs/plan/linux/03-timerfd.md create mode 100644 docs/plan/linux/04-signalfd.md create mode 100644 docs/plan/linux/05-inotify.md create mode 100644 docs/plan/linux/06-sendfile.md create mode 100644 docs/plan/linux/07-io_uring.md create mode 100644 docs/plan/linux/08-mmap.md create mode 100644 docs/plan/linux/09-fallocate.md create mode 100644 docs/plan/linux/10-pidfd.md create mode 100644 docs/plan/linux/11-memfd_create.md create mode 100644 docs/runtime/database.md create mode 100644 docs/runtime/database/01-evaluation.md create mode 100644 docs/runtime/database/02-wo-language.md create mode 100644 docs/runtime/database/03-inmemory-engine.md create mode 100644 docs/runtime/database/04-client-api.md create mode 100644 docs/runtime/database/05-go-sdk.md create mode 100644 docs/runtime/database/06-lowcode-fullstack.md create mode 100644 docs/runtime/database/07-wo-seg-migration.md create mode 100644 docs/runtime/wo-language.md create mode 100644 prototypes/wo-db/README.md create mode 100644 reference/README.md create mode 100644 reference/rest/README.md diff --git a/.gitignore b/.gitignore index 1a91d87..b9a4c85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,29 @@ +# Cargo build artifacts /target +/reference/crates/target + +# C++ prototype build output +/prototypes/*/build + +# Runtime data directories for the sample projects. +# `wo.toml` points at `./data` which holds the per-project engine state. /data -/content \ No newline at end of file +/docs/examples/*/data + +# Legacy blog content and data (v1 writeonce storage) +/content + +# Symlink to the Linux kernel source tree for research — user-specific +# absolute path; each contributor sets their own via +# ln -s reference/linux +/reference/linux + +# Editor / OS noise — left broad on purpose so a contributor doesn't +# accidentally commit their IDE scratch or macOS metadata. +.DS_Store +*.swp +*.swo +/.idea/ +/.vscode/* +!/.vscode/settings.json.example +!/.vscode/extensions.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5856ac4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,138 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +writeonce is a **declarative full-stack programming language**. You write `.wo` files; the runtime compiles them into a single binary that owns the database, serves REST, and (Stage 3+) pushes live subscriptions. Think: Go + Postgres + `net/http` + Phoenix LiveView folded into one language. + +## The end goal — zero external deps, kernel primitives only + +The runtime's north star — documented in [`docs/01-problem.md`](docs/01-problem.md), [`docs/02-recovery.md`](docs/02-recovery.md), and [`docs/plan/linux/00-linux.md`](docs/plan/linux/00-linux.md) — is **one binary, no external Rust crates, all I/O driven directly by Linux kernel primitives**. `epoll` (or `io_uring`), `inotify`, `eventfd`, `timerfd`, `signalfd`, `sendfile`, `mmap` — the kernel IS the subscription engine, the async runtime, and the file watcher. + +Target end state of `crates/rt/Cargo.toml`: + +```toml +[dependencies] +libc = "0.2" # the unavoidable FFI bridge to syscalls +``` + +Stage 2 today carries six transitional deps (`anyhow`, `serde`, `serde_json`, `tokio`, `axum`, `tower`). [`docs/plan/02`](docs/plan/02-event-loop-epoll.md) through [`docs/plan/08`](docs/plan/08-sendfile-static-assets.md) sequence the removal of each one, replaced by hand-rolled modules ported from the v1 crates that already did exactly this (`reference/crates/wo-event`, `wo-http`, `wo-route`, `wo-serve`, `wo-watch`). **When working on the runtime, default to direct-syscall solutions over reaching for new crates** — the `docs/plan/` docs name the port source for every module. + +## Layout + +The repo holds three cuts of the same project plus one research reference: + +1. **`crates/rt/`** — the **active Rust runtime** (Stage 2 shipped). Monolithic on purpose for now: lexer, parser, AST, in-memory engine, axum REST server all in one crate. The `wo` binary lives at `crates/rt/src/bin/wo.rs`. +2. **`crates/{ql,value,engine,txn,db,wal,sub,http,gen,policy,logic,service,ui,app}/`** — 14 **empty sibling crates** scaffolded to match the 7-phase design. Each has a `Cargo.toml` + `src/lib.rs` with just a doc comment pointing at its phase spec. Real code moves in from `rt` as each phase activates; do NOT refactor `rt` to use these today — it would break Stage 2. +3. **`reference/crates/`** — the **v1 writeonce blog** (13 crates: `wo-seg`, `wo-index`, `wo-store`, `wo-htmlx`, etc.). This is a **nested Cargo workspace**, deliberately excluded from the root workspace. The v1 crates keep their `wo-` prefix; the new runtime crates dropped theirs. `cd reference/crates && cargo build` builds v1 standalone. See `docs/runtime/database/07-wo-seg-migration.md` for the plan replacing v1 with the new runtime. +4. **`reference/linux/`** — **symlink to the Linux kernel source tree** (`/home/shoney/projects/linux`). Not committed (see `.gitignore`). Research resource for the kernel-primitives work: read `io_uring/`, `fs/notify/inotify/`, `kernel/eventfd.c`, `include/uapi/linux/*.h` when designing the runtime's kernel-facing modules. Each contributor sets their own target via `ln -s reference/linux`. + +There is also **`prototypes/wo-db/`** — a ~2k-line **C++ prototype** of the query-layer engine (SQL + Cypher + document paths, `RETURNING` aliases, `LIVE` stub). It keeps its `wo-db` directory name (C++ project, separate from the Rust crate `db`). It's the reference implementation the Rust port follows; `make test` still passes. + +## Commands + +```bash +# Build + test the runtime +cargo build # compiles all 15 crates +cargo test --lib # 14 unit tests (all in rt today) +cargo test --lib parses_inline_struct -- --nocapture # single named test + +# Run the runtime against a sample project +cargo run --bin wo -- run docs/examples/blog # :8080 — blog sample +cargo run --bin wo -- run docs/examples/ecommerce # :8080 — ecommerce sample +WO_LISTEN=127.0.0.1:9000 cargo run --bin wo -- run docs/examples/blog # override port + +# v1 blog codebase (nested workspace — must cd first) +cd reference/crates && cargo build && cargo test + +# C++ prototype of the query-layer engine +cd prototypes/wo-db && make test # smoke.wo + checkout.wo +cd prototypes/wo-db && make run # interactive REPL + +# Manual HTTP smoke against a running `wo run ...` +# Open reference/rest/blog.rest or ecommerce.rest in VS Code (with REST Client) +# or JetBrains (built-in HTTP client). Or run curl per reference/rest/README.md. +``` + +## Architecture — what requires reading multiple files to understand + +### Naming convention + +- **New runtime crates are unprefixed.** `ql`, `value`, `engine`, `txn`, `db`, `wal`, `sub`, `http`, `gen`, `policy`, `logic`, `service`, `ui`, `app`, `rt`. Internal imports read cleanly: `use ql::Parser`, `use db::Tx`, `use http::router`. +- **V1 crates keep the `wo-` prefix.** `wo-seg`, `wo-index`, `wo-store`, `wo-htmlx`, `wo-md`, and v1's own `wo-rt`/`wo-http`/`wo-sub`. These live in `reference/crates/`. +- **The C++ prototype directory is `prototypes/wo-db/`** — unchanged, not a Rust crate. +- **The binary is `wo`** — defined in `crates/rt/Cargo.toml` `[[bin]]`. Independent of the crate name. + +### The two-layer `.wo` language + +Covered in `docs/runtime/database/02-wo-language.md`: + +- **Schema layer** — unified `type Name { ... }` DSL (fields, embedded structs, `ref`, `multi @edge`, `multi via`, `backlink`, tagged unions, `policy`, `on `, `service`). This is what developers write day-to-day. +- **Query layer** — hybrid SQL + Cypher with five "fixed-glue" rules that make the three grammars share semantics: `$name` parameters everywhere, cross-paradigm `RETURNING col AS alias` visible to later statements in the same `BEGIN … COMMIT`, dotted paths identical in SQL/doc/Cypher, one transaction block syntax, one `LIVE` prefix on subscriptions. + +Both layers are `.wo` files. The schema layer compiles down to query-layer operations — but only when Phase 5 codegen and Phase 6 full-stack blocks need a single authoritative input. Stage 2 ships with the schema layer only. + +### Single-threaded event loop + +Covered in `docs/runtime/database/02-wo-language.md § Concurrency Model` and `03-inmemory-engine.md`. The runtime is Redis/TigerBeetle-style: **one userland thread owns everything** — connection accept, parser, engine, subscription registry. The only non-userland thread is the kernel-owned io_uring SQPOLL helper. This is pinned architecturally — group commit still applies (loop drains many commits into one fsync SQE per tick), and scaling past one core is done by **sharding** independent engine processes, not by adding worker threads. Keep this in mind before proposing `Arc>` anything beyond what's already there. + +### What's in `rt` today vs. what the empty crates promise + +`rt`'s modules deliberately mirror the future crate names so the extraction is mechanical when each phase activates: + +| `rt` module | Will move to | Phase | +| --- | --- | --- | +| `token.rs` + `lexer.rs` + `ast.rs` + `parser.rs` | `ql` | 2 | +| `engine.rs` (Value + Row helpers) | `value` | 2 | +| `engine.rs` (Engine + Catalog) | `engine` | 2 | +| `compile.rs` | `engine` | 2 | +| `server.rs` | `http` + `service` | 4 / 6 | +| `bin/wo.rs` | stays in `rt` (the binary) | — | + +The `sub`, `wal`, `txn`, `policy`, `logic`, `ui`, `app`, `gen` crates have no `rt` counterpart yet — they land when their phase activates. + +### Sample projects drive the grammar + +`docs/examples/blog/` and `docs/examples/ecommerce/` are **both docs artifacts and the de facto integration tests**. The parser survives these because specific features in them (nested `{...}` object literals inside trigger actions, `count(...)` / `words(...)` computed defaults, unions like `Pending | Paid | Shipped`) forced real fixes. When changing the parser, run the full end-to-end against both samples, not just `cargo test`. + +The ecommerce sample in particular uses features that are deliberately **parse-and-discard** in Stage 2: `fn checkout(...) in txn snapshot`, type-attached `on update` triggers with multi-line `do` actions, `policy read for role ...`. These are part of the `.wo` language but Stage 2 does not execute them. + +### The migration story + +`docs/runtime/database/07-wo-seg-migration.md` specifies **phased coexistence**: abstract the v1 article store behind an `ArticleStore` trait, stand up the `.wo` engine as a second implementation, dual-write, cut over, decommission v1. Phase A (trait abstraction) hasn't started — the plan is on paper, the v1 code is still monolithic in `reference/crates/wo-store/`. Do not remove anything from `reference/crates/` without checking the migration doc. + +### Stage progress + +| Stage | Status | +| --- | --- | +| 1 — `wo run ` discovers `.wo` files | ✅ shipped | +| 2 — parser + engine + REST CRUD | ✅ shipped (`cargo run -- run docs/examples/blog`) | +| 3 — LIVE subscriptions over WebSocket | pending — `/api//live` returns 501 as a stub | +| 4+ — transactional `fn`, policies, triggers, `##ui`, WAL, codegen | design-only (see `docs/runtime/database.md`) | + +Stage-3 stubs (501) and policy-shaped 405/404 responses are **intentional and documented** in `reference/rest/*.rest`. Don't "fix" them without checking those files first. + +## Non-obvious gotchas + +- **`rt` is monolithic on purpose.** Splitting it into the 14 sibling crates is Phase-by-Phase work, not a Stage-2 refactor. +- **`reference/crates/` is its own workspace.** Running `cargo build` at the root does not build v1. Running it in `reference/crates/` does. +- **Parser identifiers vs. keywords.** `subscribe`, `receive`, `expect_abort`, `me` are NOT keywords in the lexer — they stay as plain idents so they can appear as operation names in `expose` lists. Adding them to the keyword map breaks `service rest "..." expose subscribe`. +- **Parser skip-on-block.** Unknown triggers (`on update do ...`) are parsed-and-discarded by brace-depth-aware skipping. Object literals like `{ article_id: self.id }` inside trigger actions contain `}` that must not be mistaken for the type's outer close brace — the depth counter exists specifically because of this. +- **Newline significance.** The lexer emits `Kind::Newline` tokens and the parser uses them to end policy/trigger lines. Do not filter newlines globally. +- **Default-value parsing.** `= now()` is recognised explicitly as `DefaultExpr::Now`; anything else falls into an opaque-expression path that `engine::eval_default` then **omits from created rows** (computed fields display as empty, not as debug-printed tokens). +- **Binary variable shadowing.** `crates/rt/src/bin/wo.rs` has `let rt = ...` (a tokio runtime handle) inside `run()` that shadows the crate named `rt`. Inside `run()` the variable wins; inside `serve()` (a different function) `rt::` refers to the crate. Don't rename the variable without also auditing the crate-path references. + +## Where to read next + +- `docs/runtime/wo-language.md` — user-facing language overview +- `docs/runtime/database.md` — 7-phase engineering series index +- `docs/plan/linux/00-linux.md` — catalogue of kernel primitives the runtime leans on +- `docs/plan/02-event-loop-epoll.md` through `08-sendfile-static-assets.md` — the dependency-removal phase sequence +- `docs/plan/done/01-scafolding-crates.md` — the completed crate-scaffolding phase +- `docs/examples/blog/README.md` — the canonical worked example +- `prototypes/wo-db/README.md` — the C++ prototype that shows the query layer +- `reference/rest/README.md` — how to exercise the running prototype +- `reference/README.md` — what's in the v1 archive and why it's preserved +- `reference/linux/` (symlink) — the Linux kernel source tree itself; grep `io_uring/`, `fs/notify/inotify/`, `include/uapi/linux/*.h` when designing kernel-facing modules +- `crates/README.md` — inventory of all 15 crates with phase assignments diff --git a/README.md b/README.md index 115c7a6..e7d307d 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,72 @@ # writeonce -A single self-contained binary that serves a content platform — no external database, no cloud pipeline, no JavaScript framework. Built in Rust on raw Linux kernel primitives. +A declarative full-stack programming language. You write `.wo` files; the runtime compiles them into a binary that owns the database, serves REST, and pushes live subscriptions — no external database, no external web server, no frontend framework. -## Why +Think **Go + Postgres + `net/http` + Phoenix LiveView, folded into one language and one binary.** -The original writeonce system spread across five repositories, four languages, AWS infrastructure (S3, Lambda, API Gateway), PostgreSQL, and an Angular frontend. All of that to serve articles from local files. This project collapses everything into one process that owns storage, serves content, and pushes real-time updates. +## Quickstart -## Architecture - -- **Single process** — one binary replaces S3 + Lambda + Rust API + PostgreSQL + Angular -- **Embedded storage** — custom `.seg` segment files with positional indexing, no external database -- **Real-time subscriptions** — route-based SSE streams push content diffs to connected clients -- **Server-rendered HTML** — `.htmlx` templates with data bindings, minimal client-side JS -- **Markdown-first content** — `.md` files are the source of truth, JSON holds only metadata -- **Linux kernel I/O** — `epoll`, `inotify`, `eventfd`, `timerfd`, `sendfile` — no tokio, no async runtime - -## Workspace Crates - -| Crate | Purpose | -|-------|---------| -| `wo-model` | Article and metadata types | -| `wo-seg` | Segment file reader/writer (.seg format) | -| `wo-index` | Title hash map, date sorted array, tags inverted index | -| `wo-store` | Query API over segments and indexes | -| `wo-watch` | `inotify`-based content directory watcher | -| `wo-event` | `epoll` event loop, `eventfd`, `timerfd`, `signalfd` | -| `wo-sub` | Subscription manager and diff delivery | -| `wo-rt` | Single-threaded runtime tying I/O sources together | -| `wo-http` | HTTP request parsing and response writing | -| `wo-route` | URL routing and handler dispatch | -| `wo-htmlx` | Template engine for `.htmlx` files | -| `wo-md` | Markdown to HTML rendering | -| `wo-serve` | Binary entry point — wires everything together | - -## Build - -```sh -cargo build --release +```bash +git clone https://github.com/shoneyJ/writeonce +cd writeonce +cargo run --bin wo -- run docs/examples/blog # serve the sample blog on :8080 +curl http://127.0.0.1:8080/api/articles # it's a real REST API now ``` -## Deploy +See [`reference/rest/blog.rest`](reference/rest/blog.rest) for a preconfigured HTTP-request file that drives the whole sample — open it in VS Code (with the REST Client extension) or JetBrains and click "Send Request" on each block. -The binary runs behind nginx with Let's Encrypt SSL. See `infra/setup.sh` for first-time server setup and `docs/07-ssl.md` for the full deployment walkthrough. +## What this repository contains -```sh -# Build, copy binary, sync content, restart service -./infra/deploy.sh +| Path | What it is | +| --- | --- | +| [`crates/rt/`](crates/rt/) | The new `.wo` language runtime — lexer, type-DSL parser, in-memory engine, axum REST server. Produces the `wo` binary. | +| [`crates/{ql,value,engine,txn,db,wal,sub,http,gen,policy,logic,service,ui,app}/`](crates/) | 14 empty placeholder crates scaffolded for Phases 2–6. Real code extracts from `rt/` as each phase activates. | +| [`docs/runtime/wo-language.md`](docs/runtime/wo-language.md) | **Start here.** The language overview: toolchain, hello-world, stdlib, client model. | +| [`docs/runtime/database.md`](docs/runtime/database.md) | The 7-phase engineering series that drives the runtime's design. | +| [`docs/examples/blog/`](docs/examples/blog/) | Sample `.wo` project: blog with articles, authors, tags, comments. ~200 lines. | +| [`docs/examples/ecommerce/`](docs/examples/ecommerce/) | Sample `.wo` project: storefront + live order-ops table + cross-paradigm checkout. ~300 lines. | +| [`prototypes/wo-db/`](prototypes/wo-db/) | C++ prototype of the query-layer engine (SQL + Cypher + document paths, `RETURNING` aliases, `LIVE` stub). ~2k lines, smoke tests pass. Reference implementation the Rust port follows. | +| [`reference/rest/`](reference/rest/) | `.rest` files (VS Code REST Client / JetBrains HTTP format) for manually testing the running prototype. | +| [`reference/crates/`](reference/crates/) | The v1 writeonce blog — 13 Rust crates implementing the original `.seg` + sidecar-index storage engine and `.htmlx` templating. Preserved as a nested workspace; see [`reference/README.md`](reference/README.md). | + +## Current stage + +The runtime is under active development. Each stage lands as an independently shippable cut: + +| Stage | What works | Status | +| --- | --- | --- | +| **1** | `wo run ` discovers every `.wo` file under a directory | ✅ shipped | +| **2** | Type-DSL parser, in-memory engine, REST CRUD (`list` / `get` / `create` / `update` / `delete`) generated from `service rest` blocks, JSON bodies with auto-id, default-value seeding, partial-update PATCH | ✅ shipped — `cargo run -- run docs/examples/blog` | +| **3** | LIVE subscriptions over WebSocket, delta frames on commit, `me` / session layer | pending | +| **4+** | Transactional fns (`fn checkout in txn snapshot`), row-level policies, type-attached triggers, `##ui` SSR, WAL durability, codegen | see [docs/runtime/database.md](docs/runtime/database.md) | + +`cargo test --lib` at the root runs 14 unit tests covering the lexer, parser, compiler, and engine. Stage-3 endpoints respond `501 Not Implemented` until they land. + +## Build & test + +```bash +cargo build # builds all 15 crates (only `rt` has real code) +cargo test --lib # 14 unit tests + +cargo run --bin wo -- run docs/examples/blog # serve the blog sample +cargo run --bin wo -- run docs/examples/ecommerce # serve the ecommerce sample + +# Override the listen address +WO_LISTEN=127.0.0.1:9000 cargo run --bin wo -- run docs/examples/blog ``` -## Documentation +## The v1 codebase (reference) -Design documents live in `docs/`: +The original writeonce blog engine — 13 crates, flat-file `.seg` storage, sidecar indexes, `.htmlx` templates, hand-rolled `epoll` event loop — moved to [`reference/crates/`](reference/crates/) when the new runtime was scaffolded. It's a nested Cargo workspace: -- `00-linux.md` — Linux kernel primitives used -- `01-problem.md` — Problem statement and motivation -- `02-recovery.md` — Target architecture -- `03-data.md` — Embedded storage and subscription model -- `04-ui.md` — Server-rendered HTMLX templates -- `05-datalayer.md` — Data layer implementation status -- `06-markdown-render.md` — Markdown-first content model -- `07-ssl.md` — SSL, nginx, and deployment -- `runtime/` — Deep dives on async runtimes, fibers, and Rust's ownership model -- `future-scope/` — Planned features including AI agent content management +```bash +cd reference/crates +cargo build # all 13 v1 crates still compile +cargo test # 12 unit tests, 1 ignored integration test +``` + +V1 crates keep the `wo-` prefix (`wo-seg`, `wo-store`, …). The new runtime crates dropped it (`ql`, `value`, `engine`, …). [`docs/runtime/database/07-wo-seg-migration.md`](docs/runtime/database/07-wo-seg-migration.md) is the phased coexistence plan for replacing v1 with the new runtime — abstract behind a trait, dual-write, cut over, decommission. + +## License & status + +Work in progress. Nothing here is stable. Read the language overview in [`docs/runtime/wo-language.md`](docs/runtime/wo-language.md) if you want to know the shape; read the phase docs if you want to see the engineering plan; look in [`docs/examples/`](docs/examples/) if you want to see what the end product feels like. diff --git a/crates/README.md b/crates/README.md new file mode 100644 index 0000000..652a362 --- /dev/null +++ b/crates/README.md @@ -0,0 +1,54 @@ +# `crates/` — the `.wo` runtime + +Fifteen crates make up the new runtime. Only `rt/` carries real code today (Stage 2); the other fourteen are **empty placeholders** scaffolded to match the 7-phase design so each phase's extraction work becomes a mechanical code move into an existing home. + +> The crate-name prefix `wo-` was dropped when the active project namespaced itself under `wo` (the binary, the file extension, the language). Internal imports read cleanly: `use ql::Parser`, `use db::Tx`, `use http::router`. The v1 codebase keeps its `wo-*` prefix in [`reference/crates/`](../reference/crates/) to distinguish the generations. + +## Map + +| Phase | Crate | Purpose | Status | +| --- | --- | --- | --- | +| 2 | [`ql`](./ql/) | `.wo` grammar — lexer, parser, AST | placeholder | +| 2 | [`value`](./value/) | tagged `Value` + dotted-path helpers | placeholder | +| 2 | [`engine`](./engine/) | in-memory executor (rel / doc / graph) + schema catalog | placeholder | +| 2 | [`txn`](./txn/) | transaction coordinator — MVCC, `RETURNING` alias table | placeholder | +| 2 | [`db`](./db/) | top-level facade — `open()`, `Tx`, `Query`, `Subscribe` | placeholder | +| 3 | [`wal`](./wal/) | write-ahead log — io_uring + fsync + recovery | placeholder | +| 4 | [`sub`](./sub/) | live subscriptions — delta frames on commit | placeholder | +| 4 | [`http`](./http/) | wire protocol — REST / GraphQL-over-WS / native codec | placeholder | +| 5 | [`gen`](./gen/) | codegen — `.wo type` → Go / TS / Rust / Python clients | placeholder | +| 6 | [`policy`](./policy/) | RBAC + row-level rules compiled into planner rewrites | placeholder | +| 6 | [`logic`](./logic/) | `on ` triggers + `fn ... in txn` interpreter | placeholder | +| 6 | [`service`](./service/) | `service rest/graphql/native` endpoint dispatch | placeholder | +| 6 | [`ui`](./ui/) | `##ui` screens → SSR HTML + client runtime | placeholder | +| 6 | [`app`](./app/) | `##app` route manifest + startup hooks | placeholder | +| — | [`rt`](./rt/) | **active** — Stage-2 monolith + the `wo` binary | **shipped** | + +## Why `rt/` is monolithic right now + +`rt/` currently holds every module the runtime needs — lexer, parser, AST, in-memory engine, axum REST server — because **shipping working Stage 2 was more important than hitting the final crate layout on day one**. Each module inside `rt/src/` is written with a target home in mind: + +| `rt` module | Moves to | Phase | +| --- | --- | --- | +| `token.rs` + `lexer.rs` + `ast.rs` + `parser.rs` | `ql/` | 2 | +| `engine.rs` (Value + Row helpers) | `value/` | 2 | +| `engine.rs` (Engine + Catalog) | `engine/` | 2 | +| `compile.rs` | `engine/` | 2 | +| `server.rs` | `http/` + `service/` | 4 / 6 | +| `bin/wo.rs` | stays in `rt/` (the binary) | — | + +Extractions happen phase-by-phase — first one lands when a second caller appears (likely when Stage 3 needs the parser for raw-`.wo` HTTP requests). + +## Build & test + +```bash +cargo build # compiles all 15 crates +cargo test --lib # 14 unit tests (all in rt today) +cargo run --bin wo -- run docs/examples/blog # serve the blog sample +``` + +## What's outside this directory + +- [`../reference/crates/`](../reference/crates/) — the v1 writeonce blog (13 crates, nested workspace). Preserved for reference per [docs/runtime/database/07-wo-seg-migration.md](../docs/runtime/database/07-wo-seg-migration.md). Keeps its `wo-*` prefix. +- [`../prototypes/wo-db/`](../prototypes/wo-db/) — C++ prototype of the query-layer engine (~2k lines). The reference implementation this Rust port follows at the language level. +- [`../docs/plan/`](../docs/plan/) — planning documents for in-flight work (the `.md` files directly under `plan/` are upcoming phases; `plan/done/` holds completed ones). [`plan/done/01-scafolding-crates.md`](../docs/plan/done/01-scafolding-crates.md) is the authoritative scope doc for the 14 new placeholders. diff --git a/docs/examples/blog/README.md b/docs/examples/blog/README.md new file mode 100644 index 0000000..b558212 --- /dev/null +++ b/docs/examples/blog/README.md @@ -0,0 +1,175 @@ +# `blog` — a sample writeonce app + +A complete blogging website in **~200 lines of `.wo`** that creates a database, exposes REST + live-subscription endpoints, renders HTML pages, enforces row-level policies, and emits typed client SDKs. + +> This project is a **docs artifact** — it illustrates the shape of a real `wo init`'d project. The `wo` toolchain referenced here is the one specified in [`../../runtime/wo-language.md`](../../runtime/wo-language.md); the engine is at prototype stage in [`../../../prototypes/wo-db/`](../../../prototypes/wo-db/). + +## What it does + +| Thing | How | +| --- | --- | +| Persists articles, authors, tags, comments | `type` declarations compiled to relational rows + embedded documents + graph edges | +| Serves 24 REST endpoints (CRUD + subscribe × 4 types) | `service rest` blocks on each type | +| Serves 4 web pages (list, detail, tag, admin) | `##ui` screens + route table in `app.wo` | +| Pushes live updates on every commit | `live: true` on screens + `LIVE` queries under the hood | +| Enforces "drafts hidden from anonymous readers" | `policy read anyone when published == true` | +| Bumps `published_at` automatically | `on update` trigger inside the transaction | +| Generates a typed Go client | `wo gen sdk --lang go` | + +## Project layout + +``` +blog/ +├── wo.toml # project manifest (like go.mod) +├── app.wo # routes, theme, startup hooks +├── types/ +│ ├── author.wo # Author type + per-type service/policy +│ ├── article.wo # Article — all three paradigms in one type +│ ├── tag.wo # Tag taxonomy +│ └── comment.wo # Reader comments +├── ui/ +│ ├── article_list.wo # home page list view (live) +│ └── article_detail.wo # per-article page with comments + related +└── tests/ + └── article_test.wo # `wo test` picks this up +``` + +No `main.wo` is needed — a pure type+service app auto-generates its entry point. Add `main.wo` if you need CLI args, background workers, or custom startup logic beyond the `on startup` hook in `app.wo`. + +## Run it + +```bash +$ cd docs/examples/blog +$ wo run +[wo] parsing: 7 files, 4 types, 2 ui screens +[wo] compiling schema: 4 sql tables, 1 doc collection, 3 graph edge types +[wo] starting runtime (engine: in-memory, data_dir: ./data) +[wo] on startup: seed_admin() — inserted admin@example.com +[wo] HTTP listening on :8080 + + GET /api/articles list + GET /api/articles/:id get + POST /api/articles create + PATCH /api/articles/:id update + DELETE /api/articles/:id delete + WS /api/articles/live subscribe + GET /api/authors list + GET /api/authors/me me + WS /api/authors/live subscribe + GET /api/tags list + GET /api/comments list + POST /api/comments create + WS /api/comments/live subscribe + ... (and the rest) + + GET / ui.article-list + GET /article/:slug ui.article-detail + GET /tag/:slug ui.article-list (filtered) + GET /admin ui.article-list (role: Admin) +``` + +## Exercise the REST API + +```bash +# Create an author (requires admin session — see auth docs; stub'd here for brevity) +$ curl -X POST localhost:8080/api/authors \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -d '{"email":"alice@example.com","handle":"alice","display":"Alice","role":"Author"}' +{"id":2,"email":"alice@example.com","handle":"alice",...} + +# Create an article as that author +$ curl -X POST localhost:8080/api/articles \ + -H "Authorization: Bearer $ALICE_TOKEN" \ + -d '{ + "slug": "hello", + "title": "Hello, writeonce", + "author": 2, + "meta": {"excerpt":"First post","body_md":"# Hi\n\nHello."}, + "published": true + }' +{"id":1,"slug":"hello","title":"Hello, writeonce","published_at":"2026-04-17T12:00:00Z",...} + +# List published articles (public — no token) +$ curl localhost:8080/api/articles +[{"id":1,"slug":"hello","title":"Hello, writeonce",...}] + +# Filter by tag (via the query layer) +$ curl 'localhost:8080/api/articles?tags.slug=rust' +[...] +``` + +## Subscribe to live updates + +```bash +$ websocat ws://localhost:8080/api/articles/live?published=eq.true +{"kind":"snapshot","rows":[{"id":1,"slug":"hello",...}]} + +# Now in another terminal, update article 1. The open socket receives: +{"kind":"update","id":1,"old":{"title":"Hello, writeonce"},"new":{"title":"Hello!"}} +``` + +No polling. The subscription predicate was registered at connect time; the engine's commit path emits the delta directly. + +## Generate a Go client + +```bash +$ wo gen sdk --lang go --out ./client +[wo] reading types from ./types/ +[wo] writing ./client/sdk.go (4 types, 16 endpoints, 4 subscriptions) +``` + +Use it: + +```go +import "github.com/you/blog/client" + +c, _ := client.Connect(ctx, "wo://localhost:8080", client.WithToken(token)) + +// Typed query +articles, _ := c.Articles.List(ctx, client.Where{Published: ptr(true)}) + +// Typed subscription — deltas arrive on a channel +sub, _ := c.Articles.Subscribe(ctx, client.Where{Published: ptr(true)}) +for d := range sub.C { + switch d.Kind { + case client.Insert: + fmt.Printf("new article: %s\n", d.Row.Title) + case client.Update: + fmt.Printf("updated: %s\n", d.Row.Slug) + } +} +``` + +## Run the tests + +```bash +$ wo test +=== tests/article_test.wo === + create and fetch by slug OK (3ms) + policy blocks public read of unpublished drafts OK (4ms) + graph traversal: related articles OK (7ms) + live subscription receives delta on commit OK (12ms) + +PASS 4/4 tests, 0 failures (26ms) +``` + +Each `test` block runs against an isolated engine snapshot that's rolled back at the end — no setup/teardown code needed. + +## Build a production binary + +```bash +$ wo build --target linux-amd64 --out bin/blog +[wo] static binary: bin/blog (14 MB, database + HTTP + subscription engine embedded) +$ ./bin/blog +[wo] HTTP listening on :8080 +``` + +One binary, no dependencies. Copy it to a server, run it, done. The database file lives in `./data/` relative to the binary; the WAL ensures crash safety ([Phase 3](../../runtime/database/03-inmemory-engine.md)). + +## What to read next + +- [`../../runtime/wo-language.md`](../../runtime/wo-language.md) — the user-facing language overview this project builds on +- [`../../runtime/database/02-wo-language.md`](../../runtime/database/02-wo-language.md) — the two-layer language spec (schema + query layers) +- [`../../runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md) — the `##ui`/`##policy`/`##service`/`##app` block spec +- [`../../../prototypes/wo-db/`](../../../prototypes/wo-db/) — the C++ prototype that runs the query-layer subset today diff --git a/docs/examples/ecommerce/README.md b/docs/examples/ecommerce/README.md new file mode 100644 index 0000000..f2a6932 --- /dev/null +++ b/docs/examples/ecommerce/README.md @@ -0,0 +1,251 @@ +# `ecommerce` — a sample writeonce e-commerce app + +A storefront + checkout + live ops dashboard in **~300 lines of `.wo`**. Exercises the features that make `.wo` distinct from a plain REST app: **cross-paradigm ACID transactions**, **type-attached lifecycle triggers**, **link types with properties**, and a **live-updating operations table**. + +> Like the [blog sample](../blog/), this is a **docs artifact** — illustrative `.wo` source showing the shape of a real `wo init`'d project. Toolchain specified in [`../../runtime/wo-language.md`](../../runtime/wo-language.md). + +## What's here + +| File | What it shows | +| --- | --- | +| [`types/product.wo`](./types/product.wo) | Relational scalars + embedded doc (`meta`, `inventory`) + computed field (`available`) + graph edge (`similar_to`) + inventory-low trigger | +| [`types/order.wo`](./types/order.wo) | Tagged union status, array-of-struct `line_items`, computed `total`, four lifecycle triggers setting timestamp columns atomically | +| [`types/customer.wo`](./types/customer.wo) | Role union + `multi Product via Purchase` (link with properties) + `backlink Order.customer` | +| [`types/purchase.wo`](./types/purchase.wo) | `link Customer -> Product` — a graph edge **type** with its own columns (`order`, `qty`, `unit_price`) | +| [`logic/checkout.wo`](./logic/checkout.wo) | The canonical cross-paradigm transaction: reserve inventory + insert order + create graph edge, atomic across all three engines | +| [`ui/admin_orders.wo`](./ui/admin_orders.wo) | **The live order-ops table** — role-gated, auto-subscribes, delta-in-place updates | +| [`ui/storefront.wo`](./ui/storefront.wo) | Customer-facing product list with live inventory | +| [`ui/order_tracker.wo`](./ui/order_tracker.wo) | Customer-facing order history, same live engine, policy-filtered source | +| [`app.wo`](./app.wo) | Route table, Admin/Ops bypass policy, idempotent `seed()` | +| [`tests/checkout_test.wo`](./tests/checkout_test.wo) | Three tests covering the atomic checkout, the abort-without-partial-state guarantee, and the live-subscription delta stream | + +## Project layout + +``` +ecommerce/ +├── wo.toml +├── app.wo +├── types/ +│ ├── customer.wo +│ ├── product.wo +│ ├── order.wo +│ └── purchase.wo # link type — graph edge with properties +├── logic/ +│ └── checkout.wo # transactional functions (fn … in txn snapshot) +├── ui/ +│ ├── storefront.wo +│ ├── order_tracker.wo +│ └── admin_orders.wo # the live ops table +└── tests/ + └── checkout_test.wo +``` + +## Run it + +```bash +$ cd docs/examples/ecommerce +$ wo run +[wo] parsing: 10 files, 4 types + 1 link type, 3 ui screens, 4 fns +[wo] compiling schema: 3 sql tables, 2 doc collections, 2 graph edge types +[wo] starting runtime (engine: in-memory, data_dir: ./data, isolation: snapshot) +[wo] on startup: seed() — 1 customer, 2 products +[wo] HTTP listening on :8080 + + GET /api/products list + GET /api/products/:id get + WS /api/products/live subscribe + GET /api/orders list + GET /api/orders/:id get + WS /api/orders/live subscribe + GET /api/customers/:id get + GET /api/customers/me me + PATCH /api/customers/:id update + WS /api/customers/live subscribe + POST /api/fn/checkout fn checkout(customer, product, qty) -> Order + POST /api/fn/mark_paid fn mark_paid(order) + POST /api/fn/mark_shipped fn mark_shipped(order) + + GET / ui.storefront + GET /product/:sku ui.product-detail + GET /orders ui.order-tracker + GET /admin/orders ui.admin-orders (Admin | Ops) +``` + +> **Runtime model.** The engine is a single-threaded event loop today ([Phase 2 concurrency](../../runtime/database/02-wo-language.md#concurrency-model)). Snapshot isolation is trivially correct because there are no concurrent writers — the checkout, mark_paid, and mark_shipped fns run sequentially even when fired in quick succession. The throughput ceiling is ~one core (plenty for the sample); sharding across independent engine processes is the horizontal-scale path. + +## Exercise the cross-paradigm checkout + +The `fn checkout(...)` in [`logic/checkout.wo`](./logic/checkout.wo) is the canonical Phase 2 test case: one transaction that mutates relational, document, and graph state atomically. + +```bash +# Place an order — one HTTP call runs the whole BEGIN ... COMMIT block +$ curl -X POST localhost:8080/api/fn/checkout \ + -H "Authorization: Bearer $CUSTOMER_TOKEN" \ + -d '{"customer":1, "product":2, "qty":3}' + +{"id":1, "status":"Pending", "total":5997, "line_items":[{...}], "placed_at":"..."} + +# Verify inventory was reserved (not yet decremented) +$ curl localhost:8080/api/products/2 +{"sku":"SKU-GIZMO", "inventory":{"on_hand":12, "reserved":3, "reorder_at":3}, "available":9, ...} + +# Verify the graph edge was created in the same transaction +$ curl localhost:8080/api/customers/1/purchased +[{"target":{"sku":"SKU-GIZMO"}, "order":1, "qty":3, "unit_price":1999, "at":"..."}] +``` + +If the inventory check failed inside `checkout`, **none** of the above writes happen — the order isn't created, the reservation isn't made, and the graph edge doesn't exist. That atomicity is the whole point of building your own engine instead of stitching Postgres + Neo4j. + +## Watch the live admin ops table + +Open the admin orders UI in a browser: + +```bash +$ open http://localhost:8080/admin/orders # authenticated as Admin or Ops +``` + +The page renders a table with the columns declared in [`ui/admin_orders.wo`](./ui/admin_orders.wo). Behind the scenes, the client runtime has opened one WebSocket to the engine's subscription endpoint: + +``` +WS /api/orders/live ? status!=Cancelled +``` + +Now, from another terminal, fire a sequence of state changes: + +```bash +# 1. New customer places an order — admin table gains a row, highlighted for 2s +$ curl -X POST localhost:8080/api/fn/checkout -d '{"customer":2,"product":1,"qty":1}' + +# 2. Payment webhook flips status Pending → Paid — row updates in place, paid_at fills in +$ curl -X POST localhost:8080/api/fn/mark_paid -d '{"order":2}' + +# 3. Ops ships the order — status → Shipped, shipped_at fills in +$ curl -X POST localhost:8080/api/fn/mark_shipped -d '{"order":2}' +``` + +The browser table re-renders each row delta as it arrives, without a full list refetch. `status` cell swaps its pill colour; timestamp cells populate. No polling anywhere in the path — the deltas are emitted by the transaction coordinator on commit, routed through the subscription registry, and pushed down the socket ([Phase 4](../../runtime/database/04-client-api.md)). + +A filtered subscription — the admin clicking the **"Ready to ship"** quick-filter — doesn't rebuild state client-side. It sends the new predicate to the server, which replies with a `SNAPSHOT` frame of just the matching rows, then streams deltas that match the new predicate. Also zero-polling. + +## Generate a typed Go client + +```bash +$ wo gen sdk --lang go --out ./client +[wo] reading types from ./types/ and fns from ./logic/ +[wo] writing ./client/sdk.go (4 types, 13 endpoints, 4 fns, 3 subscriptions) +``` + +The generated client speaks the native wire protocol: + +```go +import "myshop/client" + +c, _ := client.Connect(ctx, "wo://localhost:8080", client.WithToken(token)) + +// Typed transactional function call +order, err := c.Checkout(ctx, client.CheckoutArgs{ + Customer: 1, + Product: 2, + Qty: 3, +}) + +// Typed live subscription — same wire as the admin UI uses +sub, _ := c.Orders.Subscribe(ctx, client.Where{Status: client.Ne(client.Cancelled)}) +for d := range sub.C { + switch d.Kind { + case client.Insert: + fmt.Printf("new order #%d from %s — $%.2f\n", d.Row.ID, d.Row.Customer.Name, float64(d.Row.Total)/100) + case client.Update: + fmt.Printf("order #%d → %s\n", d.Row.ID, d.Row.Status) + } +} +``` + +## Checkout from Go without codegen + +For ad-hoc scripts, admin tools, or client paths not on the app's hot loop, send raw `.wo` DML with `client.Wo(...)`. The server parses the block exactly like `wo run` would — same parser, same transaction coordinator, same `RETURNING` alias table — so the **cross-paradigm checkout runs in one round trip**: + +```go +import "go.writeonce.dev/wo" + +c, _ := wo.Connect(ctx, "wo://localhost:8080", wo.WithToken(token)) + +// Same logic as fn checkout(), but authored at the Go call site. +// BEGIN SNAPSHOT ... COMMIT runs server-side; RETURNING aliases ($pid, $oid) +// thread from the SQL UPDATE/INSERT into the Cypher CREATE within the txn. +result, err := c.Wo(ctx, ` + BEGIN SNAPSHOT; + + UPDATE products + SET inventory.reserved = inventory.reserved + $qty + WHERE id = $pid AND available >= $qty + RETURNING id AS pid; + + INSERT INTO orders (customer, status, line_items) + VALUES ($uid, 'Pending', [{product: $pid, qty: $qty, unit_price: $unit}]) + RETURNING id AS oid; + + MATCH (u:Customer {id: $uid}), (p:Product {id: $pid}) + CREATE (u)-[:PURCHASED {order: $oid, qty: $qty, unit_price: $unit}]->(p); + + COMMIT; +`, wo.Params{"uid": 1, "pid": 2, "qty": 3, "unit": 4999}) + +if err != nil { log.Fatal(err) } +orderID := result.Aliases["oid"].(int64) +fmt.Printf("created order #%d\n", orderID) +``` + +**When to reach for this form** — see the [raw-vs-typed guidance in the Go SDK doc](../../runtime/database/05-go-sdk.md#when-to-use-raw-wo-vs-typed-codegen). Rule of thumb: typed `c.Checkout(...)` for the app's storefront; raw `c.Wo(...)` for an ops console that runs a custom report, or when you want to paste a block from [`logic/checkout.wo`](./logic/checkout.wo) straight into Go. + +## Run the tests + +```bash +$ wo test +=== tests/checkout_test.wo === + checkout atomically reserves inventory, creates order, and creates graph edge OK (8ms) + checkout aborts without partial state when inventory is insufficient OK (4ms) + admin live-orders subscription receives deltas across the order lifecycle OK (14ms) + +PASS 3/3 tests, 0 failures (26ms) +``` + +The third test is the important one for the docs: it proves that the same engine that serves `/admin/orders` in the browser delivers deltas in commit order through a programmatic `subscribe live` handle. One engine, one delta stream, two consumers (the browser and the test). + +## Build a production binary + +```bash +$ wo build --target linux-amd64 --out bin/shop +[wo] static binary: bin/shop (15 MB — database + HTTP + subscription engine embedded) +$ ./bin/shop +[wo] HTTP listening on :8080 +``` + +Drop the binary on a server, give it a writable directory for `./data/` (WAL + engine state), run it behind nginx or let it terminate TLS itself. The admin ops table works on the first page-load — no Redis, no Kafka, no separate DB process, no ORM-and-migration dance. + +## Compare to the blog sample + +Both projects use the same language and runtime. They showcase different slices: + +| Feature | [blog](../blog/) | ecommerce (this project) | +| --- | --- | --- | +| Embedded document | `article.meta` | `product.meta`, `product.inventory` | +| Graph edges (zero-prop) | tags, related | similar_to | +| Graph edges **with** properties | — | `type Purchase link Customer -> Product` | +| Tagged union | — | `Pending \| Paid \| Shipped \| ...` | +| Computed field | `word_count` | `available`, `total` (sum over line_items) | +| Array-of-struct column | — | `line_items: [{product, qty, unit_price}]` | +| Stored procedure (`fn ... in txn`) | seed only | full checkout + fulfillment | +| Cross-paradigm transaction | — | `checkout` (relational + doc + graph atomic) | +| Live subscription | list view | live **ops** table with in-place delta updates | +| Row-level policy | draft hiding | customer sees own orders; ops/admin sees all | + +If the blog shows **what a CRUD app looks like in `.wo`**, the ecommerce sample shows **what a transactional business app looks like in `.wo`** — and why building the engine as part of the language is the differentiator. + +## What to read next + +- [`../../runtime/wo-language.md`](../../runtime/wo-language.md) — language overview and toolchain +- [`../../runtime/database/02-wo-language.md`](../../runtime/database/02-wo-language.md) — the schema/query two-layer spec +- [`../../runtime/database/04-client-api.md`](../../runtime/database/04-client-api.md) — the wire protocol and subscription engine behind the live ops table +- [`../../runtime/database/06-lowcode-fullstack.md`](../../runtime/database/06-lowcode-fullstack.md) — `##ui` / `##app` block spec +- [`../../../prototypes/wo-db/tests/checkout.wo`](../../../prototypes/wo-db/tests/checkout.wo) — the C++ prototype's smoke test that exercises the same cross-paradigm transaction at the query layer diff --git a/docs/future-scope/ai-agents-content-management.md b/docs/future-scope/ai-agents-content-management.md index e0f4369..f4cad9f 100644 --- a/docs/future-scope/ai-agents-content-management.md +++ b/docs/future-scope/ai-agents-content-management.md @@ -46,13 +46,13 @@ Per [06-markdown-render.md](./06-markdown-render.md), the JSON metadata is minim ### Mapping Types -| Type | Meaning | Agent Use | -|------|---------|-----------| -| `related` | Topically related articles | Agent loads these for cross-reference when editing | -| `prerequisite` | Articles the reader should read first | Agent ensures no concept duplication, references prerequisites instead of re-explaining | -| `series` | Articles that form an ordered sequence | Agent maintains narrative continuity across the series | -| `supersedes` | This article replaces an older one | Agent can mark the old article as outdated or unpublished | -| `references` | External articles or URLs the content builds on | Agent checks links are still valid, cites them properly | +| Type | Meaning | Agent Use | +| -------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------- | +| `related` | Topically related articles | Agent loads these for cross-reference when editing | +| `prerequisite` | Articles the reader should read first | Agent ensures no concept duplication, references prerequisites instead of re-explaining | +| `series` | Articles that form an ordered sequence | Agent maintains narrative continuity across the series | +| `supersedes` | This article replaces an older one | Agent can mark the old article as outdated or unpublished | +| `references` | External articles or URLs the content builds on | Agent checks links are still valid, cites them properly | ### Directory Structure with Mappings @@ -76,6 +76,7 @@ content/ The author asks an agent: "Write an article about deploying GitLab Runner on ECS." The agent: + 1. Scans the content directory for existing articles with tags `gitlab`, `ci-cd`, `aws` 2. Finds `gitlab-runner-with-kubernetes-executor` and `auto-scale-gitlab-runner-using-aws-spot-instance` 3. Reads their `.md` files to understand what's already covered @@ -87,6 +88,7 @@ The agent: The author asks: "Update the Kubernetes executor article with the new runner token format." The agent: + 1. Reads the article's JSON metadata and `.md` content 2. Reads the `mappings.related` articles to check for consistency 3. Makes the update in the `.md` file @@ -98,6 +100,7 @@ The agent: The author asks: "Which articles reference outdated AWS configurations?" The agent: + 1. Loads all article metadata (the Store already indexes everything) 2. Follows `mappings` to build a dependency graph 3. Reads the `.md` files of articles tagged with `aws` @@ -109,6 +112,7 @@ The agent: The author asks: "Add a new part to the gitlab-runner series." The agent: + 1. Finds all articles with `mappings.series.name == "gitlab-runner"` 2. Reads them in order to understand the narrative arc 3. Writes the new article continuing from where the series left off @@ -140,9 +144,7 @@ The `article.htmlx` template can render related articles: ```html

{{article.title}}

- {{article.content_html}} - - {{#each article.related}} + {{article.content_html}} {{#each article.related}}