docs: pending iterations renumbered by dependency + priority
- developer directive: pending iteration IDs now ARE the priority order; LANDED iterations keep historical numbers (code comments and commit history cite them — records, not a queue); 8/11 (the half-landed arc), 17 (parked, artifacts on a branch), 18 (next, artifacts named) also frozen - mapping (recorded in 00-story): 19<-20 Float+Bytes, 20<-9c attach, 21<-9d keypair, 22<-9e benchmarks, 23<-9f io_uring WAL, 24<-19 chat, 25<-10 services, 26<-12 blue-green, 27<-9g query corpus, 28<-14 skillhost, 29<-13 metaprogramming - 11 story files renamed; every doc reference re-numbered (word-boundary sweep for the lettered 9x ids, phrase-level for numeric ones); the iterations table rewritten with Seq == priority and "(was N)" notes; story-scoped link check: zero broken - merge-recovery folded in: the partial master merge had dropped the chat story, the fibers exploration note, the arc spec+plan, the framework-v2 plan, and the iteration-17 spec+plan — all restored from their branches and renumbered consistently Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4c8a3bd2ed
commit
5521d21a84
31 changed files with 1626 additions and 147 deletions
|
|
@ -12,4 +12,4 @@ Native speed — the big one. Everything is interpreted: ~40× behind Go on raw
|
||||||
- Language expressiveness. No generics — the cache stores Text and tells you to json.encode; no function values or closures (doctrine, but it's why every handler is a class with one method); byte-based strings with no Unicode awareness; no Result-style error values (traps + try only); pattern matching is a switch, not destructuring. Some of this is deliberate rejection, but "deliberate" doesn't make the expressiveness appear.
|
- Language expressiveness. No generics — the cache stores Text and tells you to json.encode; no function values or closures (doctrine, but it's why every handler is a class with one method); byte-based strings with no Unicode awareness; no Result-style error values (traps + try only); pattern matching is a switch, not destructuring. Some of this is deliberate rejection, but "deliberate" doesn't make the expressiveness appear.
|
||||||
- Concurrency holes the arc hasn't closed. send is one-way — no reply/request-response primitive (my own benchmarks couldn't await the actor and had to sleep); no supervision, links, or actor death (actors live until process end); unbounded mailboxes with zero backpressure; no timers beyond sleep; round-robin placement with no work stealing; multi-shard DB access still traps (stage 3 unbuilt); accept lives on one shard.
|
- Concurrency holes the arc hasn't closed. send is one-way — no reply/request-response primitive (my own benchmarks couldn't await the actor and had to sleep); no supervision, links, or actor death (actors live until process end); unbounded mailboxes with zero backpressure; no timers beyond sleep; round-robin placement with no work stealing; multi-shard DB access still traps (stage 3 unbuilt); accept lives on one shard.
|
||||||
- Production plumbing. No TLS anywhere (proxy-mandated forever), no HTTP/2 or WebSockets yet, no crypto primitives (blocked on the bit-ops-vs-builtins fork), observability is print/stderr — no metrics, tracing, or profiler; no debugger, no LSP (discussed, never built); deps are git-rev-only with no registry, no transitive resolution, no semver; blue-green deploy and schema migrations are recorded futures, not features.
|
- Production plumbing. No TLS anywhere (proxy-mandated forever), no HTTP/2 or WebSockets yet, no crypto primitives (blocked on the bit-ops-vs-builtins fork), observability is print/stderr — no metrics, tracing, or profiler; no debugger, no LSP (discussed, never built); deps are git-rev-only with no registry, no transitive resolution, no semver; blue-green deploy and schema migrations are recorded futures, not features.
|
||||||
- Proof maturity. 9e's benchmark battery has never run — every number so far is a scratch measurement on one machine; TSan covers one demo; no fuzzing, no CI beyond local just, and the whole ecosystem is one framework, five samples, and one committed consumer. The honest summary: the architecture is ahead of the product — the doctrine bets (ownership+inference, actors, one binary, io_uring) are landing and measurable, while the surface a developer touches daily (types, tooling, ecosystem) is years behind the languages it benchmarks against.
|
- Proof maturity. 22's benchmark battery has never run — every number so far is a scratch measurement on one machine; TSan covers one demo; no fuzzing, no CI beyond local just, and the whole ecosystem is one framework, five samples, and one committed consumer. The honest summary: the architecture is ahead of the product — the doctrine bets (ownership+inference, actors, one binary, io_uring) are landing and measurable, while the surface a developer touches daily (types, tooling, ecosystem) is years behind the languages it benchmarks against.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
> green is startable today. Rebuilt 2026-08-20 from a sweep of every
|
> green is startable today. Rebuilt 2026-08-20 from a sweep of every
|
||||||
> story/spec/plan markdown (the "misses" pass: iteration 17's outgoing
|
> story/spec/plan markdown (the "misses" pass: iteration 17's outgoing
|
||||||
> edges, the concurrency chain, the post-12 parked drain, 9b→10,
|
> edges, the concurrency chain, the post-12 parked drain, 9b→10,
|
||||||
> 14's gap fan-out, 9c's fiber caveat).
|
> 14's gap fan-out, 20's fiber caveat).
|
||||||
|
|
||||||
## 1. Story iterations
|
## 1. Story iterations
|
||||||
|
|
||||||
|
|
@ -30,17 +30,17 @@ flowchart TD
|
||||||
FWREORG["framework internal/ reorg + check mode (kills the --emit workaround; WO-E108/E109 reserved)"]:::parked
|
FWREORG["framework internal/ reorg + check mode (kills the --emit workaround; WO-E108/E109 reserved)"]:::parked
|
||||||
I18["18 framework v2: transaction{} + cache/flags/jobs (spec APPROVED — the next implementation)"]:::specd
|
I18["18 framework v2: transaction{} + cache/flags/jobs (spec APPROVED — the next implementation)"]:::specd
|
||||||
|
|
||||||
I9c["9c cross-program tables (half-built)"]:::open
|
I9c["20 cross-program tables (half-built)"]:::open
|
||||||
I9d["9d keypair attach auth (half-built; crypto+handshake already on its branch)"]:::open
|
I9d["21 keypair attach auth (half-built; crypto+handshake already on its branch)"]:::open
|
||||||
I9e["9e durability + throughput baseline"]:::open
|
I9e["22 durability + throughput baseline"]:::open
|
||||||
I8["8 shard-actor runtime"]:::open
|
I8["8 shard-actor runtime"]:::open
|
||||||
I9f["9f io_uring group-commit"]:::open
|
I9f["23 io_uring group-commit"]:::open
|
||||||
I10["10 HTTP service layer (lowers onto the framework)"]:::open
|
I10["10 HTTP service layer (lowers onto the framework)"]:::open
|
||||||
I11["11 fibers"]:::open
|
I11["11 fibers"]:::open
|
||||||
I12["12 blue-green deploy"]:::open
|
I12["12 blue-green deploy"]:::open
|
||||||
I13["13 metaprogramming @derive"]:::open
|
I13["13 metaprogramming @derive"]:::open
|
||||||
I14["14 skillhost workload (demoted)"]:::open
|
I14["14 skillhost workload (demoted)"]:::open
|
||||||
I9g["9g query grammar corpus (likely collapses)"]:::open
|
I9g["27 query grammar corpus (likely collapses)"]:::open
|
||||||
GAPS["14's gap fan-out: bounded subprocess, stdin/stdout transport, fs metadata, FFI-vs-out-of-process"]:::open
|
GAPS["14's gap fan-out: bounded subprocess, stdin/stdout transport, fs metadata, FFI-vs-out-of-process"]:::open
|
||||||
DRAIN["post-12 parked drain: pub(read)/using/#if, WO-E225, ADT roster, group-by"]:::parked
|
DRAIN["post-12 parked drain: pub(read)/using/#if, WO-E225, ADT roster, group-by"]:::parked
|
||||||
|
|
||||||
|
|
@ -75,14 +75,14 @@ flowchart TD
|
||||||
```
|
```
|
||||||
|
|
||||||
Reading it: **18 is the only spec-approved open node with all
|
Reading it: **18 is the only spec-approved open node with all
|
||||||
prerequisites green — the next implementation.** After 18: 9c/9d and 9e
|
prerequisites green — the next implementation.** After 18: 20/21 and 22
|
||||||
are startable (chosen order: 9c/9d first — half-built branches rot).
|
are startable (chosen order: 20/21 first — half-built branches rot).
|
||||||
17 unparks on directive: its prerequisites landed, its spec+plan wait on
|
17 unparks on directive: its prerequisites landed, its spec+plan wait on
|
||||||
branch `library-internal`, and its landing brings the framework reorg
|
branch `library-internal`, and its landing brings the framework reorg
|
||||||
node with it. 13 and the parked drain sit behind 12 by the 2026-08-08
|
node with it. 13 and the parked drain sit behind 12 by the 2026-08-08
|
||||||
scope directive (dashed), not by any technical edge.
|
scope directive (dashed), not by any technical edge.
|
||||||
|
|
||||||
## 2. The concurrency chain (iterations 8 / 9f / 11 and everything they gate)
|
## 2. The concurrency chain (iterations 8 / 23 / 11 and everything they gate)
|
||||||
|
|
||||||
The runtime's concurrency work is the single biggest unlocker — every
|
The runtime's concurrency work is the single biggest unlocker — every
|
||||||
⏸ row in the framework ledger and two v2 follow-ons hang off it.
|
⏸ row in the framework ledger and two v2 follow-ons hang off it.
|
||||||
|
|
@ -95,17 +95,17 @@ flowchart TD
|
||||||
|
|
||||||
I7b2["7b per-shard collector (done — the precondition 8 waited on)"]:::rt
|
I7b2["7b per-shard collector (done — the precondition 8 waited on)"]:::rt
|
||||||
I8x["8 shard-actor runtime: thread-per-core, ownership-move messages"]:::rt
|
I8x["8 shard-actor runtime: thread-per-core, ownership-move messages"]:::rt
|
||||||
I9fx["9f io_uring group-commit (batch = the shard tick)"]:::rt
|
I9fx["23 io_uring group-commit (batch = the shard tick)"]:::rt
|
||||||
I11x["11 fibers: reduction-budget preemption, blocking builtins park"]:::rt
|
I11x["11 fibers: reduction-budget preemption, blocking builtins park"]:::rt
|
||||||
I9ex["9e baseline (numbers 8/9f sign against)"]:::rt
|
I9ex["22 baseline (numbers 8/23 sign against)"]:::rt
|
||||||
|
|
||||||
KEEPAL["keep-alive parking retired (close-when-idle policy dies; parked fds)"]:::gated
|
KEEPAL["keep-alive parking retired (close-when-idle policy dies; parked fds)"]:::gated
|
||||||
H2C2["h2c HTTP/2 cleartext (spec §C: also needs 9f)"]:::gated
|
H2C2["h2c HTTP/2 cleartext (spec §C: also needs 23)"]:::gated
|
||||||
STREAM2["request body streaming + backpressure"]:::gated
|
STREAM2["request body streaming + backpressure"]:::gated
|
||||||
SRESP2["streaming responses + explicit commit point"]:::gated
|
SRESP2["streaming responses + explicit commit point"]:::gated
|
||||||
CANCEL2["per-request cancellation propagation"]:::gated
|
CANCEL2["per-request cancellation propagation"]:::gated
|
||||||
PUBSUB2["pub/sub + WebSockets (rejected until here)"]:::gated
|
PUBSUB2["pub/sub + WebSockets (rejected until here)"]:::gated
|
||||||
ASYNC9C["9c async attach statements (rejected-for-now alternative)"]:::gated
|
ASYNC9C["20 async attach statements (rejected-for-now alternative)"]:::gated
|
||||||
TIMEOUTS2["idle timeouts become schedulable (net seam still needed)"]:::gated
|
TIMEOUTS2["idle timeouts become schedulable (net seam still needed)"]:::gated
|
||||||
|
|
||||||
FIBJOBS2["fiber-scheduled jobs (replaces drain-on-request; queue table stays)"]:::v2
|
FIBJOBS2["fiber-scheduled jobs (replaces drain-on-request; queue table stays)"]:::v2
|
||||||
|
|
@ -164,7 +164,7 @@ flowchart TD
|
||||||
UNIX["unix socket binding"]:::blocked
|
UNIX["unix socket binding"]:::blocked
|
||||||
PEERV["trusted-proxy PEER verification"]:::blocked
|
PEERV["trusted-proxy PEER verification"]:::blocked
|
||||||
|
|
||||||
CRYPTO["GATE: crypto fork — C builtins vs language bit ops (brainstorm); digests want iteration 20's Bytes"]:::gate
|
CRYPTO["GATE: crypto fork — C builtins vs language bit ops (brainstorm); digests want iteration 19's Bytes"]:::gate
|
||||||
SHA["SHA-256/512, HMAC, CRC32"]:::blocked
|
SHA["SHA-256/512, HMAC, CRC32"]:::blocked
|
||||||
ETAG["ETag + conditional requests"]:::blocked
|
ETAG["ETag + conditional requests"]:::blocked
|
||||||
COOKIE["signed cookies"]:::blocked
|
COOKIE["signed cookies"]:::blocked
|
||||||
|
|
@ -174,7 +174,7 @@ flowchart TD
|
||||||
JWT["JWT HS256 (HARD STOP after)"]:::blocked
|
JWT["JWT HS256 (HARD STOP after)"]:::blocked
|
||||||
|
|
||||||
RADIX["radix-tree routing"]:::blocked
|
RADIX["radix-tree routing"]:::blocked
|
||||||
I9E3["GATE: 9e measures the linear scan"]:::gate
|
I9E3["GATE: 22 measures the linear scan"]:::gate
|
||||||
|
|
||||||
STORAGE["storage-integration rows: migrations (future story), eager loading + tenant roots (query-surface work, 9-series)"]:::blocked
|
STORAGE["storage-integration rows: migrations (future story), eager loading + tenant roots (query-surface work, 9-series)"]:::blocked
|
||||||
|
|
||||||
|
|
@ -195,7 +195,7 @@ flowchart TD
|
||||||
Green nodes (CORS, security headers, host validation, strict-parsing
|
Green nodes (CORS, security headers, host validation, strict-parsing
|
||||||
audit, wildcards, route groups, `req.ctx`, XFF parsing, Accept
|
audit, wildcards, route groups, `req.ctx`, XFF parsing, Accept
|
||||||
negotiation) need nothing — startable in any order, gated by
|
negotiation) need nothing — startable in any order, gated by
|
||||||
`just web-app`. Note: 9d's keypair crypto is its own C implementation
|
`just web-app`. Note: 21's keypair crypto is its own C implementation
|
||||||
(already on branch `keypair-auth`) — it neither waits for nor feeds the
|
(already on branch `keypair-auth`) — it neither waits for nor feeds the
|
||||||
crypto-fork gate.
|
crypto-fork gate.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ operators, streaming/cancellation park behind 8/11). The memory-rich
|
||||||
features are **framework v2** = iteration 18 (spec APPROVED 2026-08-20,
|
features are **framework v2** = iteration 18 (spec APPROVED 2026-08-20,
|
||||||
plan next): TTL cache, @table flags, durable job queue with
|
plan next): TTL cache, @table flags, durable job queue with
|
||||||
drain-on-request, `transaction { }` over the WAL's staged batch. After 18,
|
drain-on-request, `transaction { }` over the WAL's staged batch. After 18,
|
||||||
the order resumes at 9c/9d. Edges: [00-dependency-graph.md](00-dependency-graph.md).
|
the order resumes at 20/21. Edges: [00-dependency-graph.md](00-dependency-graph.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -171,18 +171,18 @@ that sequences its tasks. Read one, approve, then the next starts.
|
||||||
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
||||||
| 9 | [Database engine](stories/language-runtime-database/done/09-database-engine.md) | 🔄 engine complete (storage/WAL/indexes/insert-update-delete); reads land with 9b |
|
| 9 | [Database engine](stories/language-runtime-database/done/09-database-engine.md) | 🔄 engine complete (storage/WAL/indexes/insert-update-delete); reads land with 9b |
|
||||||
| 9b | [`@table`, relations, query](stories/language-runtime-database/done/09b-table-relations-query.md) | 🔄 query surface + relations + FK done (branch query-surface); group-by parked |
|
| 9b | [`@table`, relations, query](stories/language-runtime-database/done/09b-table-relations-query.md) | 🔄 query surface + relations + FK done (branch query-surface); group-by parked |
|
||||||
| 9c | [Cross-program tables](stories/language-runtime-database/refine/09c-cross-program-tables.md) | 🔄 channel done (branch ipc-attach); manifest+binding pending |
|
| 20 | [Cross-program tables](stories/language-runtime-database/refine/20-cross-program-tables.md) | 🔄 channel done (branch ipc-attach); manifest+binding pending |
|
||||||
| 9d | [Keypair attach auth](stories/language-runtime-database/refine/09d-keypair-attach-auth.md) | 🔄 crypto+handshake done (branch keypair-auth); manifest pending |
|
| 21 | [Keypair attach auth](stories/language-runtime-database/refine/21-keypair-attach-auth.md) | 🔄 crypto+handshake done (branch keypair-auth); manifest pending |
|
||||||
| 9e | [Durability, throughput, scale](stories/language-runtime-database/refine/09e-durability-throughput-scale.md) | ⬜ needs a spec first |
|
| 22 | [Durability, throughput, scale](stories/language-runtime-database/refine/22-durability-throughput-scale.md) | ⬜ needs a spec first |
|
||||||
| 9f | [io_uring group-commit](stories/language-runtime-database/refine/09f-io-uring-commit.md) | ⬜ after 8 + 9e |
|
| 23 | [io_uring group-commit](stories/language-runtime-database/refine/23-io-uring-commit.md) | ⬜ after 8 + 22 |
|
||||||
| 9g | [Query grammar corpus](stories/language-runtime-database/refine/09g-query-grammar-corpus.md) | ⬜ needs a spec first |
|
| 27 | [Query grammar corpus](stories/language-runtime-database/refine/27-query-grammar-corpus.md) | ⬜ needs a spec first |
|
||||||
| 10 | [HTTP service layer](stories/language-runtime-database/10-http-service.md) | ⬜ | Hold |
|
| 10 | [HTTP service layer](stories/language-runtime-database/25-http-service.md) | ⬜ | Hold |
|
||||||
| 11 | [Fibers](stories/language-runtime-database/refine/11-fibers.md) | ⬜ | Hold |
|
| 11 | [Fibers](stories/language-runtime-database/refine/11-fibers.md) | ⬜ | Hold |
|
||||||
| 12 | [Blue-green deploy](stories/language-runtime-database/12-blue-green-deploy.md) | ⬜ | Hold |
|
| 12 | [Blue-green deploy](stories/language-runtime-database/26-blue-green-deploy.md) | ⬜ | Hold |
|
||||||
| 13 | [Compile-time metaprogramming](stories/language-runtime-database/refine/13-compile-time-metaprogramming.md) | ⬜ needs a spec first |
|
| 13 | [Compile-time metaprogramming](stories/language-runtime-database/refine/29-compile-time-metaprogramming.md) | ⬜ needs a spec first |
|
||||||
| 14 | [skillhost host workload](stories/language-runtime-database/refine/14-skillhost-host-workload.md) | ⬜ gaps recorded (branch query-grammar found skillhost needs no new query grammar); each gap a candidate iteration |
|
| 14 | [skillhost host workload](stories/language-runtime-database/refine/28-skillhost-host-workload.md) | ⬜ gaps recorded (branch query-grammar found skillhost needs no new query grammar); each gap a candidate iteration |
|
||||||
| 15 | [deps: `wo.toml [deps]`](stories/language-runtime-database/done/15-deps-package-manager.md) | ✅ **landed 2026-08-18** (branch web-framework): [deps] inline tables, git-binary fetch, wo.lock pinning, offline-when-locked, --update-deps, WO-E106/E107; `just deps-accept` 8/0 |
|
| 15 | [deps: `wo.toml [deps]`](stories/language-runtime-database/done/15-deps-package-manager.md) | ✅ **landed 2026-08-18** (branch web-framework): [deps] inline tables, git-binary fetch, wo.lock pinning, offline-when-locked, --update-deps, WO-E106/E107; `just deps-accept` 8/0 |
|
||||||
| 16 | [web framework](stories/language-runtime-database/done/16-web-framework.md) | ✅ **landed 2026-08-19** — writeonce-framework (HTTP/1.1 + router + Handler/Middleware) consumed by web-app through [deps]; h2c parked (§C) behind 8/9f/11. **v1 polish landed 2026-08-20** (branch framework-v1): get/post/put/delete_ helpers, 405+Allow, HEAD, Logging middleware, set_header; `just web-app` 16/0; fixed the interp-borrowed-field emitter crash en route. **Auth-in-core landed 2026-08-20**: http/auth.wo (Bearer/Basic, ct_eq, req.principal), web-app dogfoods BearerAuth, gate 17/0 |
|
| 16 | [web framework](stories/language-runtime-database/done/16-web-framework.md) | ✅ **landed 2026-08-19** — writeonce-framework (HTTP/1.1 + router + Handler/Middleware) consumed by web-app through [deps]; h2c parked (§C) behind 8/23/11. **v1 polish landed 2026-08-20** (branch framework-v1): get/post/put/delete_ helpers, 405+Allow, HEAD, Logging middleware, set_header; `just web-app` 16/0; fixed the interp-borrowed-field emitter crash en route. **Auth-in-core landed 2026-08-20**: http/auth.wo (Bearer/Basic, ct_eq, req.principal), web-app dogfoods BearerAuth, gate 17/0 |
|
||||||
| 17 | [library projects + `internal/`](stories/language-runtime-database/17-library-projects-internal.md) | ⏸ **PARKED 2026-08-20** (developer directive; framework v1 first) — forks settled, spec + plan approved and ready on branch `library-internal`: kind = "library" key; Go internal/ rule, dep-boundary-only; lib+bin dual; VM/GC untouched by design |
|
| 17 | [library projects + `internal/`](stories/language-runtime-database/17-library-projects-internal.md) | ⏸ **PARKED 2026-08-20** (developer directive; framework v1 first) — forks settled, spec + plan approved and ready on branch `library-internal`: kind = "library" key; Go internal/ rule, dep-boundary-only; lib+bin dual; VM/GC untouched by design |
|
||||||
| 18 | [framework v2: memory-rich features](stories/language-runtime-database/18-memory-db-features.md) | 🔄 **spec APPROVED 2026-08-20, plan next** ([spec](superpowers/specs/2026-08-20-memory-db-features-design.md)): TTL cache + @table flags + durable job queue (drain-on-request) + `transaction { }` over the WAL's staged batch; pub/sub REJECTED until 8/11 |
|
| 18 | [framework v2: memory-rich features](stories/language-runtime-database/18-memory-db-features.md) | 🔄 **spec APPROVED 2026-08-20, plan next** ([spec](superpowers/specs/2026-08-20-memory-db-features-design.md)): TTL cache + @table flags + durable job queue (drain-on-request) + `transaction { }` over the WAL's staged batch; pub/sub REJECTED until 8/11 |
|
||||||
|
|
||||||
|
|
@ -192,7 +192,7 @@ that sequences its tasks. Read one, approve, then the next starts.
|
||||||
|
|
||||||
| Track | Item | Where |
|
| Track | Item | Where |
|
||||||
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||||
| Language | nothing active — the framework v1-polish slice landed 2026-08-20 (branch framework-v1, awaiting merge); next per the order: brainstorm 9c/9d's forks | [order](#implementation-order-re-sequenced-2026-08-20--framework-goal) |
|
| Language | nothing active — the framework v1-polish slice landed 2026-08-20 (branch framework-v1, awaiting merge); next per the order: brainstorm 20/21's forks | [order](#implementation-order-re-sequenced-2026-08-20--framework-goal) |
|
||||||
|
|
||||||
Off-goal work is parked; the goal (2026-08-20) is the web framework as a
|
Off-goal work is parked; the goal (2026-08-20) is the web framework as a
|
||||||
polished micro-framework v1 — iteration 17 (library kind + `internal/`) is
|
polished micro-framework v1 — iteration 17 (library kind + `internal/`) is
|
||||||
|
|
@ -362,11 +362,11 @@ The C proving-ground work (`exploration/c-runtime/`, phases A–F: 859k reads/s,
|
||||||
### Implementation order (re-sequenced 2026-08-20 — framework goal)
|
### Implementation order (re-sequenced 2026-08-20 — framework goal)
|
||||||
|
|
||||||
The goal is the web framework as a first-class library, so the framework
|
The goal is the web framework as a first-class library, so the framework
|
||||||
line leads and the workload-driven extras (14, 9g) demote behind it.
|
line leads and the workload-driven extras (14, 27) demote behind it.
|
||||||
Dependency rules that force the shape: 9f explicitly after 8 + 9e; 9c
|
Dependency rules that force the shape: 23 explicitly after 8 + 22; 20
|
||||||
"precedes iteration 10"; 9d's plan folds into 9c's; 12 only after 9 + 10
|
"precedes iteration 25"; 21's plan folds into 20's; 12 only after 9 + 10
|
||||||
(catalog to diff, HTTP to build on); 11 rides 8's shard scheduler; h2c
|
(catalog to diff, HTTP to build on); 11 rides 8's shard scheduler; h2c
|
||||||
parked behind 8/9f/11; the post-12 parked list stays parked by the
|
parked behind 8/23/11; the post-12 parked list stays parked by the
|
||||||
2026-08-08 scope directive. (Iteration 7 dropped from this list — landed
|
2026-08-08 scope directive. (Iteration 7 dropped from this list — landed
|
||||||
2026-08-15.)
|
2026-08-15.)
|
||||||
|
|
||||||
|
|
@ -379,27 +379,27 @@ parked behind 8/9f/11; the post-12 parked list stays parked by the
|
||||||
APPROVED 2026-08-20, the only all-green spec-approved node in the
|
APPROVED 2026-08-20, the only all-green spec-approved node in the
|
||||||
[dependency graph](00-dependency-graph.md) — plan next, then
|
[dependency graph](00-dependency-graph.md) — plan next, then
|
||||||
implement.
|
implement.
|
||||||
3. **9c then 9d** — finish the half-done branches (ipc-attach: manifest +
|
3. **20 then 21** — finish the half-done branches (ipc-attach: manifest +
|
||||||
binding; keypair-auth: manifest) before they rot; 9d folds into 9c's
|
binding; keypair-auth: manifest) before they rot; 21 folds into 20's
|
||||||
plan; both must precede 10.
|
plan; both must precede 10.
|
||||||
4. **9e** — the measurement backbone; baseline single-shard BEFORE the
|
4. **22** — the measurement backbone; baseline single-shard BEFORE the
|
||||||
runtime restructure so 8/9f/11 sign against real numbers.
|
runtime restructure so 8/23/11 sign against real numbers.
|
||||||
5. **8** — shard-actor; the framework's multi-core serving story; unblocks
|
5. **8** — shard-actor; the framework's multi-core serving story; unblocks
|
||||||
9f, 11, h2c.
|
23, 11, h2c.
|
||||||
6. **9f** — io_uring group-commit; explicitly after 8 + 9e.
|
6. **23** — io_uring group-commit; explicitly after 8 + 22.
|
||||||
7. **11** — fibers; retires the framework's disclosed keep-alive limit (an
|
7. **11** — fibers; retires the framework's disclosed keep-alive limit (an
|
||||||
idle connection starving accept forced close-when-idle in iteration 16 —
|
idle connection starving accept forced close-when-idle in iteration 16 —
|
||||||
parked fds fix it properly); with 8 + 9f done, **h2c unparks** (spec §C)
|
parked fds fix it properly); with 8 + 23 done, **h2c unparks** (spec §C)
|
||||||
as the framework's HTTP/2 slice.
|
as the framework's HTTP/2 slice.
|
||||||
8. **10** — HTTP service layer; after 9c by its own precedence note;
|
8. **10** — HTTP service layer; after 20 by its own precedence note;
|
||||||
`service` blocks lower onto the framework instead of a parallel stack.
|
`service` blocks lower onto the framework instead of a parallel stack.
|
||||||
9. **12** — blue-green; prerequisites 9 + 10 now exist; completes the
|
9. **12** — blue-green; prerequisites 9 + 10 now exist; completes the
|
||||||
framework's deploy story.
|
framework's deploy story.
|
||||||
10. **9g then 14** — demoted with the goal shift: skillhost is no longer the
|
10. **27 then 14** — demoted with the goal shift: skillhost (28) is no longer the
|
||||||
driving workload; 9g likely collapses to "confirm `len(query)` + add
|
driving workload; 27 likely collapses to "confirm `len(query)` + add
|
||||||
`exists`" and precedes 14 when they run.
|
`exists`" and precedes 28 when they run.
|
||||||
11. **13 + parked drain** — metaprogramming (spec first), then group-by,
|
11. **13 + parked drain** — metaprogramming (spec first), then group-by,
|
||||||
`pub(read)`/`using`/`#if`, ADT roster, WO-E225 — held behind 12 by the
|
`pub(read)`/`using`/`#if`, ADT roster, WO-E225 — held behind 26 by the
|
||||||
scope directive.
|
scope directive.
|
||||||
|
|
||||||
### Language track — sequenced, on the critical path
|
### Language track — sequenced, on the critical path
|
||||||
|
|
@ -412,18 +412,18 @@ parked behind 8/9f/11; the post-12 parked list stays parked by the
|
||||||
| 8 | Shard-actor runtime | [plan 4](superpowers/plans/2026-08-01-shard-actor-vm-runtime.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) |
|
| 9 | Database engine binding | [plan 5](superpowers/plans/2026-08-01-db-engine-binding.md) |
|
||||||
| 9b | `@table` + relations + language-integrated query — comprehension queries, `ref`/`backlink` navigation, GroupBy aggregates; acceptance: new `docs/examples/employee` sample | [spec](superpowers/specs/2026-08-15-table-relations-query-design.md) · [plan](plan/compiler/2026-08-15-employee-relations-query.md) |
|
| 9b | `@table` + relations + language-integrated query — comprehension queries, `ref`/`backlink` navigation, GroupBy aggregates; acceptance: new `docs/examples/employee` sample | [spec](superpowers/specs/2026-08-15-table-relations-query-design.md) · [plan](plan/compiler/2026-08-15-employee-relations-query.md) |
|
||||||
| 9c | Cross-program tables — attach to a running program's database (IPC string in wo.toml, manifest-granted rights, owner stays the single writer) | **no spec yet** — four open forks recorded in the iteration; brainstorm before planning |
|
| 20 | Cross-program tables — attach to a running program's database (IPC string in wo.toml, manifest-granted rights, owner stays the single writer) | **no spec yet** — four open forks recorded in the iteration; brainstorm before planning |
|
||||||
| 9d | Keypair attach auth — mutual challenge–response, grants name public keys, uid superseded | **no spec yet** — four forks recorded; plan folds into 9c's |
|
| 21 | Keypair attach auth — mutual challenge–response, grants name public keys, uid superseded | **no spec yet** — four forks recorded; plan folds into 20's |
|
||||||
| 9e | Durability + throughput + scale — restart-persistence, read/write benchmark, ~1M rows; the gate every later optimization re-runs | **no spec yet** — four forks recorded; the measurement backbone |
|
| 22 | Durability + throughput + scale — restart-persistence, read/write benchmark, ~1M rows; the gate every later optimization re-runs | **no spec yet** — four forks recorded; the measurement backbone |
|
||||||
| 9f | io_uring group-commit write path — batched durability overlapped on shard threads, fsync fallback | **no spec yet** — brainstorm after iterations 8 + 9e |
|
| 23 | io_uring group-commit write path — batched durability overlapped on shard threads, fsync fallback | **no spec yet** — brainstorm after iterations 8 + 22 |
|
||||||
| 9g | Query grammar from real embedded-DB corpora — whole-query count + correlated exists, driven by the skillhost SQL catalogue; add only what a corpus uses | **no spec yet** — three forks; may collapse to "confirm len(query) + add exists" |
|
| 27 | Query grammar from real embedded-DB corpora — whole-query count + correlated exists, driven by the skillhost SQL catalogue; add only what a corpus uses | **no spec yet** — three forks; may collapse to "confirm len(query) + add exists" |
|
||||||
| 14 | skillhost host workload — port skillhost (MCP host + confined script runner) to writeonce; drives the missing host capabilities into the open (bounded subprocess, stdin/stdout transport, fs metadata, FFI-vs-out-of-process) | **no spec yet** — gaps recorded in the iteration; each gap brainstormed on demand, bounded-subprocess first |
|
| 14 | skillhost host workload — port skillhost (MCP host + confined script runner) to writeonce; drives the missing host capabilities into the open (bounded subprocess, stdin/stdout transport, fs metadata, FFI-vs-out-of-process) | **no spec yet** — gaps recorded in the iteration; each gap brainstormed on demand, bounded-subprocess first |
|
||||||
| 17 | library projects + dependency privacy — `wo.toml` kind = "library" (checkable without entry, dual lib+bin) + Go-style `internal/` at the [deps] boundary; framework reorg demonstrates both | **forks settled 2026-08-20** — decisions + framework/compiler/VM/GC impact in the iteration; spec/plan next |
|
| 17 | library projects + dependency privacy — `wo.toml` kind = "library" (checkable without entry, dual lib+bin) + Go-style `internal/` at the [deps] boundary; framework reorg demonstrates both | **forks settled 2026-08-20** — decisions + framework/compiler/VM/GC impact in the iteration; spec/plan next |
|
||||||
| 10 | HTTP service layer | [plan 6](superpowers/plans/2026-08-01-http-service-layer.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) |
|
| 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 |
|
| 12 | Blue-green deploy | [spec](superpowers/specs/2026-08-03-blue-green-vm-design.md) — plan authored after iterations 9 + 25 |
|
||||||
|
|
||||||
### Language track — parked until after iteration 12
|
### Language track — parked until after iteration 26
|
||||||
|
|
||||||
Recorded 2026-08-08 by scope directive; nothing here lands before the
|
Recorded 2026-08-08 by scope directive; nothing here lands before the
|
||||||
log-watcher proof.
|
log-watcher proof.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
> **Status: target workload — does not compile on today's toolchain.**
|
> **Status: target workload — does not compile on today's toolchain.**
|
||||||
> Written ahead of iterations
|
> Written ahead of iterations
|
||||||
> [9c (cross-program tables)](../../stories/language-runtime-database/refine/09c-cross-program-tables.md)
|
> [20 (cross-program tables)](../../stories/language-runtime-database/refine/20-cross-program-tables.md)
|
||||||
> and [9d (keypair attach auth)](../../stories/language-runtime-database/refine/09d-keypair-attach-auth.md),
|
> and [21 (keypair attach auth)](../../stories/language-runtime-database/refine/21-keypair-attach-auth.md),
|
||||||
> the way every acceptance sample here precedes its features. It also leans
|
> the way every acceptance sample here precedes its features. It also leans
|
||||||
> on 9/9b (the [employee sample](../employee/) it attaches to must run
|
> on 9/9b (the [employee sample](../employee/) it attaches to must run
|
||||||
> first).
|
> first).
|
||||||
|
|
@ -37,6 +37,6 @@ the source says `employee.Employee`.
|
||||||
| `employee-list staff <dept>` | unique-name index probe + `staff` backlink scan, both in A |
|
| `employee-list staff <dept>` | unique-name index probe + `staff` backlink scan, both in A |
|
||||||
| `employee-list probe-write` | the rights matrix: registered read-only, so the insert traps with access-denied (caught, `DENIED …`, exit 4) and A's row count is unchanged |
|
| `employee-list probe-write` | the rights matrix: registered read-only, so the insert traps with access-denied (caught, `DENIED …`, exit 4) and A's row count is unchanged |
|
||||||
|
|
||||||
The 9d acceptance drives the rest from the outside: wrong key, no key,
|
The 21 acceptance drives the rest from the outside: wrong key, no key,
|
||||||
same-uid-wrong-key, impostor socket, handshake replay, key rotation — see
|
same-uid-wrong-key, impostor socket, handshake replay, key rotation — see
|
||||||
the iteration's criteria; this sample is the workload they run against.
|
the iteration's criteria; this sample is the workload they run against.
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ first (pure `.wo` cannot express it yet).
|
||||||
|
|
||||||
| Item | State |
|
| Item | State |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Path matching | 🔶 linear scan, first-match-wins; a radix tree is a performance slice that waits for iteration 9e to MEASURE it first |
|
| Path matching | 🔶 linear scan, first-match-wins; a radix tree is a performance slice that waits for iteration 22 to MEASURE it first |
|
||||||
| Method dispatch · path params · 404 · 405+`Allow` | ✅ |
|
| Method dispatch · path params · 404 · 405+`Allow` | ✅ |
|
||||||
| Wildcards | ⬜ only `:param` today; `*rest` capture is a candidate slice |
|
| Wildcards | ⬜ only `:param` today; `*rest` capture is a candidate slice |
|
||||||
| Precedence rules | 🔶 registration order IS the rule (documented); specificity-based precedence unneeded until wildcards exist |
|
| Precedence rules | 🔶 registration order IS the rule (documented); specificity-based precedence unneeded until wildcards exist |
|
||||||
|
|
|
||||||
159
docs/plan/exploration/fibers/00-fibers.md
Normal file
159
docs/plan/exploration/fibers/00-fibers.md
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
# Fibers — the concurrency model, its terminology, and why writeonce chose what it chose
|
||||||
|
|
||||||
|
> Exploration/reference note (no status banner by board convention).
|
||||||
|
> The normative decisions live in the arc spec
|
||||||
|
> ([`2026-08-20-shard-fiber-arc-design.md`](../../../superpowers/specs/2026-08-20-shard-fiber-arc-design.md))
|
||||||
|
> and iterations [8](../../../stories/language-runtime-database/refine/08-shard-actor-runtime.md) /
|
||||||
|
> [11](../../../stories/language-runtime-database/refine/11-fibers.md); this
|
||||||
|
> page explains the WHY at doctrine depth. Written 2026-08-20, when this
|
||||||
|
> file was also the target of a dangling reference from iteration 11 —
|
||||||
|
> it exists now.
|
||||||
|
|
||||||
|
> **Demonstrated live** by [`docs/examples/fibers`](../../../examples/fibers/README.md)
|
||||||
|
> (`just fibers`, 8 checks): byte-exact budget interleave, a parked
|
||||||
|
> sleeper blocking nobody, on the io_uring AND epoll backends.
|
||||||
|
|
||||||
|
## What a green thread is, and why anyone bothers
|
||||||
|
|
||||||
|
A green thread (fiber, virtual thread, coroutine — the tradition picks
|
||||||
|
the name) is a thread scheduled by the LANGUAGE RUNTIME, not the kernel.
|
||||||
|
The kernel sees a handful of OS threads; the runtime multiplexes
|
||||||
|
thousands-to-millions of logical threads on top.
|
||||||
|
|
||||||
|
The economics: an OS thread costs a fixed stack reservation (typically
|
||||||
|
1–8 MiB of address space) and every context switch goes through the
|
||||||
|
kernel scheduler (CFS is O(log N), and task counts in the hundreds
|
||||||
|
already hurt). A green thread can start around 2 KiB and switch in tens
|
||||||
|
of nanoseconds — a register save and a pointer swap, no syscall. That is
|
||||||
|
the difference between a few thousand concurrent connections and a few
|
||||||
|
million.
|
||||||
|
|
||||||
|
## The terminology, untangled
|
||||||
|
|
||||||
|
- **Green thread** — originally Java pre-1.3's model, which was M:1
|
||||||
|
(everything on ONE OS thread — no parallelism at all). Now used
|
||||||
|
loosely for any user-space thread.
|
||||||
|
- **Fiber** — usually implies COOPERATIVE: it runs until it explicitly
|
||||||
|
yields.
|
||||||
|
- **Coroutine** — the broadest term; covers stackful and stackless both.
|
||||||
|
- **Goroutine** — Go's M:N implementation, preemptively scheduled.
|
||||||
|
- **Virtual thread** — Java 21's, also M:N.
|
||||||
|
|
||||||
|
The load-bearing distinction between "fiber" and "goroutine" is NOT the
|
||||||
|
stack — it is the EVICTION POLICY. A classic fiber owns the CPU until it
|
||||||
|
yields; one selfish loop starves everything behind it. A goroutine can
|
||||||
|
be interrupted WITHOUT ITS CONSENT (since Go 1.14, via a signal that
|
||||||
|
stops it at a safe instruction).
|
||||||
|
|
||||||
|
M:1 vs M:N matters concretely here: one process embedding its own
|
||||||
|
storage would leave every core but one idle under M:1. writeonce is M:N
|
||||||
|
— stage 2 of the arc pins one kernel thread per core (shards), and
|
||||||
|
fibers multiplex above them.
|
||||||
|
|
||||||
|
## writeonce's model: cooperative mechanics, accounting-based eviction
|
||||||
|
|
||||||
|
The BEAM shape. Mechanically cooperative — no signals, no interrupts,
|
||||||
|
no async preemption machinery — but every loop back-edge decrements a
|
||||||
|
REDUCTION BUDGET (`WO_REDUCTIONS`, default 4000), and at zero the fiber
|
||||||
|
is re-queued whether it likes it or not.
|
||||||
|
|
||||||
|
What that buys, all three at once:
|
||||||
|
|
||||||
|
- goroutine-grade starvation-freedom (the corpus pins budget-1 as EXACT
|
||||||
|
round-robin across fibers — `runtime/test/test_fiber.c`);
|
||||||
|
- fiber-grade DETERMINISM: same program, same schedule, byte-identical
|
||||||
|
output — signal-based preemption points are nondeterministic by
|
||||||
|
nature, Go's included;
|
||||||
|
- zero interruption machinery: an interpreter's dispatch loop is always
|
||||||
|
at a safe point, so the "where can I safely stop this thread"
|
||||||
|
problem Go solves with signal handlers and pc maps costs nothing.
|
||||||
|
|
||||||
|
One implementation lesson worth keeping (it cost a livelock): the
|
||||||
|
budget check runs at loop back-edges AFTER the jump lands, so the saved
|
||||||
|
resume pc is the loop head. A pre-instruction save at budget 1
|
||||||
|
re-executes the jump straight into the same decrement, forever.
|
||||||
|
|
||||||
|
## Should the grammar have `async`? No — permanently rejected
|
||||||
|
|
||||||
|
`async` is only NECESSARY in stackless designs, where the compiler must
|
||||||
|
know statically which functions can suspend so it can transform each
|
||||||
|
into a state-machine struct. That requirement is function COLORING: an
|
||||||
|
async function can only be called from another async function, so the
|
||||||
|
keyword propagates virally up every call chain, and the ecosystem forks
|
||||||
|
into sync and async flavors of everything. In writeonce it would color
|
||||||
|
the query API, then every handler that touches data — the exact code
|
||||||
|
that most wants to read as straight-line logic.
|
||||||
|
|
||||||
|
With stackful fibers, a blocking read ten frames deep just parks. The
|
||||||
|
suspension point is a RUNTIME fact, not a TYPE-SYSTEM fact. Same source
|
||||||
|
text in program mode and server mode; no keyword; no coloring. This is
|
||||||
|
iteration 11's "async/await — permanently rejected surface, not
|
||||||
|
deferred", with the reasoning attached.
|
||||||
|
|
||||||
|
## Stackful vs stackless: the real fork, and the recommendation
|
||||||
|
|
||||||
|
**Stackful** (goroutines, Java virtual threads, classic fibers): each
|
||||||
|
logical thread owns a real, growable stack; it can suspend from ANY
|
||||||
|
depth. Costs: growth-by-copy must find and rewrite every pointer into
|
||||||
|
the old stack (Go's machinery), and FFI is the classic killer — C
|
||||||
|
callees assume a large contiguous stack and know nothing about growth,
|
||||||
|
so every foreign call needs a stack switch or a guaranteed segment.
|
||||||
|
|
||||||
|
**Stackless** (Rust futures, JS promises, C# async, Python coroutines):
|
||||||
|
the compiler rewrites each async function into a fixed-size state
|
||||||
|
object. Cheaper, runtime-optional — and it buys the coloring above,
|
||||||
|
plus suspension only at explicit await points.
|
||||||
|
|
||||||
|
**Recommendation, and what is already built: STACKFUL — because both of
|
||||||
|
its classic costs vanish in this stack.**
|
||||||
|
|
||||||
|
1. **No FFI, by doctrine.** The main argument against small growable
|
||||||
|
stacks is C interop. writeonce has no C to call (the reject row is
|
||||||
|
load-bearing). The cost is simply absent.
|
||||||
|
2. **The "stack" is register-indexed, not address-based.** A
|
||||||
|
`wo_fiber`'s state is the interpreter's register window + frame
|
||||||
|
array — frames reference registers by INDEX, so a context is
|
||||||
|
relocatable by construction. Growth is allocate-larger + memcpy with
|
||||||
|
ZERO pointer fixups; Go's hardest stackful problem does not exist
|
||||||
|
here. Contexts are ~42 KiB fixed today; start-small (~4 KiB)
|
||||||
|
growable contexts are the recorded improvement that takes fiber
|
||||||
|
counts from thousands toward millions (arc spec, "Fiber context
|
||||||
|
growth").
|
||||||
|
|
||||||
|
## The two problems every design must solve, and where they land here
|
||||||
|
|
||||||
|
- **Blocking syscalls.** A green thread calling fsync blocks the OS
|
||||||
|
thread carrying it and strands every fiber behind it — and an
|
||||||
|
embedded database fsyncs on every commit, milliseconds each, an
|
||||||
|
eternity for a scheduler. writeonce's answer is io_uring, twice: the
|
||||||
|
arc's per-shard ring parks blocking `net`/`time` builtins (T4,
|
||||||
|
readiness ops first, then reads/writes as ring ops), and iteration
|
||||||
|
23 rides the SAME ring for WAL WRITE+FSYNC group-commit so the
|
||||||
|
DB-owner shard keeps executing while durability drains. epoll
|
||||||
|
survives only as the portability fallback behind a startup probe
|
||||||
|
(`WO_IO=uring|epoll`) — seccomp'd containers routinely deny io_uring,
|
||||||
|
and the binary must run everywhere. The reference cards live in
|
||||||
|
[`../linux/07-io_uring.md`](../linux/07-io_uring.md).
|
||||||
|
- **Preemption.** Solved by the reduction budget above. And to the
|
||||||
|
standing question "does preemption depend on FFI?" — NO, inversely:
|
||||||
|
preemption holes come from frames the runtime cannot interrupt, which
|
||||||
|
in Go means foreign C frames (its signal preemption skips them). No
|
||||||
|
FFI means no foreign frames; the only non-preemptible regions are our
|
||||||
|
OWN builtins — C we control, bounded, and the blocking ones become
|
||||||
|
parked ring ops. FFI would have CREATED the dependency; its absence
|
||||||
|
is why the budget is airtight.
|
||||||
|
|
||||||
|
## Precedent survey (what was adopted, what was rejected)
|
||||||
|
|
||||||
|
- **BEAM (Erlang/Elixir): adopted** — reduction-budget preemption,
|
||||||
|
actor mailboxes, one-message-at-a-time delivery, isolated failure
|
||||||
|
(a fiber's uncaught trap kills that fiber alone).
|
||||||
|
- **Go: half-adopted** — M:N over pinned threads yes; stack-copy
|
||||||
|
pointer rewriting unnecessary here (register-indexed contexts);
|
||||||
|
signal-based async preemption rejected (nondeterministic, and the
|
||||||
|
budget makes it redundant).
|
||||||
|
- **Rust/Tokio, JS, C#, Python: rejected** — stackless coloring is the
|
||||||
|
cost this language exists to not pay.
|
||||||
|
- **Java Loom: matches** — park-under-a-blocking-API is exactly the
|
||||||
|
stdlib posture (`net.read` blocks in program mode, parks under
|
||||||
|
fibers, same signature).
|
||||||
|
|
@ -40,7 +40,12 @@ iterations); no commits by agents — drafts go to `.dev/commit.md`.
|
||||||
|
|
||||||
## Iterations (rows in dependency order — `#` is an immutable ID, not a rank)
|
## Iterations (rows in dependency order — `#` is an immutable ID, not a rank)
|
||||||
|
|
||||||
Re-sequenced 2026-08-20 from the edges in
|
RENUMBERED 2026-08-20 (developer directive): pending iterations carry
|
||||||
|
fresh IDs in priority order; LANDED iterations keep their historical
|
||||||
|
numbers (code comments and commit history cite them — records, not a
|
||||||
|
queue). Mapping: 19←20(scalars), 20←9c, 21←9d, 22←9e, 23←9f, 24←19(chat),
|
||||||
|
25←10, 26←12, 27←9g, 28←14, 29←13; 8, 11, 17, 18 unchanged.
|
||||||
|
Re-sequenced from the edges in
|
||||||
[`docs/00-dependency-graph.md`](../../00-dependency-graph.md): landed
|
[`docs/00-dependency-graph.md`](../../00-dependency-graph.md): landed
|
||||||
rows in landing order, then the pending rows in implementation order.
|
rows in landing order, then the pending rows in implementation order.
|
||||||
File names keep their IDs — every board, spec, and plan references
|
File names keep their IDs — every board, spec, and plan references
|
||||||
|
|
@ -52,28 +57,29 @@ iterations by number, so numbers never renumber.
|
||||||
| 2 | 2 | [VM core](done/02-vm-core.md) | `wovm`: `.wob` loader, register interpreter, arena, borrow word, `@gc` collector |
|
| 2 | 2 | [VM core](done/02-vm-core.md) | `wovm`: `.wob` loader, register interpreter, arena, borrow word, `@gc` collector |
|
||||||
| 3 | 3 | [Compiler front](done/03-compiler-front.md) | `woc`: lexer → parser → typechecker → ownership pass, diagnostics |
|
| 3 | 3 | [Compiler front](done/03-compiler-front.md) | `woc`: lexer → parser → typechecker → ownership pass, diagnostics |
|
||||||
| 4 | 4 | [Single binary end-to-end](done/04-single-binary-e2e.md) | emitter + conformance corpus + `woc build` self-contained binary |
|
| 4 | 4 | [Single binary end-to-end](done/04-single-binary-e2e.md) | emitter + conformance corpus + `woc build` self-contained binary |
|
||||||
| 5 | 5 | [Language surface](05-language-surface.md) | Haxe-parity adoptions: switch, records, optionals, try/catch, statics, modules… (`pub(read)`/`using`/`#if` remainders sit in the post-12 drain) |
|
| 5 | 5 | [Language surface](05-language-surface.md) | Haxe-parity adoptions, grammar + strictness halves (landed in waves through 2026-08-20) |
|
||||||
| 6 | 6 | [Program mode + stdlib](done/06-program-mode-stdlib.md) | `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` builtins |
|
| 6 | 6 | [Program mode + stdlib](done/06-program-mode-stdlib.md) | `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` builtins |
|
||||||
| 7 | 7 | [log-watcher proof](done/07-logwatcher-proof.md) | the driving workload compiled, executable, soak-proven (landed 2026-08-15) |
|
| 7 | 7 | [log-watcher proof](done/07-logwatcher-proof.md) | the driving workload compiled, executable, soak-proven (landed 2026-08-15) |
|
||||||
| 8 | 7b | [Inferred GC + mark-sweep](done/07b-inferred-gc-mark-sweep.md) | `@gc` removed from the language; compiler infers GC-ness; RC replaced by incremental per-shard tri-color mark-sweep |
|
| 8 | 7b | [Inferred GC + mark-sweep](done/07b-inferred-gc-mark-sweep.md) | `@gc` removed; GC-ness inferred; RC replaced by incremental per-shard tri-color mark-sweep |
|
||||||
| 9 | 9 | [Database engine](done/09-database-engine.md) | class-shaped tables, typed WAL + recovery, `insert`/`select` execute |
|
| 9 | 9 | [Database engine](done/09-database-engine.md) | class-shaped tables, typed WAL + recovery, `insert`/`select` execute |
|
||||||
| 10 | 9b | [`@table`, relations, query](done/09b-table-relations-query.md) | `@table` becomes real storage; typed `ref`/`backlink`/`multi` relations; compiler-checked LINQ-shaped queries lowered to engine ops |
|
| 10 | 9b | [`@table`, relations, query](done/09b-table-relations-query.md) | `@table` real storage; `ref`/`backlink`/`multi`; compiler-checked queries |
|
||||||
| 11 | 15 | [deps: `wo.toml [deps]`](done/15-deps-package-manager.md) | exact-rev git dependencies + `wo.lock` + `.wo-deps` cache; `use <dep>` resolves a fetched project as a module root; flat-only, network-free when locked |
|
| 11 | 15 | [deps: `wo.toml [deps]`](done/15-deps-package-manager.md) | exact-rev git deps + `wo.lock` + `.wo-deps`; flat-only, offline once locked |
|
||||||
| 12 | 16 | [web framework](done/16-web-framework.md) | a `.wo`-library framework (HTTP/1.1 keep-alive behind a TLS-terminating proxy): router, `Handler`/`Middleware` interfaces, auth, all three body hooks, `@table` data layer; `docs/examples/web-app` consumes it via `[deps]`; h2c parked behind 8/9f/11 |
|
| 12 | 16 | [web framework](done/16-web-framework.md) | the `.wo` framework v1 (router, middleware, auth, all three body hooks) consumed via `[deps]` |
|
||||||
| **13** | **18** | [framework v2: memory-rich features](18-memory-db-features.md) | **NEXT — spec approved 2026-08-20**: TTL cache, @table feature flags with cached reads, durable @table job queue with drain-on-request, `transaction { }` exposing the WAL's staged batch (enqueue + write, one commit — no outbox) |
|
| 13 | 8+11 | [Shard-actor runtime](08-shard-actor-runtime.md) · [Fibers](refine/11-fibers.md) | THE ARC (stages 1+2 landed 2026-08-20: fibers/budget/actors/io_uring plane; pinned shards, envelope sends, home-routed frees, WO-E222); stage 3 = transparent DB RPC + 22 re-run |
|
||||||
| 14 | 9c | [Cross-program tables](refine/09c-cross-program-tables.md) | attach to a running program's database over a local IPC channel: manifest-granted rights, typed statements, owner stays the single writer (channel half-built) |
|
| 14 | 18 | [framework v2: memory-rich features](18-memory-db-features.md) | spec+plan approved: TTL cache, @table flags, durable job queue, `transaction { }` over the WAL's staged batch |
|
||||||
| 15 | 9d | [Keypair attach auth](refine/09d-keypair-attach-auth.md) | program identity is a keypair: mutual challenge–response at attach, grants name public keys, replay-proof (crypto half-built; plan folds into 9c's) |
|
| 15 | 19 | [Float + Bytes](19-missing-scalar-types.md) | the missing scalars, full stack: IEEE-quiet f64 through literals/VM/@table/WAL/json + Bytes as the binary carrier — feeds 24 (WS frames) and the crypto fork (digests). *(was 20)* |
|
||||||
| 16 | 9e | [Durability, throughput, scale](refine/09e-durability-throughput-scale.md) | restart-persistence proof, read/write benchmark, ~1M-row load; the baseline 8/9f/11 sign against |
|
| 16 | 20 | [Cross-program tables](refine/20-cross-program-tables.md) | attach to a running program's database over local IPC; owner stays the single writer (channel half-built). *(was 9c)* |
|
||||||
| 17 | 8 | [Shard-actor runtime](08-shard-actor-runtime.md) | thread-per-core shards, per-shard heaps, ownership-move messaging (collector precondition met by 7b) |
|
| 17 | 21 | [Keypair attach auth](refine/21-keypair-attach-auth.md) | program identity is a keypair; mutual challenge–response at attach (crypto half-built; plan folds into 20's). *(was 9d)* |
|
||||||
| 18 | 9f | [io_uring group-commit](refine/09f-io-uring-commit.md) | replace fsync-per-commit with io_uring batched durability on the shard tick; fsync fallback kept (after 8 + 9e, explicit) |
|
| 18 | 22 | [Durability, throughput, scale](refine/22-durability-throughput-scale.md) | restart-persistence proof, benchmarks, ~1M rows — the baseline the arc and 23 sign against. *(was 9e)* |
|
||||||
| 19 | 11 | [Fibers](refine/11-fibers.md) | green threads on the shard scheduler: reduction-budget preemption, blocking builtins park; unparks h2c (with 8/9f), streaming, cancellation, pub/sub, fiber jobs |
|
| 19 | 23 | [io_uring group-commit](refine/23-io-uring-commit.md) | WAL WRITE+FSYNC chains on the arc's per-shard rings; fsync fallback kept (after 22 + the arc). *(was 9f)* |
|
||||||
| 20 | 10 | [HTTP service layer](10-http-service.md) | `service` blocks lower onto the framework (after 9b + 9c by their own precedence notes) |
|
| 20 | 24 | [chat: WebSocket workload](refine/24-chat-websocket-workload.md) | the arc's acceptance: WS upgrade + frames (SHA-1 via crypto fork, Bytes via 19), rooms/broadcast, 1k clients, drain-clean. *(was 19)* |
|
||||||
| 21 | 12 | [Blue-green deploy](12-blue-green-deploy.md) | two VM slots, in-runtime compile, atomic switch, resident rollback (plan authored after 9 + 10) |
|
| 21 | 25 | [HTTP service layer](25-http-service.md) | `service` blocks lower onto the framework (after 9b + 20 by their own precedence notes). *(was 10)* |
|
||||||
| 22 | 9g | [Query grammar corpus](refine/09g-query-grammar-corpus.md) | grow the query grammar from real corpora; likely collapses to "confirm `len(query)` + add `exists`"; precedes 14 |
|
| 22 | 26 | [Blue-green deploy](26-blue-green-deploy.md) | two VM slots, in-runtime compile, atomic switch, resident rollback (plan authored after 9 + 25). *(was 12)* |
|
||||||
| 23 | 14 | [skillhost host workload](refine/14-skillhost-host-workload.md) | host-shaped driving workload naming runtime gaps (bounded subprocess, stdin/stdout transport, fs metadata, FFI-vs-out-of-process) — demoted with the framework goal |
|
| 23 | 27 | [Query grammar corpus](refine/27-query-grammar-corpus.md) | grow the query grammar from real corpora; likely collapses to "confirm `len(query)` + add `exists`"; precedes 28. *(was 9g)* |
|
||||||
| 24 | 13 | [Compile-time metaprogramming](refine/13-compile-time-metaprogramming.md) | `@derive(Json/Csv/Eq/Hash/Show)` from class-table metadata; held behind 12 with the parked drain by the 2026-08-08 scope directive |
|
| 24 | 28 | [skillhost host workload](refine/28-skillhost-host-workload.md) | host-shaped driving workload naming runtime gaps — demoted with the framework goal. *(was 14)* |
|
||||||
| 26 | 20 | [Float + Bytes](20-missing-scalar-types.md) | the missing scalars, full stack: IEEE-quiet f64 through literals/VM/@table/WAL/json (fractions decode at last) + Bytes as the binary carrier; forks settled 2026-08-20, spec next — feeds 19 (WS frames) and the crypto fork (digests) |
|
| 25 | 29 | [Compile-time metaprogramming](refine/29-compile-time-metaprogramming.md) | `@derive(...)` from class-table metadata; held with the parked drain by the 2026-08-08 scope directive. *(was 13)* |
|
||||||
| ⏸ | 17 | [library projects + `internal/`](17-library-projects-internal.md) | **PARKED** (spec + plan approved, branch `library-internal`) — `wo.toml` kind = "library" + Go's `internal/` rule; slots anywhere after 16 whenever directed, bringing the framework reorg with it |
|
| ⏸ | 17 | [library projects + `internal/`](17-library-projects-internal.md) | **PARKED** (spec + plan approved, branch `library-internal`) — `wo.toml` kind = "library" + Go's `internal/` rule; slots anywhere after 16 on directive |
|
||||||
|
|
||||||
|
|
||||||
Review protocol: the developer reads one iteration, approves or amends;
|
Review protocol: the developer reads one iteration, approves or amends;
|
||||||
the next starts only after approval. Each iteration is an unsplittable
|
the next starts only after approval. Each iteration is an unsplittable
|
||||||
|
|
@ -104,8 +110,8 @@ list, and a pointer to the plan document that already sequences its tasks.
|
||||||
**parked** with spec + plan ready on branch `library-internal`. The
|
**parked** with spec + plan ready on branch `library-internal`. The
|
||||||
v1 slices landed 2026-08-20 (`just web-app` 21/0 after polish, auth,
|
v1 slices landed 2026-08-20 (`just web-app` 21/0 after polish, auth,
|
||||||
form, multipart). Implementation order for everything still pending:
|
form, multipart). Implementation order for everything still pending:
|
||||||
**18 (spec approved)** → 9c/9d → 9e → 8 → 9f → 11 (+ h2c unparks) →
|
**18 (spec approved)** → 20/21 → 22 → 8 → 23 → 11 (+ h2c unparks) →
|
||||||
10 → 12 → 9g → 14 → 13 + parked drain; 17 parked, slots anywhere after
|
10 → 12 → 27 → 14 → 13 + parked drain; 17 parked, slots anywhere after
|
||||||
16 on directive. The iterations table above carries this order
|
16 on directive. The iterations table above carries this order
|
||||||
row-by-row; edges live in
|
row-by-row; edges live in
|
||||||
[`docs/00-dependency-graph.md`](../../00-dependency-graph.md).
|
[`docs/00-dependency-graph.md`](../../00-dependency-graph.md).
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@
|
||||||
- **Gated by the benchmark (2026-08-15):** this is the "optimize
|
- **Gated by the benchmark (2026-08-15):** this is the "optimize
|
||||||
multithreading" lever of the performance arc — thread-per-core is a
|
multithreading" lever of the performance arc — thread-per-core is a
|
||||||
throughput/scale claim, so landing it means re-running iteration
|
throughput/scale claim, so landing it means re-running iteration
|
||||||
[9e](refine/09e-durability-throughput-scale.md) at the connection/concurrency
|
[22](refine/22-durability-throughput-scale.md) at the connection/concurrency
|
||||||
scale it unlocks and recording the before/after delta. It is also where
|
scale it unlocks and recording the before/after delta. It is also where
|
||||||
the io_uring write path ([9f](refine/09f-io-uring-commit.md)) gets a thread to
|
the io_uring write path ([23](refine/23-io-uring-commit.md)) gets a thread to
|
||||||
overlap durability against.
|
overlap durability against.
|
||||||
|
|
||||||
## Proposed Solution
|
## Proposed Solution
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ worked around.
|
||||||
writes. Honest limit stated everywhere it matters: an idle server
|
writes. Honest limit stated everywhere it matters: an idle server
|
||||||
drains nothing until the next request arrives. Fibers (11) later
|
drains nothing until the next request arrives. Fibers (11) later
|
||||||
replaces the scheduler; the queue table and job shape stay.
|
replaces the scheduler; the queue table and job shape stay.
|
||||||
(Rejected for v1: a second worker process over 9c attach — real
|
(Rejected for v1: a second worker process over 20 attach — real
|
||||||
parallelism but blocks on finishing 9c; parking jobs entirely — the
|
parallelism but blocks on finishing 20; parking jobs entirely — the
|
||||||
queue-plus-drain is useful today.)
|
queue-plus-drain is useful today.)
|
||||||
3. **`transaction { }` ships in this iteration.** Language block deferring
|
3. **`transaction { }` ships in this iteration.** Language block deferring
|
||||||
`wal_commit` to block end; a trap unwinding out of the block aborts the
|
`wal_commit` to block end; a trap unwinding out of the block aborts the
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Iteration 20 — the missing scalar types: Float and Bytes
|
# Iteration 19 — the missing scalar types: Float and Bytes
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
||||||
|
|
@ -30,9 +30,9 @@ work, but it blurs every json/interpolation boundary Text has.
|
||||||
and `trunc(f)` are the explicit bridges.
|
and `trunc(f)` are the explicit bridges.
|
||||||
3. **Bytes ships alongside**: a distinct binary scalar (len/byte_at/
|
3. **Bytes ships alongside**: a distinct binary scalar (len/byte_at/
|
||||||
slice/compare; base64 and future digests return it; net reads can
|
slice/compare; base64 and future digests return it; net reads can
|
||||||
fill it) — Text goes back to meaning text; iteration 19 and the
|
fill it) — Text goes back to meaning text; iteration 24 and the
|
||||||
crypto fork inherit a clean carrier.
|
crypto fork inherit a clean carrier.
|
||||||
4. **Recorded as iteration 20; spec before code.**
|
4. **Recorded as iteration 19; spec before code.**
|
||||||
|
|
||||||
## Surveyed and deliberately NOT added (the rest of the missing-type list)
|
## Surveyed and deliberately NOT added (the rest of the missing-type list)
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Iteration 10 — HTTP service layer
|
# Iteration 25 — HTTP service layer
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Iteration 12 — blue-green in-runtime deployment
|
# Iteration 26 — blue-green in-runtime deployment
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
- **Gated by the benchmark (2026-08-15):** this is the "implement garbage
|
- **Gated by the benchmark (2026-08-15):** this is the "implement garbage
|
||||||
collection" lever of the performance arc — tri-color mark-sweep replacing
|
collection" lever of the performance arc — tri-color mark-sweep replacing
|
||||||
RC changes the write path's tail latency, so landing it means re-running
|
RC changes the write path's tail latency, so landing it means re-running
|
||||||
iteration [9e](../refine/09e-durability-throughput-scale.md) and recording the
|
iteration [22](../refine/22-durability-throughput-scale.md) and recording the
|
||||||
delta (does tracing help or hurt p99 under write load?).
|
delta (does tracing help or hurt p99 under write load?).
|
||||||
- **Constraint added by the database track (2026-08-15):** a GC-managed value
|
- **Constraint added by the database track (2026-08-15):** a GC-managed value
|
||||||
in a `@table` field is a compile error (the engine/heap bulkhead — 9b
|
in a `@table` field is a compile error (the engine/heap bulkhead — 9b
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
>
|
>
|
||||||
> **Inserted 2026-08-11**, hence `9b` rather than a renumber. It follows
|
> **Inserted 2026-08-11**, hence `9b` rather than a renumber. It follows
|
||||||
> iteration 9 because a query surface needs tables that actually execute, and
|
> iteration 9 because a query surface needs tables that actually execute, and
|
||||||
> precedes iteration 10 because `service` blocks will want to return query
|
> precedes iteration 25 because `service` blocks will want to return query
|
||||||
> results.
|
> results.
|
||||||
>
|
>
|
||||||
> **Spec exists (2026-08-15):**
|
> **Spec exists (2026-08-15):**
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
HTTP/UI track's; a query that pushes updates is a later composition of the
|
HTTP/UI track's; a query that pushes updates is a later composition of the
|
||||||
two.
|
two.
|
||||||
- Migrations. Changing a `@table` class's shape is the blue-green spec's
|
- Migrations. Changing a `@table` class's shape is the blue-green spec's
|
||||||
additive-only differ (iteration 12), not this iteration's problem.
|
additive-only differ (iteration 26), not this iteration's problem.
|
||||||
- Query optimisation beyond index selection. A cost-based planner is a
|
- Query optimisation beyond index selection. A cost-based planner is a
|
||||||
separate, much later concern; this iteration must only prove that declared
|
separate, much later concern; this iteration must only prove that declared
|
||||||
indexes are used.
|
indexes are used.
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
> like any dependency (iteration 15 is the prerequisite). TLS terminates at a
|
> like any dependency (iteration 15 is the prerequisite). TLS terminates at a
|
||||||
> reverse proxy — browsers get TLS+ALPN+h2 from nginx/caddy while the
|
> reverse proxy — browsers get TLS+ALPN+h2 from nginx/caddy while the
|
||||||
> framework speaks HTTP/1.1 keep-alive behind it, so no TLS exists anywhere
|
> framework speaks HTTP/1.1 keep-alive behind it, so no TLS exists anywhere
|
||||||
> in the toolchain. h2c is the parked successor (after iterations 8/9f/11,
|
> in the toolchain. h2c is the parked successor (after iterations 8/23/11,
|
||||||
> when multiplexing has a scheduler to pay off on).
|
> when multiplexing has a scheduler to pay off on).
|
||||||
>
|
>
|
||||||
> **Spec exists:** [`2026-08-18-web-framework-design.md`](../../../superpowers/specs/2026-08-18-web-framework-design.md)
|
> **Spec exists:** [`2026-08-18-web-framework-design.md`](../../../superpowers/specs/2026-08-18-web-framework-design.md)
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
(concurrency arrives underneath via iterations 8/11), no chunked encoding,
|
(concurrency arrives underneath via iterations 8/11), no chunked encoding,
|
||||||
no WebSockets, JSON-first (no templates — the removed UI track stays
|
no WebSockets, JSON-first (no templates — the removed UI track stays
|
||||||
removed).
|
removed).
|
||||||
- **Relationship to iteration 10 recorded in both**: `service` blocks later
|
- **Relationship to iteration 25 recorded in both**: `service` blocks later
|
||||||
*lower onto this library* — compiler sugar over the same router, never a
|
*lower onto this library* — compiler sugar over the same router, never a
|
||||||
rival stack.
|
rival stack.
|
||||||
|
|
||||||
|
|
@ -111,9 +111,9 @@
|
||||||
## Out Of Scope
|
## Out Of Scope
|
||||||
|
|
||||||
TLS in the toolchain (proxy-terminated by decision); HTTP/2 + the
|
TLS in the toolchain (proxy-terminated by decision); HTTP/2 + the
|
||||||
bytes/buffer type (parked to the h2c successor, after 8/9f/11); chunked
|
bytes/buffer type (parked to the h2c successor, after 8/23/11); chunked
|
||||||
transfer encoding; WebSockets/SSE; templates/SSR; multipart uploads;
|
transfer encoding; WebSockets/SSE; templates/SSR; multipart uploads;
|
||||||
performance work beyond the soak's flatness gate (benchmarks belong to 9e's
|
performance work beyond the soak's flatness gate (benchmarks belong to 22's
|
||||||
measurement backbone).
|
measurement backbone).
|
||||||
|
|
||||||
## Proposed Solution
|
## Proposed Solution
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
- **Given** a parked fiber at shard shutdown,
|
- **Given** a parked fiber at shard shutdown,
|
||||||
- **when** the shard unwinds it,
|
- **when** the shard unwinds it,
|
||||||
- **then** every drop map runs (ASan zero leaks) — parked fibers die
|
- **then** every drop map runs (ASan zero leaks) — parked fibers die
|
||||||
as cleanly as trapped ones. (Iteration 12's blue-green drain reuses
|
as cleanly as trapped ones. (Iteration 26's blue-green drain reuses
|
||||||
exactly this unwind path.)
|
exactly this unwind path.)
|
||||||
- What to achieve?
|
- What to achieve?
|
||||||
- **Given** `@gc` objects referenced only from a parked fiber's frames,
|
- **Given** `@gc` objects referenced only from a parked fiber's frames,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
# Iteration 9c — cross-program tables: attach to a running program's database
|
# Iteration 20 — cross-program tables: attach to a running program's database
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
>
|
>
|
||||||
> **Inserted 2026-08-15**, hence `9c`. It follows 9b because a program
|
> **Inserted 2026-08-15**, hence `20`. It follows 9b because a program
|
||||||
> attaching to another's tables wants the same typed statements and queries
|
> attaching to another's tables wants the same typed statements and queries
|
||||||
> the owner has — a surface that must exist before it can be shared — and
|
> the owner has — a surface that must exist before it can be shared — and
|
||||||
> precedes iteration 10 because HTTP is the *external* face of a program;
|
> precedes iteration 25 because HTTP is the *external* face of a program;
|
||||||
> this iteration is the *writeonce-native* face, program to program on the
|
> this iteration is the *writeonce-native* face, program to program on the
|
||||||
> same machine.
|
> same machine.
|
||||||
>
|
>
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
## Out Of Scope
|
## Out Of Scope
|
||||||
|
|
||||||
- **Remote machines.** The IPC string names a local channel; cross-host
|
- **Remote machines.** The IPC string names a local channel; cross-host
|
||||||
access is the HTTP/service layer's job (iteration 10) or a much later
|
access is the HTTP/service layer's job (iteration 25) or a much later
|
||||||
network protocol. Same-machine is what "attach" means here.
|
network protocol. Same-machine is what "attach" means here.
|
||||||
- **B caching A's rows.** Every read crosses the channel; a client-side
|
- **B caching A's rows.** Every read crosses the channel; a client-side
|
||||||
cache (and its invalidation) is a later performance iteration, if ever.
|
cache (and its invalidation) is a later performance iteration, if ever.
|
||||||
|
|
@ -84,7 +84,7 @@
|
||||||
subscription registry later (the client-api phase doc already sketches
|
subscription registry later (the client-api phase doc already sketches
|
||||||
the wire shape).
|
the wire shape).
|
||||||
- **Schema migration while attached** — a blue-green swap in A while B
|
- **Schema migration while attached** — a blue-green swap in A while B
|
||||||
holds an attachment is iteration 12's compatibility problem; this
|
holds an attachment is iteration 26's compatibility problem; this
|
||||||
iteration may simply drop attachments on swap.
|
iteration may simply drop attachments on swap.
|
||||||
|
|
||||||
## Info
|
## Info
|
||||||
|
|
@ -136,8 +136,8 @@ read or read+write (per-table refinement deferred until a workload needs
|
||||||
it), and the registration is A's manifest so a grant is a config change +
|
it), and the registration is A's manifest so a grant is a config change +
|
||||||
restart, not an API. **Superseded as the end state (2026-08-15):**
|
restart, not an API. **Superseded as the end state (2026-08-15):**
|
||||||
identity is a keypair and grants name public keys — iteration
|
identity is a keypair and grants name public keys — iteration
|
||||||
[9d](09d-keypair-attach-auth.md) owns that; the uid check is only this
|
[21](21-keypair-attach-auth.md) owns that; the uid check is only this
|
||||||
iteration's bootstrap and must be flagged pre-9d wherever it ships.
|
iteration's bootstrap and must be flagged pre-21 wherever it ships.
|
||||||
|
|
||||||
**4. What does B's statement actually block on?** B's insert crosses the
|
**4. What does B's statement actually block on?** B's insert crosses the
|
||||||
channel, executes in A (RAM + WAL + fsync), and acknowledges back — a
|
channel, executes in A (RAM + WAL + fsync), and acknowledges back — a
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
# Iteration 9d — keypair authentication for cross-program attach
|
# Iteration 21 — keypair authentication for cross-program attach
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
>
|
>
|
||||||
> **Inserted 2026-08-15.** Promotes iteration 9c's identity fork (Info,
|
> **Inserted 2026-08-15.** Promotes iteration 20's identity fork (Info,
|
||||||
> fork 3) to its own iteration: the name + unix-uid lean is the milestone
|
> fork 3) to its own iteration: the name + unix-uid lean is the milestone
|
||||||
> bootstrap, and THIS is what replaces it — program identity is a keypair,
|
> bootstrap, and THIS is what replaces it — program identity is a keypair,
|
||||||
> and an attachment is granted to a public key, not to a process that
|
> and an attachment is granted to a public key, not to a process that
|
||||||
> happens to share a uid. It follows 9c (there is nothing to authenticate
|
> happens to share a uid. It follows 20 (there is nothing to authenticate
|
||||||
> until attach exists) and stays same-machine; the same handshake is what
|
> until attach exists) and stays same-machine; the same handshake is what
|
||||||
> a future remote channel would reuse, which is the point of doing it
|
> a future remote channel would reuse, which is the point of doing it
|
||||||
> properly now.
|
> properly now.
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
manifest) and a public key it can print/export. Identity stops being
|
manifest) and a public key it can print/export. Identity stops being
|
||||||
"whoever reached the socket first with the right uid".
|
"whoever reached the socket first with the right uid".
|
||||||
- **Grants name public keys.** A's `[share]` registers a client by its
|
- **Grants name public keys.** A's `[share]` registers a client by its
|
||||||
public key (fingerprint), with rights exactly as 9c defined them; B's
|
public key (fingerprint), with rights exactly as 20 defined them; B's
|
||||||
`[connect.a]` **pins A's public key** beside the IPC string. Both sides
|
`[connect.a]` **pins A's public key** beside the IPC string. Both sides
|
||||||
authenticate: A proves it is A before B sends a byte of intent, B proves
|
authenticate: A proves it is A before B sends a byte of intent, B proves
|
||||||
it is B before A executes a statement.
|
it is B before A executes a statement.
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
read+write, and B's `[connect.a]` pinning A's public key,
|
read+write, and B's `[connect.a]` pinning A's public key,
|
||||||
- **when** B attaches,
|
- **when** B attaches,
|
||||||
- **then** the mutual handshake completes, the attachment carries B's
|
- **then** the mutual handshake completes, the attachment carries B's
|
||||||
granted rights, and every 9c acceptance behavior (statements, traps,
|
granted rights, and every 20 acceptance behavior (statements, traps,
|
||||||
refusals) holds unchanged on top of it.
|
refusals) holds unchanged on top of it.
|
||||||
- What to achieve?
|
- What to achieve?
|
||||||
- **Given** a client presenting a keypair A never registered,
|
- **Given** a client presenting a keypair A never registered,
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
client sees the catchable authentication trap, and A logs the offered
|
client sees the catchable authentication trap, and A logs the offered
|
||||||
fingerprint (so granting it is a copy-paste, not an investigation).
|
fingerprint (so granting it is a copy-paste, not an investigation).
|
||||||
- What to achieve?
|
- What to achieve?
|
||||||
- **Given** a same-uid process (the 9c bootstrap's whole trust basis)
|
- **Given** a same-uid process (the 20 bootstrap's whole trust basis)
|
||||||
presenting no key or the wrong key,
|
presenting no key or the wrong key,
|
||||||
- **when** it attempts to attach,
|
- **when** it attempts to attach,
|
||||||
- **then** it is refused — proving the uid check has been superseded,
|
- **then** it is refused — proving the uid check has been superseded,
|
||||||
|
|
@ -128,11 +128,11 @@ authorization input.
|
||||||
## Proposed Solution
|
## Proposed Solution
|
||||||
|
|
||||||
- **Brainstorm the spec** settling the four forks, then fold the plan into
|
- **Brainstorm the spec** settling the four forks, then fold the plan into
|
||||||
9c's implementation plan as its authentication tasks — one plan, because
|
20's implementation plan as its authentication tasks — one plan, because
|
||||||
9c without 9d ships a placeholder identity and 9d without 9c has nothing
|
20 without 21 ships a placeholder identity and 21 without 20 has nothing
|
||||||
to authenticate. The 9c milestone may still land first with the uid
|
to authenticate. The 20 milestone may still land first with the uid
|
||||||
bootstrap, flagged loudly as pre-9d.
|
bootstrap, flagged loudly as pre-21.
|
||||||
- **Acceptance extends the 9c workload**: the employee-A /
|
- **Acceptance extends the 20 workload**: the employee-A /
|
||||||
employee-list-B pair (`docs/examples/employee-list`, pre-authored
|
employee-list-B pair (`docs/examples/employee-list`, pre-authored
|
||||||
2026-08-15) carries the key exchange in both manifests — A's
|
2026-08-15) carries the key exchange in both manifests — A's
|
||||||
`[[share.clients]]` names B's fingerprint, B's `[connect.employee]` pins
|
`[[share.clients]]` names B's fingerprint, B's `[connect.employee]` pins
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Iteration 9e — durability proof, throughput, and scale under load
|
# Iteration 22 — durability proof, throughput, and scale under load
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
> **Inserted 2026-08-15.** The measurement backbone. Everything after the
|
> **Inserted 2026-08-15.** The measurement backbone. Everything after the
|
||||||
> functional engine (9/9b) is an *optimization*, and an optimization without
|
> functional engine (9/9b) is an *optimization*, and an optimization without
|
||||||
> a number is a guess — this iteration is the number. It comes before the
|
> a number is a guess — this iteration is the number. It comes before the
|
||||||
> optimization iterations (7b GC, 8 shard-actor, 9f io_uring) reopen for
|
> optimization iterations (7b GC, 8 shard-actor, 23 io_uring) reopen for
|
||||||
> performance work, because each of those must be gated by re-running THIS
|
> performance work, because each of those must be gated by re-running THIS
|
||||||
> iteration's benchmark and showing the number moved the right way.
|
> iteration's benchmark and showing the number moved the right way.
|
||||||
>
|
>
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
a stated duration, with throughput and tail latency inside a stated budget
|
a stated duration, with throughput and tail latency inside a stated budget
|
||||||
and RSS flat (the log-watcher soak discipline, at database scale).
|
and RSS flat (the log-watcher soak discipline, at database scale).
|
||||||
- **The benchmark is the contract every later optimization signs.** 7b (GC),
|
- **The benchmark is the contract every later optimization signs.** 7b (GC),
|
||||||
8 (shard-actor threads), and 9f (io_uring) each re-run this and record the
|
8 (shard-actor threads), and 23 (io_uring) each re-run this and record the
|
||||||
before/after — no optimization lands without a measured delta.
|
before/after — no optimization lands without a measured delta.
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
@ -58,14 +58,14 @@
|
||||||
a durable configuration — a kill mid-load followed by replay loses no
|
a durable configuration — a kill mid-load followed by replay loses no
|
||||||
acknowledged write.
|
acknowledged write.
|
||||||
- What to achieve?
|
- What to achieve?
|
||||||
- **Given** any later optimization iteration (7b, 8, 9f),
|
- **Given** any later optimization iteration (7b, 8, 23),
|
||||||
- **when** it claims a speedup,
|
- **when** it claims a speedup,
|
||||||
- **then** this benchmark's before/after numbers are in that iteration's
|
- **then** this benchmark's before/after numbers are in that iteration's
|
||||||
record, and a claim with no measured delta is not accepted.
|
record, and a claim with no measured delta is not accepted.
|
||||||
|
|
||||||
## Out Of Scope
|
## Out Of Scope
|
||||||
|
|
||||||
- **The optimizations themselves.** This iteration MEASURES; 7b/8/9f change.
|
- **The optimizations themselves.** This iteration MEASURES; 7b/8/23 change.
|
||||||
A single-thread RAM-authoritative baseline is a legitimate first number —
|
A single-thread RAM-authoritative baseline is a legitimate first number —
|
||||||
the point is to have one before anyone tunes.
|
the point is to have one before anyone tunes.
|
||||||
- **Distributed / multi-machine load.** Same-machine, one process (or one
|
- **Distributed / multi-machine load.** Same-machine, one process (or one
|
||||||
|
|
@ -114,7 +114,7 @@ a bounded concurrent read/write workload here; connection scale deferred to
|
||||||
the honest number for a durable workload; RAM-only (no `WO_DATA`) measures
|
the honest number for a durable workload; RAM-only (no `WO_DATA`) measures
|
||||||
the engine's ceiling. Both matter and mean different things. Leaning:
|
the engine's ceiling. Both matter and mean different things. Leaning:
|
||||||
publish both, labeled — durable is the number an operator plans against, and
|
publish both, labeled — durable is the number an operator plans against, and
|
||||||
the gap between them is precisely what iteration 9f (io_uring group-commit)
|
the gap between them is precisely what iteration 23 (io_uring group-commit)
|
||||||
exists to close.
|
exists to close.
|
||||||
|
|
||||||
## Proposed Solution
|
## Proposed Solution
|
||||||
|
|
@ -123,13 +123,13 @@ exists to close.
|
||||||
task is the harness and the baseline file, because nothing downstream means
|
task is the harness and the baseline file, because nothing downstream means
|
||||||
anything without them.
|
anything without them.
|
||||||
- **Sequence the whole performance arc around this iteration:**
|
- **Sequence the whole performance arc around this iteration:**
|
||||||
1. 9b lands → employee compiles and runs → **9e restart-persistence** and
|
1. 9b lands → employee compiles and runs → **22 restart-persistence** and
|
||||||
**9e baseline benchmark** (single-thread, both durable and RAM-only).
|
**22 baseline benchmark** (single-thread, both durable and RAM-only).
|
||||||
2. **7b** (inferred GC + mark-sweep) → re-run 9e, record the delta (does
|
2. **7b** (inferred GC + mark-sweep) → re-run 22, record the delta (does
|
||||||
tracing change the write path's tail latency?).
|
tracing change the write path's tail latency?).
|
||||||
3. **8** (shard-actor, thread-per-core) → re-run 9e at the connection/
|
3. **8** (shard-actor, thread-per-core) → re-run 22 at the connection/
|
||||||
concurrency scale it unlocks, record the delta.
|
concurrency scale it unlocks, record the delta.
|
||||||
4. **9f** (io_uring group-commit) → re-run 9e's durable write number, record
|
4. **23** (io_uring group-commit) → re-run 22's durable write number, record
|
||||||
the delta against the fsync-per-commit baseline — the payoff.
|
the delta against the fsync-per-commit baseline — the payoff.
|
||||||
- The benchmark harness and its baseline live under `bench/` (or the existing
|
- The benchmark harness and its baseline live under `bench/` (or the existing
|
||||||
`runtime/bench/`), and `just` gets a `db-bench` recipe kept off the fast
|
`runtime/bench/`), and `just` gets a `db-bench` recipe kept off the fast
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
# Iteration 9f — io_uring group-commit write path
|
# Iteration 23 — io_uring group-commit write path
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
>
|
>
|
||||||
> **Inserted 2026-08-15.** The write-path optimization, and deliberately the
|
> **Inserted 2026-08-15.** The write-path optimization, and deliberately the
|
||||||
> LAST database performance iteration: it only earns its complexity once
|
> LAST database performance iteration: it only earns its complexity once
|
||||||
> there is a measured fsync-per-commit baseline to beat (iteration 9e) and a
|
> there is a measured fsync-per-commit baseline to beat (iteration 22) and a
|
||||||
> multithreaded runtime to overlap against (iteration 8). Doing it earlier
|
> multithreaded runtime to overlap against (iteration 8). Doing it earlier
|
||||||
> would optimize a number nobody had measured, against a runtime that
|
> would optimize a number nobody had measured, against a runtime that
|
||||||
> couldn't use it.
|
> couldn't use it.
|
||||||
|
|
@ -24,21 +24,21 @@
|
||||||
statements while the ring drains, instead of blocking one thread on one
|
statements while the ring drains, instead of blocking one thread on one
|
||||||
fdatasync — the multithreading the throughput number has been waiting for.
|
fdatasync — the multithreading the throughput number has been waiting for.
|
||||||
- **Keep the durability promise byte-for-byte.** Every guarantee iterations 9
|
- **Keep the durability promise byte-for-byte.** Every guarantee iterations 9
|
||||||
and 9e proved — replay-whole-or-not-at-all, torn-tail drop, no
|
and 22 proved — replay-whole-or-not-at-all, torn-tail drop, no
|
||||||
acknowledged write ever lost — holds identically; io_uring changes HOW the
|
acknowledged write ever lost — holds identically; io_uring changes HOW the
|
||||||
bytes reach the platter, never WHETHER an ack means durable.
|
bytes reach the platter, never WHETHER an ack means durable.
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- What to achieve?
|
- What to achieve?
|
||||||
- **Given** the io_uring write path under the iteration-9e crash battery
|
- **Given** the io_uring write path under the iteration-22 crash battery
|
||||||
(concurrent writers, kill -9 mid-stream, reboot, replay),
|
(concurrent writers, kill -9 mid-stream, reboot, replay),
|
||||||
- **when** it runs,
|
- **when** it runs,
|
||||||
- **then** every acknowledged write is present after replay and no
|
- **then** every acknowledged write is present after replay and no
|
||||||
unacknowledged partial write is ever visible — the exact result the
|
unacknowledged partial write is ever visible — the exact result the
|
||||||
fsync path gives, so durability is provably unchanged.
|
fsync path gives, so durability is provably unchanged.
|
||||||
- What to achieve?
|
- What to achieve?
|
||||||
- **Given** the iteration-9e durable write benchmark,
|
- **Given** the iteration-22 durable write benchmark,
|
||||||
- **when** it is run on the fsync-per-commit path and then the io_uring
|
- **when** it is run on the fsync-per-commit path and then the io_uring
|
||||||
group-commit path on the same machine,
|
group-commit path on the same machine,
|
||||||
- **then** the io_uring path's write throughput is materially higher and
|
- **then** the io_uring path's write throughput is materially higher and
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
read path. This is a write-durability optimization, full stop.
|
read path. This is a write-durability optimization, full stop.
|
||||||
- **Registered buffers / fixed files / SQPOLL tuning** beyond what the
|
- **Registered buffers / fixed files / SQPOLL tuning** beyond what the
|
||||||
benchmark shows is worth it. Start with the plain submit/complete model;
|
benchmark shows is worth it. Start with the plain submit/complete model;
|
||||||
add ring features only when 9e's number says a specific one pays.
|
add ring features only when 22's number says a specific one pays.
|
||||||
- **Replacing the WAL format or the commit contract.** The bytes on disk and
|
- **Replacing the WAL format or the commit contract.** The bytes on disk and
|
||||||
the meaning of an ack are iteration 9's; this changes the syscall, not the
|
the meaning of an ack are iteration 9's; this changes the syscall, not the
|
||||||
format.
|
format.
|
||||||
|
|
@ -78,7 +78,7 @@ wrap it behind the existing `wo_wal_commit` boundary (drop-in, the engine
|
||||||
never learns) or expose an async-commit primitive the shard scheduler drives
|
never learns) or expose an async-commit primitive the shard scheduler drives
|
||||||
(faster overlap, but couples the WAL to iteration 8's loop). Leaning:
|
(faster overlap, but couples the WAL to iteration 8's loop). Leaning:
|
||||||
drop-in behind `wo_wal_commit` first — it is the correctness-preserving
|
drop-in behind `wo_wal_commit` first — it is the correctness-preserving
|
||||||
step and 9e can measure it standalone — then an async variant only if 8's
|
step and 22 can measure it standalone — then an async variant only if 8's
|
||||||
scheduler shows the blocking boundary is the remaining bottleneck.
|
scheduler shows the blocking boundary is the remaining bottleneck.
|
||||||
|
|
||||||
**2. liburing or raw syscalls?** liburing is the ergonomic wrapper but is a
|
**2. liburing or raw syscalls?** liburing is the ergonomic wrapper but is a
|
||||||
|
|
@ -104,13 +104,13 @@ auto-probe is what production uses.
|
||||||
|
|
||||||
## Proposed Solution
|
## Proposed Solution
|
||||||
|
|
||||||
- **Brainstorm the spec** after iterations 8 and 9e exist — this iteration is
|
- **Brainstorm the spec** after iterations 8 and 22 exist — this iteration is
|
||||||
meaningless without a multithreaded runtime to overlap against and a
|
meaningless without a multithreaded runtime to overlap against and a
|
||||||
measured baseline to beat, and its plan's acceptance is literally "9e's
|
measured baseline to beat, and its plan's acceptance is literally "22's
|
||||||
durable number improved, 9e's crash battery still green, fsync fallback
|
durable number improved, 22's crash battery still green, fsync fallback
|
||||||
still correct".
|
still correct".
|
||||||
- Expected shape: a `wo_wal` write-mode switch (fsync vs uring), the raw ring
|
- Expected shape: a `wo_wal` write-mode switch (fsync vs uring), the raw ring
|
||||||
setup + submit/complete in `database/src/wal.c` (or a `wal_uring.c`
|
setup + submit/complete in `database/src/wal.c` (or a `wal_uring.c`
|
||||||
beside it), the startup probe + `WO_WAL_MODE` override, the binding doc's
|
beside it), the startup probe + `WO_WAL_MODE` override, the binding doc's
|
||||||
WAL section extended with the ring layout, and iteration 9e re-run on both
|
WAL section extended with the ring layout, and iteration 22 re-run on both
|
||||||
paths with the delta committed.
|
paths with the delta committed.
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Iteration 24 — chat: the WebSocket pub/sub driving workload
|
||||||
|
|
||||||
|
> Format: `product/story-iteration-template`. Part of
|
||||||
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
|
>
|
||||||
|
> **Inserted 2026-08-20** (concurrency-chain refinement): the 8+11 arc's
|
||||||
|
> driving workload, the role log-watcher played for iterations 3–7. Needs
|
||||||
|
> its spec AFTER the arc's — it lands at the arc's end and proves it.
|
||||||
|
|
||||||
|
## Why this iteration exists
|
||||||
|
|
||||||
|
Everything the framework ledger parks behind concurrency — WebSockets,
|
||||||
|
pub/sub, streaming, per-request cancellation, the keep-alive parking
|
||||||
|
retirement — needs a workload that actually exercises long-lived
|
||||||
|
connections and cross-shard broadcast, or the arc ships mechanism without
|
||||||
|
proof. Chat is the smallest honest such workload: rooms, N concurrent
|
||||||
|
clients, fan-out on every message, presence on connect/disconnect — one
|
||||||
|
binary, no broker.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- `docs/examples/chat`: rooms + broadcast + presence over WebSocket,
|
||||||
|
served by the framework through `[deps]` exactly as the web-app is.
|
||||||
|
- Framework grows the WS mechanism in pure `.wo`: the HTTP/1.1 upgrade
|
||||||
|
handshake (Sec-WebSocket-Accept needs SHA-1/base64 — the crypto-builtin
|
||||||
|
fork's first real consumer), frame parse/serialize (text, close, ping),
|
||||||
|
fiber-per-connection serving (iteration 11), and in-memory channels
|
||||||
|
whose delivery is cross-shard message send (iteration 8).
|
||||||
|
- The arc's acceptance teeth: 1k concurrent clients across shards,
|
||||||
|
broadcast latency measured, starvation-free under one hot room,
|
||||||
|
SIGTERM drains every connection cleanly.
|
||||||
|
|
||||||
|
## Acceptance Criteria (draft — the spec after the arc refines)
|
||||||
|
|
||||||
|
- **Given** two clients in one room on DIFFERENT shards, **when** one
|
||||||
|
sends, **then** the other receives the frame (cross-shard ownership-move
|
||||||
|
delivery), and a third client in another room receives nothing.
|
||||||
|
- **Given** 1k connected clients with one hot sender, **when** the
|
||||||
|
reduction budget preempts, **then** every room keeps making progress
|
||||||
|
(no starvation) on one OS thread per core, verified by TID.
|
||||||
|
- **Given** SIGTERM with clients connected, **when** the server drains,
|
||||||
|
**then** every connection gets a close frame, every fiber unwinds its
|
||||||
|
drop maps (ASan zero leaks), and the process exits 0.
|
||||||
|
- **Given** the web-app running beside chat features, **when** the
|
||||||
|
standing gates run, **then** nothing regresses — HTTP and WS share the
|
||||||
|
serve loop honestly.
|
||||||
|
|
||||||
|
## Out Of Scope
|
||||||
|
|
||||||
|
Message persistence/history (a `@table` an app adds if it wants — not the
|
||||||
|
workload's point); auth beyond the existing bearer mechanism; permessage
|
||||||
|
compression; binary frames beyond echo coverage; wss (TLS stays at the
|
||||||
|
proxy — the proxy story extends to WS pass-through, documented).
|
||||||
|
|
||||||
|
## Info
|
||||||
|
|
||||||
|
- Dependencies: the 8+11 arc (fibers + cross-shard send), the crypto
|
||||||
|
builtins fork (SHA-1 for the upgrade handshake — note: the ledger's
|
||||||
|
crypto slice lists SHA-256/512; the WS handshake specifically needs
|
||||||
|
SHA-1, so the builtin set must include it), and the framework's parse
|
||||||
|
seam (upgrade is an HTTP request until it isn't).
|
||||||
|
- Unparks on landing: the framework ledger's WebSocket/pub-sub rows and
|
||||||
|
the iteration-18 rejection note ("pub/sub REJECTED until 8/11").
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
|
||||||
|
Brainstorm → spec → plan after the arc's spec exists; the arc's plan and
|
||||||
|
this iteration's are written against each other (the arc names chat as
|
||||||
|
its acceptance, chat names the arc as its substrate).
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Iteration 9g — query grammar, driven by real embedded-DB corpora
|
# Iteration 27 — query grammar, driven by real embedded-DB corpora
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Iteration 14 — skillhost: a host-shaped workload, and the capability gaps it exposes
|
# Iteration 28 — skillhost: a host-shaped workload, and the capability gaps it exposes
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
## What is already expressible (verified 2026-08-16)
|
## What is already expressible (verified 2026-08-16)
|
||||||
|
|
||||||
- **The skill catalog** — `@table` with the query surface already exceeds
|
- **The skill catalog** — `@table` with the query surface already exceeds
|
||||||
skillhost's in-memory SQLite `skills` table; iteration 9g's
|
skillhost's in-memory SQLite `skills` table; iteration 27's
|
||||||
`docs/examples/skill-catalog` is literally this table, running. (Or a plain
|
`docs/examples/skill-catalog` is literally this table, running. (Or a plain
|
||||||
`map`/`multi` would do — the catalog is a lookup cache, not persistence.)
|
`map`/`multi` would do — the catalog is a lookup cache, not persistence.)
|
||||||
- **Discovery** — `fs.exists`/`fs.list` (one level) + `fs.read_all` walk
|
- **Discovery** — `fs.exists`/`fs.list` (one level) + `fs.read_all` walk
|
||||||
|
|
@ -65,7 +65,7 @@ Two directions, and they are a real fork, not a detail:
|
||||||
- **FFI as a language capability** — a way to declare and call C functions
|
- **FFI as a language capability** — a way to declare and call C functions
|
||||||
from writeonce. This is a large, doctrine-level addition (the runtime is
|
from writeonce. This is a large, doctrine-level addition (the runtime is
|
||||||
libc-only by principle; the one sanctioned exception so far is the vendored
|
libc-only by principle; the one sanctioned exception so far is the vendored
|
||||||
Ed25519, 9d). FFI would reopen the dependency-sprawl question the whole
|
Ed25519, 21). FFI would reopen the dependency-sprawl question the whole
|
||||||
project is built to avoid. Likely its own spec, likely contested.
|
project is built to avoid. Likely its own spec, likely contested.
|
||||||
- **Out-of-process model, no FFI** — drive a llama.cpp binary via `proc`
|
- **Out-of-process model, no FFI** — drive a llama.cpp binary via `proc`
|
||||||
(`llama-cli`) or `llama-server` over `net` + `json` (it accepts a GBNF
|
(`llama-cli`) or `llama-server` over `net` + `json` (it accepts a GBNF
|
||||||
|
|
@ -194,6 +194,6 @@ then B and the partials narrow the gap to skillhost's real behavior.
|
||||||
exactly the open gaps (A: out-of-process model, B: socket not stdio, C:
|
exactly the open gaps (A: out-of-process model, B: socket not stdio, C:
|
||||||
bounded once its iteration lands, plus the fs partials) — the list is the
|
bounded once its iteration lands, plus the fs partials) — the list is the
|
||||||
iteration's own scoreboard.
|
iteration's own scoreboard.
|
||||||
- Reuse iteration 9g's `skill-catalog` as the catalog layer, log-watcher's
|
- Reuse iteration 27's `skill-catalog` as the catalog layer, log-watcher's
|
||||||
MCP mode as the transport skeleton, and the systems stdlib for discovery
|
MCP mode as the transport skeleton, and the systems stdlib for discovery
|
||||||
and execution.
|
and execution.
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Iteration 13 — compile-time metaprogramming (derive from the class table)
|
# Iteration 29 — compile-time metaprogramming (derive from the class table)
|
||||||
|
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
> [Story — one language, one runtime, one database, one binary](../00-story.md).
|
||||||
|
|
@ -99,7 +99,7 @@ rather than against it.
|
||||||
- **Monomorphized generics as a general feature.** Per-type generation here is
|
- **Monomorphized generics as a general feature.** Per-type generation here is
|
||||||
specific to the derive set, not a general generics engine.
|
specific to the derive set, not a general generics engine.
|
||||||
- **Deriving across the attach channel** — a client generating an encoder over
|
- **Deriving across the attach channel** — a client generating an encoder over
|
||||||
the owner's types (iterations 9c/9d). Composes later; the class-table
|
the owner's types (iterations 20/21). Composes later; the class-table
|
||||||
metadata already crosses the channel's schema handshake, so the pieces are
|
metadata already crosses the channel's schema handshake, so the pieces are
|
||||||
in place, but it is not this iteration's problem.
|
in place, but it is not this iteration's problem.
|
||||||
- **Reopening principle 13 in any form.** If a derive appears to need runtime
|
- **Reopening principle 13 in any form.** If a derive appears to need runtime
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# HTTP Service Layer Implementation Plan
|
# HTTP Service Layer Implementation Plan
|
||||||
|
|
||||||
> **Status: ⬜ pending** (story iteration 10) — `service` blocks route to VM methods; REST parity with the shipped Rust Stage 2 runtime. Board: [00-status.md](../../00-status.md)
|
> **Status: ⬜ pending** (story iteration 25) — `service` blocks route to VM methods; REST parity with the shipped Rust Stage 2 runtime. Board: [00-status.md](../../00-status.md)
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
# Iteration 18 — framework v2 (transaction{} + cache/flags/jobs): implementation plan
|
||||||
|
|
||||||
|
> **Status: ready to execute (2026-08-20).** Board:
|
||||||
|
> [docs/00-status.md](../../00-status.md).
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use
|
||||||
|
> superpowers:subagent-driven-development (recommended) or
|
||||||
|
> superpowers:executing-plans to implement this plan task-by-task. Steps
|
||||||
|
> use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
>
|
||||||
|
> **Style rule (user convention):** concept, reason, and required behavior
|
||||||
|
> in words plus verification commands only — no implementation or test
|
||||||
|
> code blocks; the executor writes the code.
|
||||||
|
|
||||||
|
**Goal:** `transaction { }` makes multi-statement writes one WAL commit
|
||||||
|
(trap = abort), and three framework pieces ride the single process:
|
||||||
|
a TTL cache, `@table` feature flags with a cached read, and a durable
|
||||||
|
`@table` job queue drained in-process — the web-app proves the headline
|
||||||
|
(order + job enqueue, ONE commit, SIGKILL-survivable).
|
||||||
|
|
||||||
|
**Architecture:** the engine already stages WAL batches
|
||||||
|
(`wal_append_*` → `wo_wal_commit`, per-statement today in
|
||||||
|
`database/src/db.c`'s three write cases); a transaction defers the commit
|
||||||
|
and keeps an undo log for RAM. The VM learns two internal builtins and a
|
||||||
|
transaction-flagged catch frame; the parser one keyword. Cache/flags/jobs
|
||||||
|
are pure `.wo` in the framework plus one serve-loop seam. No new opcodes,
|
||||||
|
no `.wob` version bump, GC untouched.
|
||||||
|
|
||||||
|
**Tech Stack:** C11 libc-only (engine + VM), OCaml stdlib-only (`woc`),
|
||||||
|
pure `.wo` (framework), bash gates.
|
||||||
|
|
||||||
|
**Spec:** [`../specs/2026-08-20-memory-db-features-design.md`](../specs/2026-08-20-memory-db-features-design.md)
|
||||||
|
(approved 2026-08-20, normative). Story:
|
||||||
|
[`18-memory-db-features.md`](../../stories/language-runtime-database/hold/18-memory-db-features.md).
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Branch `framework-v2` off master; commits local only, never push.
|
||||||
|
- Exit/trap doctrine unchanged: WO-E110 nested `transaction` (compile),
|
||||||
|
WO_T_DB "nested transaction" (dynamic, through a call); commit failure
|
||||||
|
is the existing WO_T_IO shape with RAM never ahead of disk.
|
||||||
|
- Gates that must stay green after every task: `just woc-test`,
|
||||||
|
`just oop-e2e`, `just deps-accept`, `just web-app`, `just log-watcher`,
|
||||||
|
`just employee` — plus the oop-accept ASan clause for anything the VM
|
||||||
|
touches.
|
||||||
|
- Framework tables carry the `wf_` prefix; policy stays app-side.
|
||||||
|
- `time.now` is wall-clock MILLISECONDS everywhere in this iteration.
|
||||||
|
|
||||||
|
## Spec deviations, disclosed up front
|
||||||
|
|
||||||
|
1. **`return`/`break`/`continue` crossing a `transaction { }` boundary is
|
||||||
|
rejected at compile time (new WO-E112).** The spec is silent on early
|
||||||
|
exit; commit-on-return vs abort-on-return is exactly the ambiguity a
|
||||||
|
v1 must not guess at. A later iteration may define it; today the block
|
||||||
|
has one entrance and one exit (a TRAP still aborts — that path is
|
||||||
|
defined).
|
||||||
|
2. **The cache exposes a time-injected seam** (`get_at`/`put_at` taking a
|
||||||
|
now-milliseconds argument, with `get`/`put` sugar reading `time.now`)
|
||||||
|
so the corpus fixture injects stamps instead of sleeping — the spec's
|
||||||
|
"stamps injected, not waited" made concrete.
|
||||||
|
3. **Flags are gate-proven, not corpus-proven**: a `@table` needs
|
||||||
|
`WO_DATA` and a persistent directory, which the corpus harness does
|
||||||
|
not provide; `just web-app` carries the flags checks (the spec's gate
|
||||||
|
section already put them there).
|
||||||
|
4. **The WAL gains an explicit staged-batch discard** (`wo_wal_abort`):
|
||||||
|
today a failed commit "stays staged" by contract; abort needs to drop
|
||||||
|
the batch deliberately. Same file, same batch machinery, new entry
|
||||||
|
point.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1 — engine transactions: defer commit, undo log, abort
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `database/src/db.h` (txn state on `wo_db`, three new entry
|
||||||
|
points), `database/src/db.c` (the three write cases ~lines 17–80),
|
||||||
|
`database/src/wal.h` + `wal.c` (`wo_wal_abort`),
|
||||||
|
`database/src/table.h` + `table.c` only if the replay-only fixed-id
|
||||||
|
create/index pair needs a non-static wrapper.
|
||||||
|
- Test: `runtime/test/test_txn.c` (new, mirroring the existing
|
||||||
|
`test_*.c` harness shape), wired into the runtime test recipe.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `wo_db_txn_begin(db)` (0 ok; nonzero = already open — the
|
||||||
|
dynamic-nesting signal), `wo_db_txn_commit(db, wal)` (the ONE
|
||||||
|
`wo_wal_commit`; on failure the undo walk runs and the error returns),
|
||||||
|
`wo_db_txn_abort(db, wal)` (reverse undo walk + `wo_wal_abort`).
|
||||||
|
Task 3's builtins call exactly these three.
|
||||||
|
|
||||||
|
- [ ] `wo_db` grows a transaction flag and an undo log (a growable array
|
||||||
|
of entries: op kind, class id, row id, and for update/delete a copy of
|
||||||
|
the row's bytes — `wo_row_ptr` + the class's `row_size` are the copy;
|
||||||
|
the log exists only while a transaction is open, zero cost otherwise).
|
||||||
|
- [ ] The three write cases in `wo_builtin_db`: when the flag is set,
|
||||||
|
record the undo entry BEFORE the RAM apply (update/delete pre-images;
|
||||||
|
insert records just the new id AFTER apply), still `wal_append_*`, and
|
||||||
|
SKIP the per-statement `wo_wal_commit`. Flag clear = byte-identical
|
||||||
|
behavior to today (every existing gate is the proof).
|
||||||
|
- [ ] Statement-level failures inside a transaction change nothing: a
|
||||||
|
unique violation traps before apply and stages nothing (already true —
|
||||||
|
`wo_row_insert` refuses first); a failed append keeps the pre-existing
|
||||||
|
error shape.
|
||||||
|
- [ ] Abort walks the undo log in REVERSE: inserted row → removed;
|
||||||
|
updated row → bytes restored and index entries fixed the replay way
|
||||||
|
(remove + re-add through the same engine-internal pair `wal.c` replay
|
||||||
|
uses); deleted row → re-created with its FIXED id and re-indexed (the
|
||||||
|
replay-only create), then `wo_wal_abort` discards the staged batch.
|
||||||
|
- [ ] Reads inside a transaction need no change: RAM stays applied, so
|
||||||
|
scans/point-reads see the block's own writes for free.
|
||||||
|
- [ ] `test_txn.c`: begin→insert+insert→commit = both rows + ONE wal
|
||||||
|
flush; begin→insert→abort = zero rows, next insert works; update and
|
||||||
|
delete pre-images restored on abort (indexed column included);
|
||||||
|
begin-while-open refused; abort with an empty log is a no-op.
|
||||||
|
- [ ] Run the runtime test suite + `just employee` (engine untouched when
|
||||||
|
no txn opens). Commit.
|
||||||
|
|
||||||
|
## Task 2 — the language surface: keyword, block, WO-E110/E112
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `compiler/src/token.ml` + `lexer.ml` (KwTransaction),
|
||||||
|
`compiler/src/ast.ml` (a Transaction statement holding a body),
|
||||||
|
`compiler/src/parser.ml` (block statement + LEXICAL nesting =
|
||||||
|
WO-E110), `compiler/src/types.ml` (walk the body; E-code constants),
|
||||||
|
`compiler/src/owner.ml` (treat as a plain nested scope),
|
||||||
|
`compiler/src/dump.ml` (labels).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: nothing new.
|
||||||
|
- Produces: the AST node Task 3 lowers; WO-E110 (parsing prefix, nested
|
||||||
|
block), WO-E112 (parsing prefix, `return`/`break`/`continue` whose
|
||||||
|
jump would cross the block boundary — a loop wholly INSIDE the block
|
||||||
|
keeps its own break/continue).
|
||||||
|
|
||||||
|
- [ ] Keyword + statement parse; the body is an ordinary statement list.
|
||||||
|
A `transaction` token while one is already open (parser-tracked depth)
|
||||||
|
is WO-E110 at the inner keyword.
|
||||||
|
- [ ] WO-E112: while parsing the block, a `return` at any depth, or a
|
||||||
|
`break`/`continue` not enclosed by a loop that itself started inside
|
||||||
|
the block, names the rule ("a transaction has one exit; lift the
|
||||||
|
return out or end the block first").
|
||||||
|
- [ ] Types/owner: the body checks exactly like a bare block — no new
|
||||||
|
typing rule (the ownership pass sees a scope; values born inside drop
|
||||||
|
inside, exactly as today).
|
||||||
|
- [ ] Compile-fail fixtures: `transaction-nested` (WO-E110),
|
||||||
|
`transaction-early-return` (WO-E112). Verify both + `just woc-test`
|
||||||
|
(dump labels) + `just oop-e2e`. Commit.
|
||||||
|
|
||||||
|
## Task 3 — VM lowering: internal builtins + the abort-on-unwind frame
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `runtime/src/wob.h` (two builtin ids in the internal range),
|
||||||
|
`runtime/src/builtin.c` (dispatch to Task 1's three entry points),
|
||||||
|
`runtime/src/vm.c` (transaction-flagged catch frame; the vm_trap walk;
|
||||||
|
unwind/rt-destroy cleanup), `compiler/src/emit.ml` (lower the
|
||||||
|
Transaction statement), `runtime/test/test_unwind.c` (frame cleanup on
|
||||||
|
a trap that leaves the whole method).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 1's `wo_db_txn_begin/commit/abort`, Task 2's AST node.
|
||||||
|
- Produces: the observable spec semantics — one fdatasync at the closing
|
||||||
|
brace; trap unwinding OUT aborts and keeps unwinding; `try` INSIDE the
|
||||||
|
block keeps it alive.
|
||||||
|
|
||||||
|
- [ ] Two builtin ids the emitter emits directly from the Transaction
|
||||||
|
case (no name in any user-callable table — nothing to collide with):
|
||||||
|
begin pushes a TRANSACTION-FLAGGED catch frame (the existing TRY frame
|
||||||
|
machinery with one flag bit) and calls txn_begin (nonzero = the
|
||||||
|
WO_T_DB "nested transaction" trap); end calls txn_commit FIRST and
|
||||||
|
pops the frame only on success — a commit failure traps with the frame
|
||||||
|
still in place, so the abort path below runs and RAM is rolled back
|
||||||
|
(the "RAM never ahead of disk" rule, transaction-sized).
|
||||||
|
- [ ] vm_trap's handler search: a transaction-flagged frame is not a
|
||||||
|
handler — abort the transaction, pop it, CONTINUE searching. An inner
|
||||||
|
`try` frame sits ABOVE it and catches first (the spec's
|
||||||
|
inner-try-keeps-it-alive rule falls out of frame order, no special
|
||||||
|
case).
|
||||||
|
- [ ] Program exit / rt teardown with a transaction somehow open (a trap
|
||||||
|
that reaches main uncaught) must abort, not leak the undo log.
|
||||||
|
- [ ] Emitter: begin, body statements, end — plus the WO-E112 guarantee
|
||||||
|
from Task 2 meaning no jump ever leaves the region except a trap.
|
||||||
|
- [ ] Corpus: `run/transaction-commit` (two inserts, both rows readable
|
||||||
|
after — needs the trap corpus's WO_DATA-less shape? No: @table without
|
||||||
|
WO_DATA runs RAM-only with no WAL, which still exercises begin/commit
|
||||||
|
frames; the DURABILITY half lives in Task 6's gate where WO_DATA
|
||||||
|
exists), `run/transaction-abort` (second insert unique-traps, caught
|
||||||
|
OUTSIDE the block: first row gone too, inserts after the abort work,
|
||||||
|
process exits clean under ASan).
|
||||||
|
- [ ] `just oop-e2e` (ASan stage covers the new frames) + full battery.
|
||||||
|
Commit.
|
||||||
|
|
||||||
|
## Task 4 — framework cache: TTL + capacity, pure `.wo`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `docs/examples/writeonce-framework/store/cache.wo`.
|
||||||
|
- Test: `tests/corpus/run/cache-ttl/` (fixture copies the class inline —
|
||||||
|
corpus fixtures cannot `use` the framework; the framework file is the
|
||||||
|
same code verified by the framework's standalone compile + Task 7's
|
||||||
|
consumer build).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `pub class Cache { ttl_ms: Int, cap: Int, keys: multi Text,
|
||||||
|
vals: map<Text, Text>, stamps: map<Text, Int> }` with `get_at(now,
|
||||||
|
key) -> ?Text`, `put_at(now, key, value)`, and `get`/`put` sugar over
|
||||||
|
`time.now` — the shape apps hold as a field on any long-lived
|
||||||
|
instance.
|
||||||
|
|
||||||
|
- [ ] `get_at`: absent → nil; older than `ttl_ms` → remove the entry
|
||||||
|
(lazy expiry — there are no timers by design) and answer nil; live →
|
||||||
|
the value (caller-owned copy).
|
||||||
|
- [ ] `put_at`: store + stamp; when the key list exceeds `cap`, evict
|
||||||
|
OLDEST-INSERTED until within capacity (FIFO — the file states the
|
||||||
|
LRU tradeoff the spec settled). Re-putting an existing key refreshes
|
||||||
|
value + stamp without duplicating the key entry.
|
||||||
|
- [ ] Values are Text; the file says "json.encode structure into it" —
|
||||||
|
no generics exist, stated, not apologized for.
|
||||||
|
- [ ] Fixture drives injected stamps: fresh hit, expiry at exactly
|
||||||
|
ttl+1, eviction order under cap pressure, re-put refresh; ASan run.
|
||||||
|
- [ ] Framework standalone compile stays clean. Commit.
|
||||||
|
|
||||||
|
## Task 5 — framework flags: wf_flags + cached read-through
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `docs/examples/writeonce-framework/store/flags.wo`.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `@table(name: "wf_flags")` class `Flag { name: Text @unique,
|
||||||
|
on: Int }` (Int 0/1 — Bool columns are unproven storage, spec's call)
|
||||||
|
and `pub class Flags { loaded: Int, cache: map<Text, Int> }` with
|
||||||
|
`read(name, default: Bool) -> Bool` and `set(name, on: Bool)`.
|
||||||
|
|
||||||
|
- [ ] `read`: first call fills the map from the table (query by name —
|
||||||
|
the employee-proven point-read), later calls answer from the map;
|
||||||
|
absent flag → the default, uncached (so a later `set` is seen).
|
||||||
|
- [ ] `set`: update-or-insert the row, then update the map in the same
|
||||||
|
call — single process, invalidation is an assignment. Durability is
|
||||||
|
the table's (WAL), restart rebuilds via `read`.
|
||||||
|
- [ ] Framework standalone compile; behavior proven in Task 7's gate
|
||||||
|
(deviation 3). Commit.
|
||||||
|
|
||||||
|
## Task 6 — framework jobs: wf_jobs, enqueue, JobRunner, the idle seam
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `docs/examples/writeonce-framework/store/jobs.wo`.
|
||||||
|
- Modify: `docs/examples/writeonce-framework/http/serve.wo` (Dispatcher
|
||||||
|
gains `fn idle()`; the serve loop calls it after `net.accept`, BEFORE
|
||||||
|
parsing the connection's first request), `app.wo` (`App` satisfies
|
||||||
|
`idle`; `jobs(take r: Jr, budget: Int)` registration; `Jr { r:
|
||||||
|
JobRunner }` wrapper, the Mw/Route pattern), `README.md` (the drain
|
||||||
|
contract + the idle-server-drains-nothing disclosure).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `transaction { }` (Tasks 1–3) only in the DEMO — enqueue
|
||||||
|
itself is an ordinary insert, composition happens in app code.
|
||||||
|
- Produces: `@table(name: "wf_jobs")` class `Job { kind: Text, payload:
|
||||||
|
Text, attempts: Int, not_before: Int }`; `pub fn enqueue(kind,
|
||||||
|
payload)`; `pub interface JobRunner { fn run(kind: Text, payload:
|
||||||
|
Text) -> Bool }`; `App.jobs(take r, budget)`; `Dispatcher.idle()`.
|
||||||
|
|
||||||
|
- [ ] Drain (in `App.idle`): no runner registered → return immediately.
|
||||||
|
Else query up to `budget` due jobs (`not_before <= time.now`,
|
||||||
|
registration order via `take`), each inside `try`: true → `delete`
|
||||||
|
the row; false or trap → `attempts + 1` (update), row stays —
|
||||||
|
retry/backoff policy is the app's (it can rewrite `not_before` from
|
||||||
|
its own runner).
|
||||||
|
- [ ] The post-accept/pre-parse placement is the DETERMINISM the gate
|
||||||
|
needs: a job enqueued by connection A never runs before A closes, and
|
||||||
|
a kill after A's response provably leaves the row. Latency cost
|
||||||
|
(≤ budget jobs ahead of the next request) stated in the README.
|
||||||
|
- [ ] Framework standalone compile; the serve loop's existing gates
|
||||||
|
(`just web-app` current count) stay green with NO runner registered —
|
||||||
|
the seam must cost nothing. Commit.
|
||||||
|
|
||||||
|
## Task 7 — the web-app demo + the gate
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/examples/web-app/main.wo` (transactional CreateOrder +
|
||||||
|
confirm runner + `GET /jobs` count + `POST /flags/:name` + the
|
||||||
|
flag-gated header on the product list), `types.wo` (nothing — wf_
|
||||||
|
tables come from the framework), `README.md`,
|
||||||
|
`scripts/web-app-accept.sh`.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: everything above, through `[deps]` exactly like every other
|
||||||
|
framework feature.
|
||||||
|
|
||||||
|
- [ ] `CreateOrder.handle`: `transaction { insert Order {...};
|
||||||
|
enqueue("confirm", <order json>); }` — the headline composition, one
|
||||||
|
commit. A `Confirm` runner class answers `confirm` by printing the
|
||||||
|
order-confirmation line to stderr and returning true; registered via
|
||||||
|
`app.jobs(Jr { r: Confirm {...} }, budget)`.
|
||||||
|
- [ ] `GET /jobs` (behind the existing bearer auth): pending count as
|
||||||
|
JSON. `POST /flags/:name`: flips through `Flags.set`; the product
|
||||||
|
list answers an extra header (e.g. `x-store-banner`) while the flag
|
||||||
|
is on.
|
||||||
|
- [ ] Gate additions, in order: (a) `POST /orders` 201, then `kill -9`
|
||||||
|
the server IMMEDIATELY (no further requests), restart on the same
|
||||||
|
`WO_DATA`, then `GET /jobs` — the confirmation line appears in the
|
||||||
|
restarted server's log (the drain ran post-accept on this very
|
||||||
|
request) and the count answers 0: the job survived the kill because
|
||||||
|
it committed WITH the order; (b) `POST /flags/banner` then
|
||||||
|
`GET /products` carries the header, restart, still carries it;
|
||||||
|
(c) the standing matrix unchanged. Counts stay dynamic in the script.
|
||||||
|
- [ ] Full battery: `just web-app`, `woc-test`, `oop-e2e`,
|
||||||
|
`deps-accept`, `log-watcher`, `employee`. Commit.
|
||||||
|
|
||||||
|
## Task 8 — docs closeout
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/00-status.md` (row 18 ✅ with measured results; NEXT
|
||||||
|
PLAN advances to 20/21 per the order), story `18-memory-db-features.md`
|
||||||
|
(landing banner) then `git mv` into `stories/.../done/` with links
|
||||||
|
re-pathed and VERIFIED, `docs/00-dependency-graph.md` (node classes:
|
||||||
|
18 done; TPRMW unblocks), framework `README.md` (ledger rows:
|
||||||
|
storage-integration txn-per-request now buildable; the v2 pieces ✅),
|
||||||
|
`compiler/src/CODE-LOGIC.md` + `runtime/src/CODE-LOGIC.md` +
|
||||||
|
`database/src/CODE-LOGIC.md` (the txn seams, one paragraph each).
|
||||||
|
- [ ] Apply; run `just web-app` once more after doc edits; commit.
|
||||||
|
|
||||||
|
## Success criteria (spec, restated as the gate reads them)
|
||||||
|
|
||||||
|
1. Two inserts in one block: SIGKILL before the next request → both rows
|
||||||
|
after restart (gate a); a trap unwinding out → neither row and the
|
||||||
|
process keeps serving (`run/transaction-abort` + ASan).
|
||||||
|
2. The order's job runs after the NEXT accepted connection within
|
||||||
|
budget, never before the posting connection closes, and survives a
|
||||||
|
kill in between (gate a).
|
||||||
|
3. An expired or evicted cache entry answers nil with no timer having
|
||||||
|
existed (`run/cache-ttl`, stamps injected).
|
||||||
|
4. A flipped flag holds across restart (gate b). Every standing gate
|
||||||
|
green; opcode set and `.wob` format byte-identical.
|
||||||
|
|
||||||
|
## Self-review notes
|
||||||
|
|
||||||
|
- Spec coverage: Part A semantics → Tasks 1–3 (observable rules mapped
|
||||||
|
one-to-one; the early-exit hole closed by deviation 1); cache → T4;
|
||||||
|
flags → T5; jobs + seam → T6; demo + gate → T7; out-of-scope list
|
||||||
|
untouched. Corpus/gate split follows deviations 2–3.
|
||||||
|
- Type consistency: the three engine entry points, the two E-codes
|
||||||
|
(E110/E112), `wf_flags`/`wf_jobs`, `get_at`/`put_at`,
|
||||||
|
`JobRunner.run(kind, payload) -> Bool`, `App.jobs(take r, budget)`,
|
||||||
|
`Dispatcher.idle()` — spelled identically in every task that names
|
||||||
|
them.
|
||||||
|
- Risk, disclosed: the abort walk's index restoration is the one place
|
||||||
|
correctness is subtle (indexed column updated then aborted); Task 1's
|
||||||
|
unit test pins exactly that case before any VM work stacks on it.
|
||||||
|
- Ordering: engine (T1) before VM (T3) with the language (T2) between so
|
||||||
|
T3 has both; cache/flags (T4/T5) are independent and could land any
|
||||||
|
time, kept after the critical path so the risky work gets the freshest
|
||||||
|
attention.
|
||||||
267
docs/superpowers/plans/2026-08-20-library-kind-internal.md
Normal file
267
docs/superpowers/plans/2026-08-20-library-kind-internal.md
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
# Iteration 17 — library kind + `internal/`: implementation plan
|
||||||
|
|
||||||
|
> **Status: ⏸ PARKED 2026-08-20** (developer directive: framework v1 work
|
||||||
|
> proceeds instead; this plan stays ready on branch `library-internal`,
|
||||||
|
> execution not started). Board: [docs/00-status.md](../../00-status.md).
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use
|
||||||
|
> superpowers:subagent-driven-development (recommended) or
|
||||||
|
> superpowers:executing-plans to implement this plan task-by-task. Steps use
|
||||||
|
> checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
>
|
||||||
|
> **Style rule (user convention):** this plan carries concept, reason, and
|
||||||
|
> required behavior in words plus verification commands only — no
|
||||||
|
> implementation or test code blocks; the executor writes the code.
|
||||||
|
|
||||||
|
**Goal:** `kind = "library"` in `wo.toml` makes a project checkable without
|
||||||
|
an entry, the `internal/` rule keeps a dependency's plumbing private
|
||||||
|
(WO-E108), and the framework adopts both — `just web-app` proves the public
|
||||||
|
surface unmoved.
|
||||||
|
|
||||||
|
**Architecture:** every change is compile-time and lives in the driver
|
||||||
|
(`compiler/bin/main.ml`): the manifest reader learns one key, the manifest
|
||||||
|
build path grows a check branch, and the dep-use resolution walk in
|
||||||
|
`compile_image` grows the boundary rule. No lexer, parser, typechecker, VM,
|
||||||
|
`.wob`, or GC change — the spec's impact analysis is normative.
|
||||||
|
|
||||||
|
**Tech Stack:** OCaml stdlib only (`woc`), bash acceptance scripts, `just`.
|
||||||
|
|
||||||
|
**Spec:** [`../specs/2026-08-20-library-kind-internal-design.md`](../specs/2026-08-20-library-kind-internal-design.md)
|
||||||
|
(normative). Story:
|
||||||
|
[`17-library-projects-internal.md`](../../stories/language-runtime-database/17-library-projects-internal.md).
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Branch `library-internal`; commits local only, never push.
|
||||||
|
- OCaml stdlib only; no new executables, no new build steps.
|
||||||
|
- Absent `kind` means program — every existing project's behavior is
|
||||||
|
byte-identical; the standing gates prove it each task.
|
||||||
|
- Exit-code doctrine stays: 0 clean, 1 diagnostics, 2 usage/manifest/IO.
|
||||||
|
WO-E108 is a diagnostic (exit 1, printed by the normal collector path);
|
||||||
|
WO-E109 is a manifest error (exit 2, `woc: <file>: error WO-E109: ...`,
|
||||||
|
the WO-E106 shape).
|
||||||
|
- `internal` matches a whole path SEGMENT of a dep-relative module path —
|
||||||
|
never a substring.
|
||||||
|
- Gates that must stay green after every task: `just woc-test`,
|
||||||
|
`just oop-e2e`, `just deps-accept`, `just web-app`, `just log-watcher`,
|
||||||
|
`just employee`.
|
||||||
|
|
||||||
|
## Spec deviations, disclosed up front
|
||||||
|
|
||||||
|
1. The spec put WO-E108/E109 fixtures in the compile-fail corpus. The
|
||||||
|
corpus harness (`scripts/oop-e2e.sh`) compiles a fixture DIRECTORY with
|
||||||
|
no manifest and no network — a `wo.toml` in a fixture dir would flip woc
|
||||||
|
into manifest-build mode, and WO-E108 additionally needs a fetched dep.
|
||||||
|
Both diagnostics are therefore gated in `scripts/web-app-accept.sh`
|
||||||
|
(which already builds a file:// dep chain), not the corpus. Task 5.
|
||||||
|
2. The spec says check mode "stops before image emission". The cheapest
|
||||||
|
correct implementation reuses `compile_image` whole — emission happens
|
||||||
|
in memory and the image is discarded; no artifact is written, no entry
|
||||||
|
is required (an entry-less image is already legal there, the `--emit`
|
||||||
|
precedent). Behavior matches the spec; the pipeline boundary is one
|
||||||
|
step later than the spec's wording.
|
||||||
|
3. `woc build <dir> -o <out>` never reads the manifest, so it resolves no
|
||||||
|
`[deps]` (pre-existing, iteration 15). The dual lib+bin case therefore
|
||||||
|
holds for libraries without `[deps]` — the framework qualifies. Recorded
|
||||||
|
here, not fixed; a dep-aware explicit build is future work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1 — the manifest `kind` key + WO-E109
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `compiler/bin/main.ml` (`manifest_parse` known-key table
|
||||||
|
~line 808; `manifest_build` ~line 1023).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `manifest_parse` accepts top-level `kind`; `manifest_build`
|
||||||
|
exposes the validated kind ("program" when absent) to Task 2's branch.
|
||||||
|
|
||||||
|
- [ ] `manifest_parse`: add `kind` to the known TOP-LEVEL keys (the match
|
||||||
|
arm that today allows `name`, `version`, `description`). Nothing else in
|
||||||
|
the parser changes — the value is a quoted string like every other key.
|
||||||
|
- [ ] `manifest_build`: after the `name` check, read `kind`. Absent or
|
||||||
|
`"program"` continues to build. `"library"` is Task 2's branch (for this
|
||||||
|
task, temporarily fall through to build — the framework does not carry
|
||||||
|
the key until Task 4, so nothing observable changes). Any OTHER value
|
||||||
|
fails as a manifest error in the WO-E106 print shape with code WO-E109,
|
||||||
|
naming the given value and the two legal ones, exit 2.
|
||||||
|
- [ ] Verify by hand: a scratch project under the scratchpad with
|
||||||
|
`kind = "junk"` refuses with WO-E109 and exit 2; the same project with
|
||||||
|
`kind = "program"`, and with no `kind` line, builds as before.
|
||||||
|
- [ ] Verify nothing moved: `just woc-test && just deps-accept && just employee`.
|
||||||
|
- [ ] Commit.
|
||||||
|
|
||||||
|
## Task 2 — library check mode + the build-error hint
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `compiler/bin/main.ml` (`manifest_build`; `build_mode` no-entry
|
||||||
|
error ~line 639; `usage_msg` ~lines 42–52 and the long help's build
|
||||||
|
paragraph ~line 96).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 1's validated kind.
|
||||||
|
- Produces: `woc <dir>` on a `kind = "library"` manifest runs the full
|
||||||
|
check; the behavior Tasks 4–5 verify against.
|
||||||
|
|
||||||
|
- [ ] `manifest_build`, kind `"library"`: resolve `[deps]` and enforce the
|
||||||
|
`[runtime]` constraint exactly as build does (a library must be
|
||||||
|
checkable offline once locked), then run `compile_image ~deps dir`,
|
||||||
|
print diagnostics through the normal `finish` path on error (exit 1),
|
||||||
|
and exit 0 silently on success. No `target/` directory is created, no
|
||||||
|
file is written, no entry is required. The full pipeline runs — parse,
|
||||||
|
typecheck, interface satisfaction, ownership, GC inference — because
|
||||||
|
`compile_image` already runs it; the in-memory image is discarded
|
||||||
|
(deviation 2).
|
||||||
|
- [ ] `build_mode`, the no-entry error: when `<dir>/wo.toml` exists and
|
||||||
|
names `kind = "library"`, append the library hint to the existing
|
||||||
|
message — the project is a library; add a `main` for a demo binary or
|
||||||
|
check it with `woc <dir>`. Read the manifest only if the file exists;
|
||||||
|
malformed manifests keep failing as they do today.
|
||||||
|
- [ ] `usage_msg`: the `woc <dir>` line notes "builds a program / checks a
|
||||||
|
library, per the manifest's `kind`"; the long help gains two sentences
|
||||||
|
on `kind` and check mode. No new flags.
|
||||||
|
- [ ] Verify by hand: scratch library project (`kind = "library"`, one
|
||||||
|
class, no `main`) — `woc <dir>` exits 0 with no output; plant a type
|
||||||
|
error, `woc <dir>` exits 1 with the normal diagnostic; `woc build <dir>
|
||||||
|
-o x` fails with the no-entry message plus the library hint; add a
|
||||||
|
`main`, `woc build` produces a runnable binary (dual case) while
|
||||||
|
`woc <dir>` still only checks.
|
||||||
|
- [ ] Verify nothing moved: `just woc-test && just oop-e2e && just deps-accept`.
|
||||||
|
- [ ] Commit.
|
||||||
|
|
||||||
|
## Task 3 — the `internal/` boundary rule (WO-E108)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `compiler/bin/main.ml` (`compile_image`'s dep-use walk,
|
||||||
|
~lines 487–518).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: the existing walk that already knows, per file, whether a dep
|
||||||
|
owns it (`owner`) and prefixes dep-internal use paths.
|
||||||
|
- Produces: WO-E108 diagnostics in the collector; Task 5 gates on the code
|
||||||
|
string.
|
||||||
|
|
||||||
|
- [ ] In the same `List.map` over parsed files: for a file NOT owned by any
|
||||||
|
dep (the `owner = None` branch, today untouched), inspect each `Use`
|
||||||
|
whose FIRST segment names a dep in `deps` — when any LATER segment is
|
||||||
|
exactly `internal`, add a diagnostic to the collector: code WO-E108, the
|
||||||
|
use's file and `pos`, message naming the internal module path and the
|
||||||
|
dependency it belongs to (the `Diag.error` + `Collector.add` pattern at
|
||||||
|
~line 277). Do not drop the use — the collector's has-error path already
|
||||||
|
prevents emission, and later resolution errors on the same use are
|
||||||
|
harmless duplicates suppressed by exit-on-first-report ordering as
|
||||||
|
today.
|
||||||
|
- [ ] Dep-owned files stay untouched on this path — the boundary is
|
||||||
|
consumer-only (spec §3): the dep's own `use internal` (prefixed to
|
||||||
|
`<dep>/internal` by the existing arm) must keep compiling.
|
||||||
|
- [ ] The root project's own `internal/` directories are NOT matched: the
|
||||||
|
rule keys on the first segment being a DEP name, so a root-project
|
||||||
|
`use internal` never fires it. No code needed — assert it in the Task 5
|
||||||
|
gate instead.
|
||||||
|
- [ ] Verify by hand: temp dir pair — a dep with an `internal/` module and
|
||||||
|
a consumer importing `<dep>/internal` — compile fails exit 1 printing
|
||||||
|
WO-E108 at the `use`'s line; the dep's own file importing `internal`
|
||||||
|
compiles.
|
||||||
|
- [ ] Verify nothing moved: `just woc-test && just deps-accept && just web-app`.
|
||||||
|
- [ ] Commit.
|
||||||
|
|
||||||
|
## Task 4 — framework reorg: adopt `kind` + `internal/`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/examples/writeonce-framework/wo.toml` (add
|
||||||
|
`kind = "library"`), `app.wo` (imports), `README.md` (verification note).
|
||||||
|
- Move: `http/parse.wo` → `internal/parse.wo`,
|
||||||
|
`http/serve.wo` → `internal/serve.wo` (git mv; module becomes
|
||||||
|
`framework/internal` under a consumer, `internal` standalone).
|
||||||
|
- Not touched: `http/types.wo`, `router/router.wo`, the web-app.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 2's check mode (standalone verification), Task 3's rule
|
||||||
|
(what the move protects).
|
||||||
|
- Produces: the reorganized layout Task 5's gate checks against.
|
||||||
|
|
||||||
|
- [ ] Move the two plumbing files. Same-module access dies with the move:
|
||||||
|
`internal/serve.wo` and `internal/parse.wo` now need `use http` for
|
||||||
|
`Req`/`Resp` (they shared the `http` module with `types.wo` before);
|
||||||
|
`app.wo` adds `use internal` for the serve loop and `Parsed` seam. No
|
||||||
|
declaration changes — imports only.
|
||||||
|
- [ ] `wo.toml` gains `kind = "library"` (top-level, beside `name`).
|
||||||
|
- [ ] README: replace the `--emit` verification workaround sentence with
|
||||||
|
the check-mode invocation (`woc <dir>` — full pipeline, no entry), and
|
||||||
|
one sentence on `internal/` being unimportable by consumers.
|
||||||
|
- [ ] Verify: `compiler/_build/default/bin/woc docs/examples/writeonce-framework`
|
||||||
|
exits 0 silently — the workaround is dead.
|
||||||
|
- [ ] Verify the consumer: `just web-app` — all 14 standing checks pass
|
||||||
|
unchanged (the app imports only `framework`, `framework/http`,
|
||||||
|
`framework/router`, so NOTHING in it changes).
|
||||||
|
- [ ] Commit.
|
||||||
|
|
||||||
|
## Task 5 — gate: three new checks in web-app-accept
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `scripts/web-app-accept.sh` (after the build check, before the
|
||||||
|
serve matrix).
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: the `$W/fw` framework copy and `$W/app` consumer the script
|
||||||
|
already builds; Tasks 1–4's behavior.
|
||||||
|
- Produces: `just web-app` at 17 checks, the iteration's single gate.
|
||||||
|
|
||||||
|
- [ ] Check 15 — library check mode: `woc "$W/fw"` (the framework copy,
|
||||||
|
which now carries `kind = "library"` and no `main`) exits 0 with empty
|
||||||
|
output.
|
||||||
|
- [ ] Check 16 — the boundary: copy the app to a second temp dir, append a
|
||||||
|
`use framework/internal` line to its `main.wo`, compile; assert exit 1
|
||||||
|
and `WO-E108` in stderr.
|
||||||
|
- [ ] Check 17 — kind validation: copy the app again, set
|
||||||
|
`kind = "junk"` in its manifest, compile; assert exit 2 and `WO-E109`
|
||||||
|
in stderr.
|
||||||
|
- [ ] Renumber nothing — the script counts dynamically (`pass`/`fail`);
|
||||||
|
update only the header comment's check inventory.
|
||||||
|
- [ ] Run the full battery: `just web-app` (17/0) and the standing gates —
|
||||||
|
`just woc-test`, `just oop-e2e`, `just deps-accept`, `just log-watcher`,
|
||||||
|
`just employee`.
|
||||||
|
- [ ] Commit.
|
||||||
|
|
||||||
|
## Task 6 — docs closeout
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/00-status.md` (NEXT PLAN advances; board row 17 → done
|
||||||
|
with what landed; in-progress row moves to the next order item),
|
||||||
|
story `17-library-projects-internal.md` (landing banner),
|
||||||
|
`compiler/src/CODE-LOGIC.md` (driver section: `kind`, check mode, the
|
||||||
|
boundary rule), `docs/08-project-structure.md` (framework layout gains
|
||||||
|
`internal/`), framework story `16-web-framework.md` only if it names the
|
||||||
|
`--emit` workaround.
|
||||||
|
- [ ] Apply; every claim carries its measured gate result (17/0 etc.); no
|
||||||
|
forward-looking "will".
|
||||||
|
- [ ] `just web-app` once more after the doc edits (nothing should move —
|
||||||
|
honesty check).
|
||||||
|
- [ ] Commit.
|
||||||
|
|
||||||
|
## Success criteria (spec, restated as the gate reads them)
|
||||||
|
|
||||||
|
1. Framework checks entry-less (`woc <dir>` exit 0) and `woc build` on it
|
||||||
|
fails naming the library kind — gate check 15 + Task 2's hand check.
|
||||||
|
2. A consumer import of `framework/internal` is WO-E108 at the `use`;
|
||||||
|
the framework's own import stays legal — gate check 16 + Task 3.
|
||||||
|
3. `just web-app`'s original 14 checks pass unchanged after the reorg —
|
||||||
|
Task 4/5.
|
||||||
|
4. A manifest without `kind` behaves byte-identically — every standing
|
||||||
|
gate, every task.
|
||||||
|
|
||||||
|
## Self-review notes
|
||||||
|
|
||||||
|
- Spec coverage: §1 kind key → Task 1; §2 driver modes → Task 2; §3 rule →
|
||||||
|
Task 3; §4 diagnostics → Tasks 1–3; §5 reorg → Task 4; §6 gate → Task 5;
|
||||||
|
out-of-scope list untouched by any task. Corpus-fixture clause replaced
|
||||||
|
by deviation 1; emission-boundary wording by deviation 2; dual-with-deps
|
||||||
|
limit by deviation 3.
|
||||||
|
- Type consistency: the only cross-task names are the two code strings
|
||||||
|
(WO-E108, WO-E109), the manifest key `kind`, and the module path
|
||||||
|
`framework/internal` — spelled identically in every task.
|
||||||
|
- Risk, disclosed: moving serve/parse breaks same-module visibility they
|
||||||
|
silently enjoyed beside `types.wo`; Task 4 names the exact import each
|
||||||
|
file must gain, and the standalone check catches any miss before the
|
||||||
|
gate runs.
|
||||||
227
docs/superpowers/plans/2026-08-20-shard-fiber-arc.md
Normal file
227
docs/superpowers/plans/2026-08-20-shard-fiber-arc.md
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
# The 8+11 concurrency arc — implementation plan (staged)
|
||||||
|
|
||||||
|
> **Status: STAGES 1 AND 2 COMPLETE 2026-08-20** (branch `concurrency-arc`,
|
||||||
|
> T1–T4 landed + the `docs/examples/fibers` demo and its `just fibers`
|
||||||
|
> gate, 8/0). Stages 2–3 pending. Execution deviations, disclosed:
|
||||||
|
> (1) the reduction budget decrements at loop BACK-EDGES ONLY, after the
|
||||||
|
> jump lands — the spec's "same three sites as the GC" wording had a
|
||||||
|
> livelock at budget 1 (pre-instruction save re-executes the jump into
|
||||||
|
> the same decrement; pinned by test_fiber's exact round-robin);
|
||||||
|
> (2) T4 landed io_uring-FIRST per the amended spec (park.c: raw ring,
|
||||||
|
> POLL_ADD/TIMEOUT, WO_IO=uring|epoll, epoll fallback) and the battery
|
||||||
|
> ran green on BOTH backends plus LW_SOAK over parked I/O;
|
||||||
|
> (3) a partial net.write's progress crosses the park via park_wr_at —
|
||||||
|
> the retry protocol needed write-offset state the spec never named.
|
||||||
|
> Stage-2 deviations: (4) the inbox is a MUTEX-guarded list + eventfd,
|
||||||
|
> not the spec's lock-free MPSC ring — rings arrive when 22 measures the
|
||||||
|
> mutex; (5) WO-E222 applies to EVERY spawn/send (round-robin placement
|
||||||
|
> makes any actor potentially remote — compile time cannot see the
|
||||||
|
> shard); (6) the deterministic corpus pins WO_SHARDS=1, and the
|
||||||
|
> multi-shard truth (output SETS, TSan, both I/O backends) lives in the
|
||||||
|
> fibers gate — the spec's narrowed-determinism rule made operational;
|
||||||
|
> (7) two TSan-caught races fixed (late-init memset vs concurrent push;
|
||||||
|
> wake-efd read outside the lock) and one teardown SEGV (routed frees
|
||||||
|
> during teardown are now no-ops: arenas die wholesale).
|
||||||
|
> Board: [docs/00-status.md](../../00-status.md).
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use
|
||||||
|
> superpowers:subagent-driven-development (recommended) or
|
||||||
|
> superpowers:executing-plans task-by-task. Steps use checkbox syntax.
|
||||||
|
>
|
||||||
|
> **Style rule (user convention):** concept, reason, required behavior in
|
||||||
|
> words plus verification commands only — the executor writes the code.
|
||||||
|
|
||||||
|
**Goal:** the spec's three stages — fibers on one shard, shards with
|
||||||
|
ownership-moving sends, the transparent DB actor — each landing with
|
||||||
|
every standing gate green before the next begins.
|
||||||
|
|
||||||
|
**Architecture:** see the spec (normative):
|
||||||
|
[`../specs/2026-08-20-shard-fiber-arc-design.md`](../specs/2026-08-20-shard-fiber-arc-design.md).
|
||||||
|
Stories: [8](../../stories/language-runtime-database/refine/08-shard-actor-runtime.md) ·
|
||||||
|
[11](../../stories/language-runtime-database/refine/11-fibers.md).
|
||||||
|
|
||||||
|
**Tech Stack:** C11 libc-only (`wovm`), OCaml stdlib-only (`woc`), bash
|
||||||
|
gates; TSan added to the corpus harness at stage 2.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Branch `concurrency-arc` off master; commits local only, never push.
|
||||||
|
- A stage does not start until the previous stage's FULL battery is
|
||||||
|
green: `just woc-test`, `just oop-e2e` (ASan stage included),
|
||||||
|
`just deps-accept`, `just web-app`, `just log-watcher`,
|
||||||
|
`just employee`.
|
||||||
|
- Stage 1 changes NO observable behavior for single-fiber programs —
|
||||||
|
every existing fixture byte-identical; that is the refactor's proof.
|
||||||
|
- New E-codes: WO-E221 (spawn needs `receive(msg: M)`), WO-E222
|
||||||
|
(traced-containing cross-shard send). `.wob` format: spawn/send lower
|
||||||
|
to new BUILTIN ids — no new opcodes; the version bumps ONLY if the
|
||||||
|
actor-type field encoding forces it (decide in T3, disclose).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stage 1 — fibers on one shard
|
||||||
|
|
||||||
|
### Task 1 — the fiber context extraction (pure refactor)
|
||||||
|
|
||||||
|
**Files:** `runtime/src/vm.h`, `runtime/src/vm.c`, `runtime/src/gc.c`
|
||||||
|
(`vm_gc_roots`), `runtime/src/main.c`, `runtime/test/*` (compile fixes
|
||||||
|
only).
|
||||||
|
|
||||||
|
**Interfaces:** produces `wo_fiber` (registers, frames, depth, catch
|
||||||
|
stack, caught error, resume pc/state) and `wo_vm` holding module + rt +
|
||||||
|
current-fiber + a fiber list; everything else consumes `vm->cur`.
|
||||||
|
|
||||||
|
- [x] Move the five interpreter-state fields into the fiber struct; the
|
||||||
|
vm allocates fiber 0 (main) at init; `vm_run`/`wo_vm_call`/unwind/
|
||||||
|
trap paths read through the current-fiber pointer.
|
||||||
|
- [x] `vm_gc_roots` iterates ALL fibers' registers+frames (one fiber
|
||||||
|
today — the loop is the change).
|
||||||
|
- [x] Verify: full battery byte-identical (this task has NO functional
|
||||||
|
change to hide behind). Commit.
|
||||||
|
|
||||||
|
### Task 2 — run queue + reduction budget
|
||||||
|
|
||||||
|
**Files:** `runtime/src/vm.c`, `runtime/src/vm.h`.
|
||||||
|
|
||||||
|
**Interfaces:** produces fiber states (RUNNABLE/PARKED/DONE), a FIFO
|
||||||
|
queue, `WO_REDUCTIONS` (default 4000), and a scheduler loop around the
|
||||||
|
dispatch loop; consumes Task 1's context switch (save/restore = swap the
|
||||||
|
current-fiber pointer).
|
||||||
|
|
||||||
|
- [x] Budget decrements — AMENDED to back-edges only (deviation 1); originally the GC-safepoint sites (NEW, CALL,
|
||||||
|
backward-JMP); zero → re-queue + switch. One fiber = no observable
|
||||||
|
change (it re-queues to itself).
|
||||||
|
- [x] A trap unwinding out of a non-main fiber kills that fiber alone
|
||||||
|
(drops run, uncaught-trap report to stderr, program lives); main's
|
||||||
|
return ends the program and unwinds every remaining fiber through the
|
||||||
|
stop path (drop-clean).
|
||||||
|
- [x] C unit test (`runtime/test/test_fiber.c`): two hand-built fibers
|
||||||
|
interleave deterministically under a tiny budget; kill-on-main-return
|
||||||
|
leaks nothing under ASan. Full battery. Commit.
|
||||||
|
|
||||||
|
### Task 3 — spawn / send / `actor M` (language + same-shard delivery)
|
||||||
|
|
||||||
|
**Files:** `compiler/src/{token,lexer,ast,parser,types,owner,emit,dump}.ml`,
|
||||||
|
`runtime/src/{wob.h,builtin.c,vm.c}`, corpus fixtures.
|
||||||
|
|
||||||
|
**Interfaces:** produces the `actor M` field-type shape (parametric like
|
||||||
|
`multi T`), `spawn Cls { ... }` expression (M inferred from Cls's
|
||||||
|
`receive`; WO-E221 otherwise), `send(addr, msg)` (msg typed M, owner
|
||||||
|
pass TRANSFERS it — sender's later use is the existing use-after-move
|
||||||
|
diagnostic), and runtime delivery: an actor is a fiber that sleeps
|
||||||
|
between messages, mailbox FIFO, one `receive` call per message.
|
||||||
|
|
||||||
|
- [x] Type/AST/parse: `actor M` in field positions; spawn/send keywords
|
||||||
|
(grep the corpus for identifier collisions first, the standing
|
||||||
|
keyword discipline).
|
||||||
|
- [x] Owner: send's message argument is a transfer (the take machinery);
|
||||||
|
spawn's ctor literal moves its field values (existing ctor rules).
|
||||||
|
- [x] Emit + runtime: two builtin ids; spawn creates the actor fiber +
|
||||||
|
returns the address as a scalar word; send enqueues + wakes.
|
||||||
|
Addresses are copyable scalars; send-to-dead = silent drop (v1,
|
||||||
|
spec'd).
|
||||||
|
- [x] Corpus: `run/actor-echo` (spawn, send, receive mutates actor
|
||||||
|
state, deterministic output), `run/actor-move` (sender's reuse after
|
||||||
|
send = compile-fail fixture, actually its own
|
||||||
|
`compile-fail/send-after-move`), `compile-fail/spawn-no-receive`
|
||||||
|
(WO-E221). ASan. Full battery. Commit.
|
||||||
|
|
||||||
|
### Task 4 — parking builtins on the shard's io_uring loop (AMENDED)
|
||||||
|
|
||||||
|
**Files:** `runtime/src/builtin.c` (net/time cases), `runtime/src/vm.c`
|
||||||
|
(the per-shard ring the scheduler idles in), `runtime/src/sysio.c`.
|
||||||
|
|
||||||
|
**Interfaces:** produces PARKED fibers keyed by ring `user_data`;
|
||||||
|
consumes Task 2's states. AMENDED 2026-08-20 (developer directive +
|
||||||
|
the linux reference project): the loop is **io_uring in readiness mode**
|
||||||
|
(POLL_ADD for fds, TIMEOUT for sleeps; resume re-executes the now-ready
|
||||||
|
builtin), with the startup probe + `WO_IO=uring|epoll` override and the
|
||||||
|
epoll path kept as the PORTABILITY fallback (seccomp'd containers deny
|
||||||
|
io_uring) — CI runs the fiber tests on both. Raw syscalls, 5.1-safe ops
|
||||||
|
only; the ring layout goes in the binding doc. 23 later rides the SAME
|
||||||
|
per-shard ring for WAL WRITE+FSYNC chains, and the ops-mode ladder
|
||||||
|
(reads/writes as ring ops with completion results) is its own follow-up
|
||||||
|
task after T4 proves the loop.
|
||||||
|
|
||||||
|
- [x] Stop composes: the stop flag wakes every parked fiber into the
|
||||||
|
unwind path (the existing stop story, per fiber).
|
||||||
|
- [x] Proven live instead of a bespoke C test: the fibers demo (part 2) + the full battery over parked I/O (log-watcher's mcp loop, web-app's matrix) on BOTH backends + LW_SOAK; originally: fiber A parked in sleep, fiber B progresses, one TID;
|
||||||
|
A resumes on time. Corpus: `run/fiber-park-net` if expressible
|
||||||
|
without a peer (loopback pair via the existing net builtins — the
|
||||||
|
log-watcher mcp fixtures prove the shape); otherwise the unit test
|
||||||
|
carries it, disclosed.
|
||||||
|
- [x] Full battery + framework/web-app gates (serve loop untouched —
|
||||||
|
single fiber per process today, degenerate path). Commit. **Stage 1
|
||||||
|
complete: board + stories note it.**
|
||||||
|
|
||||||
|
## Stage 2 — shards
|
||||||
|
|
||||||
|
### Task 5 — shard threads + per-shard runtimes
|
||||||
|
|
||||||
|
**Files:** `runtime/src/{vm.h,vm.c,main.c,obj.c,gc.c}`,
|
||||||
|
`compiler/bin/main.ml` (manifest `[runtime] shards`).
|
||||||
|
|
||||||
|
- [x] One pinned pthread per shard, each with its own full `wo_rt`;
|
||||||
|
count = cores default, `WO_SHARDS`/manifest override; N=1 must be
|
||||||
|
byte-identical to stage 1 (the escape hatch and the proof).
|
||||||
|
- [x] Program mode: main runs on shard 0; with no spawns the other
|
||||||
|
shards idle at zero cost (parked on their eventfds).
|
||||||
|
- [x] Full battery at N=1 AND at default cores (no cross-shard sends
|
||||||
|
exist yet — the threads just have to not break anything). Commit.
|
||||||
|
|
||||||
|
### Task 6 — cross-shard send + traced rejection + home-routed frees
|
||||||
|
|
||||||
|
**Files:** `runtime/src/{vm.c,obj.c,gc.c}` (MPSC rings, eventfd wake,
|
||||||
|
home-free ring), `compiler/src/types.ml` (WO-E222 via the
|
||||||
|
transitively-traced check), corpus + TSan.
|
||||||
|
|
||||||
|
- [x] Round-robin spawn placement; pointer handoff; header shard id
|
||||||
|
routes the eventual free home (drained per tick, debug-asserted).
|
||||||
|
- [x] WO-E222 at compile time for traced-containing sends (reuse the
|
||||||
|
gc_may-shaped fixpoint the inference already computes).
|
||||||
|
- [x] The actor corpus under ASan AND TSan: zero races/leaks; output-SET
|
||||||
|
assertions (cross-shard nondeterminism is honest, spec'd).
|
||||||
|
- [x] Full battery at default cores. Commit. **Stage 2 complete.**
|
||||||
|
|
||||||
|
## Stage 3 — the DB actor + closing the arc
|
||||||
|
|
||||||
|
### Task 7 — transparent DB RPC
|
||||||
|
|
||||||
|
**Files:** `runtime/src/builtin.c` (db cases marshal when not on shard
|
||||||
|
0), `database/src/` untouched (the engine never learns), `runtime/src/vm.c`
|
||||||
|
(request/reply parking).
|
||||||
|
|
||||||
|
- [ ] Non-owner DB builtins marshal statement + args to shard 0, park,
|
||||||
|
resume with materialized reply; `transaction { }` travels as one unit
|
||||||
|
(18's staged batch stays owner-side).
|
||||||
|
- [ ] `just employee` + `just web-app` at default cores, answers
|
||||||
|
byte-identical to N=1 (iteration 8's criterion 4, the arc's headline
|
||||||
|
proof). Commit.
|
||||||
|
|
||||||
|
### Task 8 — the arc's closeout
|
||||||
|
|
||||||
|
- [ ] 22's benchmark re-run (or its minimal precursor if 22 has not
|
||||||
|
landed: the employee read/write loop timed) single- vs multi-shard;
|
||||||
|
numbers recorded in the stories.
|
||||||
|
- [ ] Stories 8 + 11 landing banners; board rows; graph node classes;
|
||||||
|
framework README ledger rows that the arc unblocks (streaming etc.
|
||||||
|
stay ⏸ until their own slices — the arc UNBLOCKS, it does not build
|
||||||
|
them); CODE-LOGIC files (vm fiber model, shard/mailbox model, DB RPC).
|
||||||
|
- [ ] Full battery once more after doc edits. Commit.
|
||||||
|
|
||||||
|
## Success criteria
|
||||||
|
|
||||||
|
The spec's four, verbatim — stage 1's determinism + ASan-clean unwind,
|
||||||
|
stage 2's TSan-clean moves + WO-E222, stage 3's byte-identical
|
||||||
|
multi-shard gates + recorded 22 delta, and the language having grown
|
||||||
|
exactly `spawn`/`send`/`actor M`.
|
||||||
|
|
||||||
|
## Self-review notes
|
||||||
|
|
||||||
|
- Spec coverage: Part A → T1–T4; Part B → T5–T6; Part C → T7–T8;
|
||||||
|
diagnostics T3 (E221) / T6 (E222); out-of-scope untouched.
|
||||||
|
- The riskiest surgery (T1) is deliberately a PURE refactor with a
|
||||||
|
byte-identical battery as its only claim — functional change never
|
||||||
|
hides inside it.
|
||||||
|
- Names used consistently: `wo_fiber`, `vm->cur`, WO-E221/E222,
|
||||||
|
`WO_REDUCTIONS`, `WO_SHARDS`, `actor M`, `spawn`/`send`.
|
||||||
|
|
@ -7,8 +7,8 @@
|
||||||
library, HTTP/1.1 behind a TLS-terminating reverse proxy, and (C) the parked
|
library, HTTP/1.1 behind a TLS-terminating reverse proxy, and (C) the parked
|
||||||
HTTP/2 path. Three sub-projects; A and B are the fundable ones, C is a
|
HTTP/2 path. Three sub-projects; A and B are the fundable ones, C is a
|
||||||
recorded successor.
|
recorded successor.
|
||||||
**Relates to:** iteration 10 (`service` blocks — this framework becomes their
|
**Relates to:** iteration 25 (`service` blocks — this framework becomes their
|
||||||
lowering target, not a rival), iterations 8/9f/11 (the concurrency work h2c
|
lowering target, not a rival), iterations 8/23/11 (the concurrency work h2c
|
||||||
waits for), `docs/plan/discarded.md` (FFI reject row — load-bearing here).
|
waits for), `docs/plan/discarded.md` (FFI reject row — load-bearing here).
|
||||||
|
|
||||||
## Decisions locked during brainstorming
|
## Decisions locked during brainstorming
|
||||||
|
|
@ -17,7 +17,7 @@ waits for), `docs/plan/discarded.md` (FFI reject row — load-bearing here).
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| TLS | **Proxy-terminated** (nginx/caddy). Browsers get TLS+ALPN+h2 from the proxy; the framework speaks HTTP/1.1 (later h2c) behind it. Zero TLS in the language or runtime. Direct-serving TLS is a separate future iteration and would be judged against the FFI/libc-only doctrine then, not now. Homegrown TLS is refused outright: a decade of side-channel and certificate-validation subtleties makes it a security liability, not a milestone. |
|
| TLS | **Proxy-terminated** (nginx/caddy). Browsers get TLS+ALPN+h2 from the proxy; the framework speaks HTTP/1.1 (later h2c) behind it. Zero TLS in the language or runtime. Direct-serving TLS is a separate future iteration and would be judged against the FFI/libc-only doctrine then, not now. Homegrown TLS is refused outright: a decade of side-channel and certificate-validation subtleties makes it a security liability, not a milestone. |
|
||||||
| Dependencies | **`wo.toml [deps]` + git fetch.** A real (mini) package manager: exact-rev git dependencies, a lockfile, a per-project cache. No registry, no semver solving. |
|
| Dependencies | **`wo.toml [deps]` + git fetch.** A real (mini) package manager: exact-rev git dependencies, a lockfile, a per-project cache. No registry, no semver solving. |
|
||||||
| HTTP/2 | **v1 is HTTP/1.1 keep-alive.** h2's payoff is multiplexing, which a single blocking thread cannot exploit; h2c lands as its own iteration after shards (8) / io_uring (9f) / fibers (11). Behind the proxy, browsers see h2 from day one regardless. |
|
| HTTP/2 | **v1 is HTTP/1.1 keep-alive.** h2's payoff is multiplexing, which a single blocking thread cannot exploit; h2c lands as its own iteration after shards (8) / io_uring (23) / fibers (11). Behind the proxy, browsers see h2 from day one regardless. |
|
||||||
| Handler model | **Structural interfaces, not closures.** The language has no function values by doctrine; a route handler is a class satisfying a `Handler` interface, dispatched by ICALL — which works on today's runtime and is checked by WO-E205. |
|
| Handler model | **Structural interfaces, not closures.** The language has no function values by doctrine; a route handler is a class satisfying a `Handler` interface, dispatched by ICALL — which works on today's runtime and is checked by WO-E205. |
|
||||||
| Incubation | Framework is born at `docs/examples/writeonce-framework/`; the consuming app at `docs/examples/web-app/` imports it **through the `[deps]` mechanism** (a local git URL), so the whole import chain is exercised by the sample. Extraction to `github.com/shoneyj/<name>` later is a `git subtree split`, not a redesign. |
|
| Incubation | Framework is born at `docs/examples/writeonce-framework/`; the consuming app at `docs/examples/web-app/` imports it **through the `[deps]` mechanism** (a local git URL), so the whole import chain is exercised by the sample. Extraction to `github.com/shoneyj/<name>` later is a `git subtree split`, not a redesign. |
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ routes, restart-persistence check, SIGTERM.
|
||||||
|
|
||||||
## C. HTTP/2 (h2c) — parked successor
|
## C. HTTP/2 (h2c) — parked successor
|
||||||
|
|
||||||
After iterations 8 (shards) / 9f (io_uring) / 11 (fibers): h2c framing +
|
After iterations 8 (shards) / 23 (io_uring) / 11 (fibers): h2c framing +
|
||||||
HPACK, either natively (needs a bytes/buffer type with cheap slicing — that
|
HPACK, either natively (needs a bytes/buffer type with cheap slicing — that
|
||||||
type rides with this iteration, not v1) or via nghttp2-in-runtime (a doctrine
|
type rides with this iteration, not v1) or via nghttp2-in-runtime (a doctrine
|
||||||
decision to re-argue then, with the TweetNaCl precedent and the libc-only
|
decision to re-argue then, with the TweetNaCl precedent and the libc-only
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
# Iteration 17 — library projects and dependency privacy: design
|
||||||
|
|
||||||
|
> **Status: ⏸ PARKED 2026-08-20** (developer directive: framework v1 work
|
||||||
|
> proceeds instead; spec + plan stay ready on branch `library-internal`).
|
||||||
|
> Approved before parking. Decisions were settled in
|
||||||
|
> [the iteration](../../stories/language-runtime-database/17-library-projects-internal.md)
|
||||||
|
> (four forks + impact analysis); this spec makes them buildable. The plan
|
||||||
|
> follows after review. Board: [docs/00-status.md](../../00-status.md).
|
||||||
|
>
|
||||||
|
> Per repo convention this spec carries concept, reason, and required
|
||||||
|
> behavior in words only — no implementation code.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
A project can say it is a library, and a dependency can keep modules to
|
||||||
|
itself. Concretely: `docs/examples/writeonce-framework` declares
|
||||||
|
`kind = "library"` in its `wo.toml`, `woc <dir>` on it typechecks the whole
|
||||||
|
project with no entry required (the iteration-16 `--emit` workaround is
|
||||||
|
deleted), its parser and serve-loop plumbing move under `internal/` where
|
||||||
|
the web-app cannot import them, and `just web-app` proves nothing public
|
||||||
|
broke.
|
||||||
|
|
||||||
|
## Background (the two gaps, from iterations 15/16)
|
||||||
|
|
||||||
|
1. A project without `fn main` cannot be checked: manifest presence forces
|
||||||
|
build mode, which errors "no `main` entry point found". The framework is
|
||||||
|
verified today through `woc --emit` — a wart.
|
||||||
|
2. `pub` is module-public with no dep-private tier: `parse_request` is
|
||||||
|
exactly as importable by the consuming app as `Handler`. Nothing marks
|
||||||
|
"this module is the library's own business".
|
||||||
|
|
||||||
|
## Settled decisions (normative, from the iteration)
|
||||||
|
|
||||||
|
- Library-ness is manifest-declared: top-level `kind` key, values
|
||||||
|
`"program"` (the default when absent) and `"library"`; any other value is
|
||||||
|
a manifest error.
|
||||||
|
- Privacy is Go's `internal/` directory rule, applied at the `[deps]`
|
||||||
|
boundary only: a consumer cannot `use` a dependency module whose path
|
||||||
|
contains an `internal` segment; inside the dependency the same import
|
||||||
|
stays legal.
|
||||||
|
- Lib+bin duality is allowed: a library may carry an entry-shaped `main`;
|
||||||
|
`kind = "library"` changes only the DEFAULT action of `woc <dir>`.
|
||||||
|
- Inherited from Go, explicitly not checked: an internal type may appear in
|
||||||
|
a public signature. The consumer can hold and pass such a value but
|
||||||
|
cannot import the module to name its type. Library author's smell to
|
||||||
|
avoid, not a diagnostic.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### 1. The manifest `kind` key
|
||||||
|
|
||||||
|
One top-level key in `wo.toml`, read where the manifest is already parsed
|
||||||
|
(the driver's manifest reader in `compiler/bin/main.ml`). Absent means
|
||||||
|
program — every existing project keeps its behavior. A value other than
|
||||||
|
`"program"` or `"library"` is rejected with the new WO-E109, naming the
|
||||||
|
value and the two legal ones. The key is meaningful only in the project
|
||||||
|
being invoked; a DEPENDENCY's `kind` is read but not enforced in v1
|
||||||
|
(iteration 15 already never uses a dep's `main`, so a program consumed as a
|
||||||
|
dep already behaves as a library — recorded, not policed).
|
||||||
|
|
||||||
|
### 2. Driver modes
|
||||||
|
|
||||||
|
- `woc <dir>` on a program: unchanged — build, requiring `main`.
|
||||||
|
- `woc <dir>` on a library: CHECK mode — the full pipeline runs (parse,
|
||||||
|
typecheck, interface satisfaction, borrow/ownership pass, GC inference)
|
||||||
|
over the whole project including its `[deps]`, and stops before image
|
||||||
|
emission. No entry is required. Exit 0 with no output on success, 1 with
|
||||||
|
diagnostics otherwise — a green check must mean exactly what a green
|
||||||
|
build means, minus the artifact. Deps are fetched/locked the same as
|
||||||
|
build mode (a library must be checkable offline once locked).
|
||||||
|
- `woc build <dir> -o <app>` on a library WITH a `main`: builds the binary
|
||||||
|
— the dual case (demo/self-test). Without a `main`: the existing
|
||||||
|
no-entry error, extended to name the kind so the message explains itself
|
||||||
|
("this project is a library; add a main or check it with woc <dir>").
|
||||||
|
- `--emit`, dump flags, `--update-deps`: unchanged; they never required an
|
||||||
|
entry or already carry their own rules.
|
||||||
|
|
||||||
|
### 3. The `internal/` rule
|
||||||
|
|
||||||
|
Where: the dep-use resolution step in `compile_image` — the same place
|
||||||
|
iteration 15 prefixes dep module paths — because that is the only spot that
|
||||||
|
knows which file belongs to which project. The rule: a `use` in a file that
|
||||||
|
does NOT belong to dependency X, naming a module of dependency X whose
|
||||||
|
dep-relative path contains a segment exactly equal to `internal`, is
|
||||||
|
rejected with WO-E108 at that `use`, naming the dependency and the module.
|
||||||
|
Files INSIDE dependency X importing the same module are untouched. Modules
|
||||||
|
are directory-shaped (`module_of_multi`: dep name + relative directory), so
|
||||||
|
"segment" means a path component — `framework/internal` and anything under
|
||||||
|
it, never a substring match (a module named `internals_x` is not caught).
|
||||||
|
|
||||||
|
Scope notes, normative: the rule fires only across the `[deps]` boundary
|
||||||
|
(decision 3 — dep-boundary-only; Go's subtree rule is a recorded possible
|
||||||
|
tightening). The root project's own `internal/` directories are legal to
|
||||||
|
import from anywhere inside the root project. Transitive deps stay
|
||||||
|
rejected by iteration 15's WO-E106, so dep-to-dep imports cannot occur.
|
||||||
|
|
||||||
|
### 4. Diagnostics (error catalog additions)
|
||||||
|
|
||||||
|
- **WO-E108** — dep-internal module imported across the `[deps]` boundary.
|
||||||
|
Points at the offending `use`, names the dependency and the internal
|
||||||
|
module, and says the module is internal to that dependency.
|
||||||
|
- **WO-E109** — invalid manifest `kind` value. Names the given value and
|
||||||
|
the two legal ones.
|
||||||
|
- The no-entry build error gains the library wording described in §2; no
|
||||||
|
new code, better message.
|
||||||
|
|
||||||
|
### 5. Framework reorg (the proof by use)
|
||||||
|
|
||||||
|
- `wo.toml` gains `kind = "library"`.
|
||||||
|
- `http/parse.wo` and `http/serve.wo` move to `internal/` (module
|
||||||
|
`framework/internal` when consumed). They are plumbing the web-app never
|
||||||
|
imports: the request parser, the carry-state record, the serve loop, the
|
||||||
|
`Dispatcher` seam.
|
||||||
|
- `http/types.wo` (Req/Resp + builders), `router/router.wo`
|
||||||
|
(Handler/Middleware/Route/Mw), and `app.wo` (App) stay where they are —
|
||||||
|
the public surface does not move.
|
||||||
|
- Intra-framework imports update to the new module path; the web-app
|
||||||
|
changes NOTHING — it already imports only `framework`, `framework/http`,
|
||||||
|
`framework/router`.
|
||||||
|
- The framework README's verification note replaces the `--emit`
|
||||||
|
workaround with the check-mode invocation.
|
||||||
|
|
||||||
|
### 6. Acceptance gate
|
||||||
|
|
||||||
|
Extend `scripts/web-app-accept.sh` (it already builds the framework remote
|
||||||
|
and the consuming app): the standing 14 checks stay, plus (a) check mode —
|
||||||
|
`woc` on the framework copy exits 0 with no entry present; (b) privacy — a
|
||||||
|
temp copy of the app with one added `use framework/internal` fails
|
||||||
|
compile and the output names WO-E108; (c) kind validation — a temp
|
||||||
|
manifest with a junk `kind` fails naming WO-E109. Compiler-side, the
|
||||||
|
corpus grows compile-fail fixtures for WO-E108/E109 and a run fixture is
|
||||||
|
not needed (no runtime behavior exists to pin — the VM is untouched by
|
||||||
|
design). Standing gates (`just woc-test`, `just deps-accept`,
|
||||||
|
`just oop-e2e`, samples) stay green.
|
||||||
|
|
||||||
|
## Out of scope (recorded, deliberate)
|
||||||
|
|
||||||
|
Go's full subtree rule; a `[lib]` manifest section; `pub(lib)`-style
|
||||||
|
keyword visibility; manifest export allowlists; multiple named binaries
|
||||||
|
per project (`cmd/` convention); enforcing a dep's own `kind`; internal
|
||||||
|
types in public signatures as a diagnostic; any VM, `.wob`, or GC change
|
||||||
|
(impact analysis in the iteration: visibility is compile-time name
|
||||||
|
resolution; libraries compile whole-program into the consumer's image;
|
||||||
|
GC inference stays whole-program and app usage may promote dep classes —
|
||||||
|
intended).
|
||||||
|
|
||||||
|
## Success criteria
|
||||||
|
|
||||||
|
1. **Given** the framework with `kind = "library"` and no `main`, **when**
|
||||||
|
`woc <dir>` runs, **then** it exits 0 having run the full pipeline, and
|
||||||
|
`woc build` on it fails with the message naming the kind.
|
||||||
|
2. **Given** the web-app importing `framework/internal`, **when** it
|
||||||
|
compiles, **then** WO-E108 points at the `use` and names the framework;
|
||||||
|
the framework's own files importing it stay legal.
|
||||||
|
3. **Given** the reorganized framework, **when** `just web-app` runs,
|
||||||
|
**then** all standing checks pass unchanged — the public surface did
|
||||||
|
not move.
|
||||||
|
4. **Given** any existing project with no `kind` key, **when** it builds,
|
||||||
|
**then** nothing changed — absent means program.
|
||||||
243
docs/superpowers/specs/2026-08-20-shard-fiber-arc-design.md
Normal file
243
docs/superpowers/specs/2026-08-20-shard-fiber-arc-design.md
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
# The 8+11 concurrency arc — shards, fibers, actors: design
|
||||||
|
|
||||||
|
> **Status: spec, awaiting review (2026-08-20).** The arc's decisions were
|
||||||
|
> settled in [iteration 8](../../stories/language-runtime-database/refine/08-shard-actor-runtime.md)
|
||||||
|
> (refined) and the brainstorm of 2026-08-20 (this document's Decisions).
|
||||||
|
> Covers iterations 8 AND 11 as one arc; iteration 24 (chat) is its
|
||||||
|
> acceptance workload and gets its own spec after this one. The plan
|
||||||
|
> follows after review. Board: [docs/00-status.md](../../00-status.md).
|
||||||
|
>
|
||||||
|
> Per repo convention: concept, reason, and required behavior in words
|
||||||
|
> only — no implementation code.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
The runtime scales past one core without ever showing a thread, a lock,
|
||||||
|
or a colored function: pinned thread-per-core shards whose only
|
||||||
|
communication is ownership-moving messages, cooperative fibers above them
|
||||||
|
preempted by reduction budget, blocking builtins that park instead of
|
||||||
|
block, and a database that stays single-writer by being runtime plumbing
|
||||||
|
on its owner shard. One `spawn`/`send` surface; app code byte-identical
|
||||||
|
at one shard or sixteen.
|
||||||
|
|
||||||
|
## Decisions (settled; the spec builds on them)
|
||||||
|
|
||||||
|
From the 2026-08-20 refinement: the DB is an actor on an owner shard
|
||||||
|
(never user-visible); 8+11 are one arc; one unified address surface;
|
||||||
|
chat (19) is the driving workload; order 22 → arc → 19 → 23.
|
||||||
|
|
||||||
|
From the arc brainstorm (2026-08-20):
|
||||||
|
|
||||||
|
1. **All cores by default.** `WO_SHARDS` / `[runtime] shards = N`
|
||||||
|
override (N=1 is the debug/serial escape hatch); the arc's landing
|
||||||
|
criterion is every standing gate green AT the default. Brave by
|
||||||
|
choice: the gates become the stress test.
|
||||||
|
2. **Typed receive.** An actor is any class with `fn receive(msg: M)` —
|
||||||
|
structural, the Handler doctrine, no blocking-receive keyword, no new
|
||||||
|
interface to implement. `spawn Cls { ... }` infers M from the class's
|
||||||
|
`receive` signature and returns an **`actor M`** address (a
|
||||||
|
parametric builtin type exactly like `multi T`/`map<K, V>`).
|
||||||
|
`send(addr, msg)` compile-checks that msg is an M and MOVES it.
|
||||||
|
3. **Transparent DB RPC.** insert/update/delete/query builtins executed
|
||||||
|
on a non-owner shard marshal the statement to the owner shard and
|
||||||
|
PARK the calling fiber until the reply; results come back
|
||||||
|
materialized (they already do). App code never changes; the fourth
|
||||||
|
iteration-8 criterion (byte-identical answers single- or multi-shard)
|
||||||
|
is the proof.
|
||||||
|
4. **Fibers first, then shards.** Stage 1 ships fibers on one shard
|
||||||
|
(framework's fiber-per-connection lands early, no threads); stage 2
|
||||||
|
ships pinned threads + cross-shard send; stage 3 ships the DB RPC and
|
||||||
|
re-runs 22. Each stage gates green independently.
|
||||||
|
|
||||||
|
## Part A — the fiber runtime (stage 1, one shard)
|
||||||
|
|
||||||
|
### The fiber context
|
||||||
|
|
||||||
|
A fiber IS the interpreter state `wo_vm` already isolates: the register
|
||||||
|
window (`WO_STACK_SLOTS`), the frame stack (`WO_MAX_FRAMES`), the catch
|
||||||
|
stack, the caught-error slot, and a resume point. Stage 1 extracts those
|
||||||
|
fields into a fiber context; `wo_vm` keeps the module, the runtime
|
||||||
|
(arena/GC/db — per SHARD, not per fiber), the current-fiber pointer, and
|
||||||
|
a FIFO run queue. Cost, stated: a context is ~42 KiB at today's
|
||||||
|
constants (32 KiB registers + frames + catches), so 1k fibers ≈ 42 MiB —
|
||||||
|
acceptable v1, arena-allocated; segmented/growable windows are recorded
|
||||||
|
future work, not v1.
|
||||||
|
|
||||||
|
### Scheduling
|
||||||
|
|
||||||
|
- FIFO run queue, no priorities (iteration 11's out-of-scope holds).
|
||||||
|
- **Preemption by reduction budget**: the budget decrements at the
|
||||||
|
EXISTING safepoint sites (NEW, CALL, backward-JMP — the same places
|
||||||
|
7b's GC polls, one counter check added, no new instrumentation); at
|
||||||
|
zero the fiber re-queues and the next runs. Default budget 4000
|
||||||
|
reductions, `WO_REDUCTIONS` overrides. Deterministic: same program,
|
||||||
|
same schedule.
|
||||||
|
- `main` is fiber 0. When it returns, the program's exit value is
|
||||||
|
main's; every other fiber — runnable or parked — is UNWOUND through
|
||||||
|
the drop machinery (the SIGTERM/stop story, reused verbatim): ASan
|
||||||
|
zero leaks is the criterion. A trap that unwinds out of a non-main
|
||||||
|
fiber kills that fiber alone (its drops run, its error goes to stderr
|
||||||
|
the uncaught-trap way); the program does not die.
|
||||||
|
- GC: every fiber's registers and frames are roots — `vm_gc_roots`
|
||||||
|
iterates all contexts, not just the live one. Parked and queued
|
||||||
|
fibers pin their values exactly like the running one.
|
||||||
|
|
||||||
|
### spawn / send (same-shard in stage 1)
|
||||||
|
|
||||||
|
- `spawn Cls { fields }` — an expression; Cls must declare
|
||||||
|
`fn receive(msg: M)` where M is a class, record, or union type
|
||||||
|
(**WO-E221** otherwise, naming what's missing); the ctor literal's
|
||||||
|
fields are the actor's state, moved in (the take-push shape). Result
|
||||||
|
type `actor M`.
|
||||||
|
- `send(addr, msg)`: msg must type as M (existing assign/param
|
||||||
|
machinery) and be OWNED (the owner pass's transfer rules; a borrowed
|
||||||
|
or copy-in-hand value is the existing WO-E3xx shape). After the send
|
||||||
|
the sender's binding is dead — compile-time move, the iteration-8
|
||||||
|
criterion.
|
||||||
|
- Delivery: the runtime calls the actee's `receive` with the message,
|
||||||
|
one message at a time per actor, on the actor's home shard, as its
|
||||||
|
fiber's next work item. An actor is a fiber that sleeps between
|
||||||
|
messages; its mailbox is the FIFO the runtime owns.
|
||||||
|
- Same-heap sends hand the pointer over — no copy, no serialization;
|
||||||
|
the move made aliasing impossible at compile time.
|
||||||
|
- `actor M` values are plain copyable words (an address), storable in
|
||||||
|
fields/containers like Int — sending TO a dead actor is a silent drop
|
||||||
|
of the message v1 (Erlang's shape; a delivery-receipt story is future
|
||||||
|
work, recorded).
|
||||||
|
|
||||||
|
### The I/O plane: io_uring-first (AMENDED 2026-08-20, developer directive)
|
||||||
|
|
||||||
|
The original wording made epoll the v1 loop with io_uring as a
|
||||||
|
benchmark-gated swap. That framing is REVERSED by directive, and the
|
||||||
|
[linux reference project](../../plan/exploration/linux/07-io_uring.md)
|
||||||
|
already pointed here: io_uring is the successor to epoll + libaio for
|
||||||
|
BOTH the storage engine's WAL path and the socket path — one event loop.
|
||||||
|
|
||||||
|
- **One io_uring per shard thread** is THE event loop, serving three op
|
||||||
|
families as the arc progresses: (1) fiber parking — `IORING_OP_POLL_ADD`
|
||||||
|
for parked net fds, `IORING_OP_TIMEOUT` for sleeps, the mailbox eventfd
|
||||||
|
registered the same way (stage 2's wake); (2) WAL durability — 23's
|
||||||
|
WRITE + FSYNC(DATASYNC) chains ride the SAME ring, `user_data` the
|
||||||
|
commit correlation, the shard executing while the ring drains; (3) the
|
||||||
|
data path — multishot accept/recv/send for iteration 24's connection
|
||||||
|
fan-in, LAST, because it changes resume semantics.
|
||||||
|
- **The readiness→ops ladder, staged honestly:** T4 uses the ring in
|
||||||
|
READINESS mode (POLL_ADD/TIMEOUT — resume re-executes the now-ready
|
||||||
|
builtin, the same retry contract epoll would have had, so the builtin
|
||||||
|
layer barely changes). Submitting the reads/writes THEMSELVES as ring
|
||||||
|
ops (completion carries the result, no retry) is the follow-up step —
|
||||||
|
bigger builtin surgery, staged after T4 proves the ring loop.
|
||||||
|
- **Kernel floor and ops discipline:** io_uring is Linux 5.1+, mature
|
||||||
|
5.11+; v1 uses only 5.1-safe ops (POLL_ADD, TIMEOUT, WRITE, FSYNC).
|
||||||
|
Multishot accept (5.19) / multishot recv (6.0) are recorded
|
||||||
|
optimizations for 19's spec, gated on the probe, never assumed.
|
||||||
|
- **The fallback is PORTABILITY, not preference:** a startup probe
|
||||||
|
(`io_uring_setup`, fall back on ENOSYS/EPERM — seccomp'd containers
|
||||||
|
routinely deny io_uring) selects the ring or the epoll+blocking-fsync
|
||||||
|
path; `WO_IO=uring|epoll` overrides so CI proves BOTH paths on one
|
||||||
|
kernel (this subsumes 23's `WO_WAL_MODE`). io_uring is the design;
|
||||||
|
epoll exists so the binary runs everywhere — the project's premise.
|
||||||
|
- Raw `io_uring_setup`/`io_uring_enter` syscalls, libc-only (23's settled
|
||||||
|
fork, now arc-wide); the mmap'd ring layout goes in the binding doc,
|
||||||
|
normative, exactly like the WAL format.
|
||||||
|
- `net.accept`/`net.read`/`net.write` and `time.sleep` PARK the calling
|
||||||
|
fiber against the ring; the shard runs other fibers; completion
|
||||||
|
re-queues the parked one. With exactly one fiber the ring wait IS the
|
||||||
|
blocking call — same code path, program mode is the one-fiber case.
|
||||||
|
- `fs.*` stays genuinely blocking in v1 (local disk, bounded); its reads
|
||||||
|
become ring ops in the ops stage if 22's numbers ask; recorded.
|
||||||
|
- The stop story composes: the stop signal wakes the ring (the existing
|
||||||
|
self-pipe/eventfd trick), every parked fiber unwinds — the SIGTERM
|
||||||
|
drain criterion extends to fibers for free.
|
||||||
|
- The framework consequence (its own slice, after stage 1): the serve
|
||||||
|
loop spawns a fiber per connection and the close-when-idle policy
|
||||||
|
dies; that lands with iteration 24's spec, not this one.
|
||||||
|
|
||||||
|
### Fiber context growth (recorded improvement, post-stage-1)
|
||||||
|
|
||||||
|
A fiber context is ~42 KiB today (fixed register window + frame array).
|
||||||
|
Unlike native-stack green threads (Go's grow-by-copy must rewrite every
|
||||||
|
pointer into the old stack), this VM's "stack" is register-INDEXED, not
|
||||||
|
address-based — a context is relocatable by construction, so growth is
|
||||||
|
an allocate-larger + memcpy with zero pointer fixups. Start-small
|
||||||
|
(~4 KiB) growable contexts are therefore cheap to add and take fiber
|
||||||
|
counts from thousands toward millions; scheduled after stage 1, before
|
||||||
|
iteration 24's 1k-connection target if measurement asks.
|
||||||
|
|
||||||
|
## Part B — shards (stage 2)
|
||||||
|
|
||||||
|
- One pinned pthread per shard; shard count = cores by default
|
||||||
|
(`WO_SHARDS`/manifest override). Each shard owns a full `wo_rt`:
|
||||||
|
arena, traced list, GC — 7b's collector is per-shard by construction,
|
||||||
|
so no global pause exists to remove.
|
||||||
|
- Cross-shard `send`: an MPSC mailbox ring per shard plus an eventfd to
|
||||||
|
wake an idle shard's epoll loop (the c-runtime exploration's proven
|
||||||
|
pair). The message POINTER crosses; the object's home shard is stamped
|
||||||
|
in its header (there since iteration 2); its eventual free routes back
|
||||||
|
to the allocation-home arena (a small home-free ring per shard,
|
||||||
|
drained at the tick).
|
||||||
|
- What may cross: OWNED values whose class transitively contains no
|
||||||
|
TRACED field — the `gc_may`-shaped fixpoint the runtime already
|
||||||
|
computes, surfaced at compile time: sending a value whose type is or
|
||||||
|
contains an inferred-traced class is **WO-E222**, naming the traced
|
||||||
|
class and why it is traced. Aliased graphs never cross heaps.
|
||||||
|
- `spawn` placement: round-robin across shards by default; no placement
|
||||||
|
argument in v1 (recorded future work if 22 shows a need). The
|
||||||
|
language never names a shard.
|
||||||
|
- Determinism criterion narrows honestly: single-shard runs stay
|
||||||
|
deterministic (stage 1's property); cross-shard interleaving is
|
||||||
|
nondeterministic by nature — the corpus asserts OUTPUT SETS and
|
||||||
|
ownership invariants under TSan, not byte-identical transcripts.
|
||||||
|
|
||||||
|
## Part C — the DB actor + serving (stage 3)
|
||||||
|
|
||||||
|
- The engine and WAL live on shard 0 (the owner). DB builtins executed
|
||||||
|
on the owner run exactly today's code. On any other shard they
|
||||||
|
marshal the statement into a runtime message, park the fiber, and
|
||||||
|
resume with the materialized reply. `transaction { }` (iteration 18)
|
||||||
|
marshals as one unit — the staged batch stays owner-side, semantics
|
||||||
|
unchanged.
|
||||||
|
- The listener: `net.listen` + `net.accept` stay on the shard that
|
||||||
|
calls them v1 (the framework serves from one accept loop; accepted
|
||||||
|
connections' fibers stay on that shard). Distributing accept
|
||||||
|
(SO_REUSEPORT per shard) is 19/23-adjacent future work the benchmark
|
||||||
|
must justify — recorded, not built.
|
||||||
|
- Stage 3 closes the arc: `just employee`/`just web-app` run
|
||||||
|
multi-shard byte-identical (criterion 4), the actor corpus runs under
|
||||||
|
ASan+TSan, and 22 re-runs with the before/after recorded.
|
||||||
|
|
||||||
|
## Diagnostics (new)
|
||||||
|
|
||||||
|
- **WO-E221** — `spawn` on a class with no `receive(msg: M)` (or an M
|
||||||
|
that is not a class/record/union).
|
||||||
|
- **WO-E222** — cross-shard send of a traced (or traced-containing)
|
||||||
|
type; names the class and the inference reason. Compile-time; stage 1
|
||||||
|
same-shard sends are exempt (same heap, aliasing is GC's problem and
|
||||||
|
GC handles it).
|
||||||
|
- Existing machinery covers the rest: send-of-borrowed is the owner
|
||||||
|
pass's transfer rules; send-type mismatch is the parameter boundary.
|
||||||
|
|
||||||
|
## Out of scope (arc-wide, restated)
|
||||||
|
|
||||||
|
Fiber migration across shards; priorities/timers/structured concurrency
|
||||||
|
(recipe-box, later `.wo` libraries); async/await (permanently rejected);
|
||||||
|
cross-shard transactions/2PC; SO_REUSEPORT accept distribution;
|
||||||
|
segmented fiber stacks; delivery receipts / actor supervision trees
|
||||||
|
(future story); program-mode API changes (none — one fiber is the
|
||||||
|
degenerate case).
|
||||||
|
|
||||||
|
## Success criteria
|
||||||
|
|
||||||
|
1. **Stage 1:** thousands of fibers on one shard, one hot loop among
|
||||||
|
them — everything progresses (starvation-free fixture), output
|
||||||
|
deterministic; a parked-fiber shutdown unwinds ASan-clean; a fiber
|
||||||
|
blocked in `net.read` resumes while the shard served others, one TID.
|
||||||
|
2. **Stage 2:** the actor corpus under ASan+TSan — zero races, zero
|
||||||
|
leaks; an owned send compiles as a move (sender's later use is a
|
||||||
|
compile error); a traced-containing send is WO-E222; frees route
|
||||||
|
home (asserted in debug builds).
|
||||||
|
3. **Stage 3:** `just employee` and `just web-app` green at the
|
||||||
|
all-cores DEFAULT with byte-identical answers; 22's benchmark re-run
|
||||||
|
with the delta recorded; every standing gate green.
|
||||||
|
4. The language grew exactly `spawn`, `send`, and the `actor M` type —
|
||||||
|
no async/await, no locks, no thread ever visible.
|
||||||
Loading…
Reference in a new issue