- README: shipped concurrency/HTTP/WebSockets sat in the roadmap as "not yet available"; "no package manager" contradicted [deps]; the deps example would not have compiled (the key IS the module name) - runtime/README: leads with wovm, wo-rt.c demoted to a historical section; dropped 2 nonexistent recipes, crates/rt, @gc refcounting, 13 suites -> 18 - employee + log-watcher READMEs claimed "does not compile"; both are gates - error catalog: +10 emitted codes incl WO-E250, the only diagnostic the shipped query surface raises; recorded why the sweep rotted - language-surface: group-by parses, then the typechecker refuses it - 00-code-review + 00-link-audit re-run; history kept, not rewritten - 48 dead Rust-era exploration links de-linked rather than re-pointed (their prose names the retired plan by number); successor map -> discarded.md - 08-project-structure: compiler/plan/ never existed; corpus has 9 dirs, 5 empty - releasing.md: dropped a --draft step the workflow never had - new docs/00-doc-audit.md: findings + disposition, incl one row where the audit was wrong and the doc it accused was right - status folders removed: 34 stories flat, status only in frontmatter; 252 links recomputed from resolved paths; board/board-views/structure retaught - story 24 -> in-progress, since frontmatter is now the only truth - new iteration 38: fs mutation verbs + net.connect, the two capability families no iteration owned - new iteration 39: gofiber/fiber v3.5.0 parity study. The ledger called CSRF/sessions unblocked by iteration 34's HMAC, but the runtime has no source of randomness at all - linkcheck skips .dev/.superpowers: 0 broken paths, 0 bad anchors Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
411 lines
20 KiB
Markdown
411 lines
20 KiB
Markdown
# Chat + actor lifecycle — implementation plan (iteration 24, absorbing 31 + 34)
|
||
|
||
> **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 chat workload proves the concurrency arc end to end —
|
||
`call`/monitor/timers/bounded mailboxes in the runtime, crypto builtins,
|
||
a pure-`.wo` WebSocket layer in the framework, and a rooms+presence chat
|
||
sample gated at 1k clients.
|
||
|
||
**Architecture:** everything reuses the arc's machinery — `call` rides
|
||
the DB-RPC envelope+park pair, timers ride the T4 timeout plumbing,
|
||
death rides the existing trap-unwind path. The framework never spawns
|
||
app classes: the app's handler owns the upgrade and moves the fd (Int)
|
||
into its own reader/writer actor pair. See the spec (normative):
|
||
[`../specs/2026-08-23-chat-websocket-actor-lifecycle-design.md`](../specs/2026-08-23-chat-websocket-actor-lifecycle-design.md).
|
||
|
||
**Tech Stack:** C11 libc-only (`wovm`), OCaml stdlib-only (`woc`),
|
||
pure-`.wo` framework code, bash + python3-stdlib gates.
|
||
|
||
## Global Constraints
|
||
|
||
- Branch `chat-ws-lifecycle` (this one); commits local only, never push.
|
||
- ALWAYS `just woc-build && just wovm-build` before any gate run — gate
|
||
scripts require built binaries and never rebuild (stale-binary
|
||
incident, 2026-08-22).
|
||
- A stage does not start until the previous stage's full battery is
|
||
green: `just woc-test wovm-test oop-e2e deps-accept web-app
|
||
log-watcher employee fibers db-actor db-bench-quick` (run as separate
|
||
recipes).
|
||
- New builtin ids: 85 `crypto.sha1`, 86 `crypto.sha256`,
|
||
87 `crypto.hmac_sha256`, 88 `call`, 89 `monitor`, 90 `time.after`.
|
||
`WO_B_MAX` follows. NO `.wob` version bump (ticks-84 precedent: pure
|
||
id additions; old runtimes reject on the id-range check).
|
||
- New trap kind: `WO_T_ACTOR = 13`. New diagnostic: WO-E226.
|
||
- No new opcodes, no new keywords — `call`/`monitor`/`time.after` are
|
||
builtins resolved like `spawn`/`send`/`time.sleep`.
|
||
- Plain-HTTP serving stays byte-identical throughout — `just web-app`
|
||
is the canary in every stage.
|
||
|
||
## Spec refinements (disclosed, decided here)
|
||
|
||
1. **Reply-type erasure rule.** `actor M` does not name the class, so
|
||
`call`'s static type comes from a program-wide agreement check:
|
||
every `receive(msg: M) -> R` for a given M must declare the same R;
|
||
two classes disagreeing is WO-E226 naming both. A `receive` with no
|
||
return type makes `call` on that M a WO-E226 at the call site.
|
||
2. **Cross-shard cap check.** The mailbox cap is enforced through a
|
||
per-actor ATOMIC queue-length counter readable from any shard;
|
||
send/call check it before enqueue and trap `WO_T_ACTOR` when at cap.
|
||
Racing senders can overshoot by at most the number of in-flight
|
||
sends — bounded, disclosed; RSS stays flat under the soak.
|
||
3. **Runtime-sourced deliveries** (monitor notices, timer messages)
|
||
have no fiber to trap: delivery to a full mailbox is dropped with a
|
||
stderr diagnostic naming both actors (spec's monitor wording,
|
||
applied to timers too).
|
||
|
||
---
|
||
|
||
## Stage 1 — crypto builtins (Part B; independent, smallest risk)
|
||
|
||
### Task 1 — sha1 / sha256 / hmac_sha256
|
||
|
||
**Files:**
|
||
- Create: `runtime/src/crypto.c` (the three digests, hand-rolled,
|
||
libc-only — one file, shares the block-schedule skeleton)
|
||
- Modify: `runtime/src/wob.h` (ids 85–87, `WO_B_MAX 87`, doc comments
|
||
in the builtin roster), `runtime/src/builtin.c` (forward the id range
|
||
to the crypto entry point), `runtime/Makefile` (new object)
|
||
- Modify: `compiler/src/types.ml` (the `crypto.sha1|sha256|hmac_sha256`
|
||
names → ids, arity/typing: Bytes→Bytes and Bytes,Bytes→Bytes —
|
||
follow exactly how `base64.encode`-family names map)
|
||
- Create: `runtime/test/test_crypto.c` (RFC vectors),
|
||
`tests/corpus/run/crypto-digests/` fixture pair
|
||
- Modify: `docs/plan/oop-vm/08-builtin-surface.md` (three rows)
|
||
|
||
**Interfaces:**
|
||
- Produces: builtins callable from `.wo` as `crypto.sha1(b)`,
|
||
`crypto.sha256(b)`, `crypto.hmac_sha256(key, msg)`, each returning
|
||
fresh Bytes; C entry `wo_builtin_crypto(vm, R, ins, msg)` consumed by
|
||
`builtin.c`'s dispatch. Task 6 consumes `crypto.sha1` from `.wo`.
|
||
|
||
- [ ] Create the board marker `docs/active-slice-2026-08-23-chat-ws-lifecycle.md`
|
||
(slice active, links to spec+plan) and flip the board's In-progress
|
||
Runtime row to this slice. Commit with the first code commit.
|
||
- [ ] Digest cores in `crypto.c`: SHA-1 and SHA-256 over one buffer
|
||
(init/update-once/final collapsed — whole-value contract), HMAC as
|
||
the RFC 2104 two-pass over SHA-256. Wrong-class-id argument traps
|
||
WO_T_BOUNDS with the same message shape the Bytes builtins use.
|
||
- [ ] `test_crypto.c`: RFC 3174 SHA-1 vectors ("abc", the 56-byte
|
||
chaining case, empty input), FIPS 180-4 SHA-256 vectors (same
|
||
three), RFC 4231 HMAC cases 1–4, plus a 63/64/65-byte block-boundary
|
||
sweep asserting against python3 hashlib-precomputed constants (put
|
||
the generator one-liner in a comment). Wire into `make -C runtime test`.
|
||
- [ ] Verify: `just wovm-build && just wovm-test` green (ASan+UBSan
|
||
stage included).
|
||
- [ ] Compiler surface + corpus fixture: a `.wo` program hashing "abc"
|
||
through all three and printing base64 of each (exercises 19's
|
||
encode); expected output = precomputed. Verify: `just woc-build &&
|
||
just woc-test && just oop-e2e`.
|
||
- [ ] Full battery. Commit (bullets: ids 85–87, vectors, surface doc).
|
||
|
||
## Stage 2 — actor lifecycle (Part A)
|
||
|
||
### Task 2 — bounded mailboxes + WO_T_ACTOR
|
||
|
||
**Files:**
|
||
- Modify: `runtime/src/wob.h` (trap kind 13 + roster comment),
|
||
`runtime/src/vm.h` (per-actor atomic queue length, the cap constant,
|
||
`WO_MAILBOX` plumbing), `runtime/src/vm.c` (check in the same-shard
|
||
enqueue AND in `inbox_push_to`'s caller path before the envelope is
|
||
built; trap message names the actor and the cap)
|
||
- Create: `runtime/test/test_mailbox.c`,
|
||
`tests/corpus/run/mailbox-full-trap/` (WO_MAILBOX=4 in its runner
|
||
env, sender catches the trap and prints proof)
|
||
|
||
**Interfaces:**
|
||
- Produces: `WO_T_ACTOR` trap reachable from `.wo` via try/catch on
|
||
send; the atomic length counter Task 3's call path reuses.
|
||
- Consumes: arc stage-2 mailbox/inbox structures as they are (mutex
|
||
list stays — no ring rewrite, spec's out-of-scope).
|
||
|
||
- [ ] Cap default 1024; `WO_MAILBOX` env override parsed once at engine
|
||
start (same pattern as `WO_SHARDS`). Counter increments at enqueue,
|
||
decrements when receive DEQUEUES (not when it finishes).
|
||
- [ ] `test_mailbox.c`: fill to cap, next send returns the trap;
|
||
dequeue one, send succeeds; two threads racing the last slot never
|
||
lose a message and never exceed cap + in-flight (assert the bound,
|
||
not exactness).
|
||
- [ ] Corpus fixture: catchable trap proven from `.wo`; deterministic
|
||
under `WO_SHARDS=1`.
|
||
- [ ] Verify: `just wovm-build && just wovm-test && just oop-e2e`,
|
||
then full battery (unchanged cap = no behavior change anywhere
|
||
else — `just fibers` and `just db-actor` are the canaries).
|
||
- [ ] Commit.
|
||
|
||
### Task 3 — call / reply
|
||
|
||
**Files:**
|
||
- Modify: `runtime/src/wob.h` (id 88), `runtime/src/vm.c` (envelope
|
||
kinds 5 request / 6 reply generalizing the DB pair: kind-5 carries
|
||
caller shard+fiber and the moved message; adoption runs the actor's
|
||
receive for it and ships kind-6 with the moved return value; the
|
||
caller parks `WO_PARK_INBOX` and re-executes the builtin to consume
|
||
the reply — mirror `wo_db_rpc`'s shape), `runtime/src/builtin.c`
|
||
(the call case), `runtime/src/vm.h` (pending-call bookkeeping on the
|
||
fiber)
|
||
- Modify: `compiler/src/types.ml` (WO-E226: the program-wide
|
||
`receive(M) -> R` agreement table, call-site typing `call(actor M,
|
||
M) -> R`, call-on-void-receive error), `compiler/src/emit.ml`
|
||
(lower to id 88 — same shape as send), `compiler/src/diag.ml`
|
||
(E226 text)
|
||
- Create: corpus `run/call-echo` (same-shard round trip, TID-printed
|
||
park proof), `run/call-cross-shard` (output-set assertion),
|
||
`run/call-dead-trap` (call after callee trap-died → caught
|
||
WO_T_ACTOR; callee dies mid-call → caught), `compile-fail/call-void-receive`,
|
||
`compile-fail/call-reply-disagree` (two classes, same M, different R)
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 2's atomic length check (call is a send first).
|
||
- Produces: `call(addr, msg) -> R` callable from `.wo`; the
|
||
kind-5/6 envelope pair; death-unparks-caller hook that Task 4's
|
||
death machinery triggers. Task 8's registry lookups consume `call`.
|
||
|
||
- [ ] Runtime first (unit-provable without the compiler): kind-5/6
|
||
paths + park/resume + dead-target immediate trap + die-mid-call
|
||
unpark-to-trap. Extend `runtime/test/test_fiber.c` with a
|
||
hand-built call round trip and a die-mid-call case (ASan).
|
||
- [ ] Compiler: the agreement table is built in the same pass that
|
||
already collects `receive` signatures for spawn/WO-E221; E226 fires
|
||
on disagreement (both class names in the message) and on
|
||
call-of-void. Reply values face the same WO-E222
|
||
traced-containment check as send arguments — extend that check to
|
||
receive RETURN types reachable via call, at the receive site.
|
||
- [ ] Corpus fixtures above; ASan AND TSan on the actor corpus
|
||
(`just fibers` carries the TSan lane).
|
||
- [ ] Verify: `just woc-test && just oop-e2e && just fibers &&
|
||
just db-actor`, then full battery. Commit.
|
||
|
||
### Task 4 — monitor
|
||
|
||
**Files:**
|
||
- Modify: `runtime/src/wob.h` (id 89), `runtime/src/vm.c` (per-actor
|
||
monitor list: observer address + the moved notice message; the
|
||
fiber-trap unwind path that already isolates actor death walks the
|
||
list and delivers each notice as an ordinary send from runtime
|
||
context — full observer = drop + stderr line naming both actors;
|
||
monitor-of-dead delivers immediately; also unpark any caller parked
|
||
in a kind-5 call on the dying actor into WO_T_ACTOR — closing
|
||
Task 3's hook), `runtime/src/builtin.c` (the monitor case)
|
||
- Modify: `compiler/src/types.ml` (arity/typing: `monitor(actor M2,
|
||
msg: M1)` where M1 is the OBSERVER's mailbox type — the msg argument
|
||
is typed against the observer address's M... the observer is the
|
||
CALLER: v1 rule, monitor's msg must be the type some actor the
|
||
caller names receives; concretely `monitor(watched, observer, msg)`
|
||
three-argument form so the target mailbox is explicit and typed),
|
||
`compiler/src/emit.ml`
|
||
- Create: corpus `run/monitor-death` (watched actor traps; observer
|
||
prints the notice; deterministic at WO_SHARDS=1),
|
||
`run/monitor-already-dead`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 3's pending-call bookkeeping (die-mid-call unpark).
|
||
- Produces: `monitor(watched, observer, msg)` from `.wo`; the death
|
||
walk Task 8's rooms use to drop dead members.
|
||
|
||
- [ ] Note the spec deviation and disclose it in the commit: the spec
|
||
wrote two-argument monitor with the caller as implicit observer, but
|
||
the caller of `monitor` may be plain `main` (no mailbox) — the
|
||
three-argument form names the observer address explicitly and stays
|
||
fully typed. Story banner records it at closeout.
|
||
- [ ] Runtime + compiler + fixtures as above.
|
||
- [ ] Verify: `just oop-e2e && just fibers`, full battery. Commit.
|
||
|
||
### Task 5 — time.after
|
||
|
||
**Files:**
|
||
- Modify: `runtime/src/wob.h` (id 90), `runtime/src/sysio.c` or
|
||
`runtime/src/park.c` (whichever owns the T4 deadline scan — arm a
|
||
timer entry: deadline + target address + moved message; expiry
|
||
delivers as an ordinary runtime send: full mailbox = drop +
|
||
diagnostic, dead target = existing silent-drop),
|
||
`runtime/src/vm.h` (the shard's timer list), `compiler/src/types.ml`
|
||
+ `emit.ml` (`time.after(ms, addr, msg)`)
|
||
- Create: corpus `run/timer-delivery` (arm 30ms, actor prints on
|
||
receipt, main outlives it — loose ordering like the fibers demo
|
||
part 2), `run/timer-generation` (the documented cancel idiom: arm
|
||
two, bump the generation, prove the stale one is ignored)
|
||
|
||
**Interfaces:**
|
||
- Produces: `time.after(ms, addr, msg)`; Task 8's presence/ping logic
|
||
consumes it.
|
||
|
||
- [ ] Timer list lives on the ARMING fiber's shard and rides that
|
||
shard's existing io_uring/epoll timeout arm — no new wait machinery;
|
||
delivery crosses shards through the normal envelope path when the
|
||
target lives elsewhere.
|
||
- [ ] Verify: `just oop-e2e && just fibers` on BOTH `WO_IO` backends
|
||
(the fibers gate already forces both), full battery. Commit.
|
||
**Stage 2 complete: board note.**
|
||
|
||
## Stage 3 — framework WebSocket (Part C)
|
||
|
||
### Task 6 — upgrade seam (ws_accept + hijack)
|
||
|
||
**Files:**
|
||
- Modify: `docs/examples/writeonce-framework/http/types.wo` (Req grows
|
||
the internal conn handle; internal-only — document it as not public
|
||
surface), `docs/examples/writeonce-framework/internal/serve.wo`
|
||
(pass the conn into Req; recognize the hijack sentinel — the
|
||
Resp status 101 — and neither serialize nor close, just return to
|
||
accept), `docs/examples/writeonce-framework/internal/parse.wo` (no
|
||
behavior change — only whatever plumbing Req's new field needs)
|
||
- Create: `docs/examples/writeonce-framework/http/ws.wo` — upgrade
|
||
validation (RFC 6455 §4.2.1: method GET, `Upgrade: websocket`,
|
||
`Connection` contains upgrade, `Sec-WebSocket-Version: 13`, the key
|
||
header present), accept-key = `base64.encode(crypto.sha1(key ++
|
||
GUID))` with the RFC GUID constant, `ws_accept(req)` writes the 101
|
||
with the computed key and returns the fd; malformed upgrade returns
|
||
nil (handler answers a plain 400 — nothing traps)
|
||
- Modify: framework probe scripts (whichever pattern auth.wo's 26-case
|
||
matrix uses — add handshake cases: the RFC 6455 worked example key
|
||
`dGhlIHNhbXBsZSBub25jZQ==` must produce
|
||
`s3pPLMBiTxaQ9kYGzzhZRbK+xOo=`, plus each missing-header rejection)
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 1's `crypto.sha1`, builtin 80 base64.
|
||
- Produces: `ws_accept(req) -> ?Int` (nil = not a valid upgrade) and
|
||
the 101-sentinel contract with serve.wo; Task 8's `/ws` handler
|
||
consumes both.
|
||
|
||
- [ ] Verify handshake probe vector; then `just web-app` (the
|
||
byte-identical canary — no HTTP behavior may move) and `just
|
||
deps-accept`. Full battery. Commit.
|
||
|
||
### Task 7 — frame codec (pure `.wo`)
|
||
|
||
**Files:**
|
||
- Create: `docs/examples/writeonce-framework/http/wsframe.wo` — parse
|
||
one frame from Bytes (fin/opcode/mask/len; 7-bit and 16-bit lengths;
|
||
64-bit length → a close verdict; unmasking via bitwise XOR;
|
||
fragmented data frames → close verdict; control frames legal between
|
||
data frames), serialize text/close/ping/pong (server frames
|
||
unmasked, per RFC), and an incremental feeder shape: a carry buffer
|
||
so a reader can accumulate `net.read` chunks and pull complete
|
||
frames — mirrors parse.wo's carry convention
|
||
- Create: codec probes (fixture-style like the multipart probes):
|
||
masked "Hello" round trip (the RFC 6455 example bytes), 16-bit
|
||
length boundary at 126, oversize close verdict, ping between
|
||
fragments of nothing (control-frame interleave), torn-buffer
|
||
reassembly across three feeds
|
||
|
||
**Interfaces:**
|
||
- Produces: frame parse/serialize functions over Bytes + the carry
|
||
convention; Task 8's reader/writer consume them. No fd, no actor —
|
||
pure functions.
|
||
|
||
- [ ] Verify probes + full battery (framework compiles = deps gates).
|
||
Commit.
|
||
|
||
## Stage 4 — the chat sample (Part D)
|
||
|
||
### Task 8 — docs/examples/chat
|
||
|
||
**Files:**
|
||
- Create: `docs/examples/chat/wo.toml` ([deps] on the framework — copy
|
||
web-app's shape), `docs/examples/chat/main.wo` (or a small module
|
||
split if main crowds 200 lines: `actors.wo` for
|
||
registry/room/reader/writer classes)
|
||
- Registry actor: map name → room address; a Lookup request answered
|
||
through `call` (the first honest consumer — reply is the room
|
||
address, a scalar). Rooms spawned on demand.
|
||
- Room actor: members = multi of writer addresses; Join/Leave add and
|
||
remove + broadcast presence lines; a text message broadcasts to all
|
||
writers; a full writer mailbox trap at broadcast = that member is
|
||
dropped (catch, remove, close) — the backpressure policy earning its
|
||
keep; `monitor` on writers so a died writer leaves the room.
|
||
- Reader actor: owns the fd read loop with wsframe's carry; text →
|
||
room; ping → writer sends pong; close/EOF → Leave to room, Close to
|
||
writer.
|
||
- Writer actor: sole fd writer; Text/Presence/Pong/Close messages →
|
||
serialized frames; Close also closes the fd.
|
||
- main: spawn registry, register the `/ws` handler (query params name
|
||
the room and user; `ws_accept`; spawn reader+writer with fd +
|
||
addresses; Join via the room), `/` answers JSON usage; serve; on
|
||
`env.stopping()` the serve loop returns — main sends Shutdown
|
||
through registry → rooms broadcast close → writers flush close
|
||
frames — then main returns (the reap).
|
||
|
||
**Interfaces:**
|
||
- Consumes: everything Tasks 1–7 produced, by exact name.
|
||
- Produces: the running sample Task 9 gates.
|
||
|
||
- [ ] Manual smoke: build, connect with the Task 9 python client
|
||
prototype, two clients two shards, exchange lines. Verify
|
||
`WO_SHARDS=1` byte-order determinism for the single-client script.
|
||
- [ ] Commit (sample alone — the gate lands next so a reviewer can
|
||
run the sample by hand first).
|
||
|
||
### Task 9 — the chat gate
|
||
|
||
**Files:**
|
||
- Create: `scripts/chat-accept.sh` + `scripts/ws_client.py`
|
||
(python3 stdlib only: socket, base64, hashlib, os, threading —
|
||
speaks the handshake with an independently computed accept-key
|
||
check, masks client frames, reads server frames)
|
||
- Modify: `justfile` (`chat` recipe), `scripts/` battery docs if the
|
||
repo lists gates anywhere beside the justfile
|
||
|
||
**Checks (the spec's five, exactly):**
|
||
1. functional: two clients, one room, cross-shard TID assert, third
|
||
client in another room silent;
|
||
2. handshake: the RFC worked-example key verified by the client
|
||
itself;
|
||
3. soak: 1k clients, one hot room, every room progresses; RSS bound
|
||
asserted; a `WO_MAILBOX=8` sub-run proving the drop-slow-member
|
||
path fires and the room survives;
|
||
4. drain: SIGTERM with connected clients → close frames observed →
|
||
exit 0; repeated under `WO_IO=uring` and `WO_IO=epoll`, once under
|
||
the ASan build (zero leaks);
|
||
5. `just web-app` byte-identical plus the full battery green.
|
||
|
||
- [ ] Verify: `just chat` 5/5 at default cores AND `WO_SHARDS=1`.
|
||
- [ ] Full battery. Commit. **Stage 4 complete.**
|
||
|
||
## Stage 5 — closeout
|
||
|
||
### Task 10 — docs, stories, board, graph
|
||
|
||
- [ ] Stories: 24 → `status: done` with the landing banner (what landed, gate
|
||
numbers, the monitor three-argument deviation, the reply-agreement
|
||
rule); 31 → `status: done` with a banner saying it landed INSIDE 24 (the
|
||
four forks and their decisions, link to the spec); 34 → `status: done`
|
||
(C-builtin resolution, ids, vectors). Frontmatter status + folder
|
||
move together (house rule).
|
||
- [ ] Board: In-progress row cleared (marker doc deleted), Landed
|
||
entries standup-shaped (the six questions), chain note: next is 23
|
||
(io_uring group-commit) with 22's numbers in hand.
|
||
- [ ] Graph: PUBSUB2/KEEPAL-adjacent nodes — PUBSUB2 done; CRYPTO gate
|
||
done (SHA/ETag row unblocked, not built); framework README ledger:
|
||
WebSocket/pub-sub rows ✅, ETag row's gate cleared, cancellation row
|
||
unblocked-not-built; `media_type`/streaming rows untouched.
|
||
- [ ] CODE-LOGIC files: `runtime/src/CODE-LOGIC.md` (lifecycle
|
||
section: call envelopes, cap counter, monitor walk, timer list;
|
||
crypto section: one paragraph, vectors pointer),
|
||
`docs/examples/chat/CODE-LOGIC.md` (actor topology, the
|
||
two-actors-per-connection reason, shutdown choreography).
|
||
- [ ] Full battery once more after doc edits. Commit.
|
||
|
||
## Success criteria
|
||
|
||
The spec's four, verbatim: chat gate 5/5 both shard counts; crypto
|
||
vectors + independent-client handshake; the four lifecycle proofs
|
||
pinned; full battery green with zero language growth (builtins only).
|
||
|
||
## Self-review notes
|
||
|
||
- Spec coverage: Part A → T2–T5, Part B → T1, Part C → T6–T7,
|
||
Part D → T8–T9, diagnostics WO-E226 (T3) / WO_T_ACTOR (T2–T4),
|
||
closeout obligations → T10.
|
||
- Two spec deviations pre-disclosed: monitor's three-argument form
|
||
(T4) and the reply-agreement rule + atomic cap counter (header).
|
||
- Names used consistently: `crypto.sha1/sha256/hmac_sha256`, `call`,
|
||
`monitor(watched, observer, msg)`, `time.after(ms, addr, msg)`,
|
||
`WO_T_ACTOR`, `WO-E226`, `WO_MAILBOX`, `ws_accept`, ids 85–90.
|
||
- Riskiest surgery is T3 (typing through erasure) — it sits behind two
|
||
green stages and its compile-fail fixtures are written with it.
|