writeonce/docs/stories/language-runtime-database/31-actor-lifecycle.md
shoney.arickathil 1fe808b7a4 docs(stories): add readiness, retire status: refine, sweep all 47 iterations
- `readiness: ready | refine` is a SECOND axis, orthogonal to status.
  `ready` = the brainstorm is complete and the decisions are LOCKED (a spec
  approved, or the forks explicitly confirmed). `refine` = open forks remain
  and it cannot be planned yet
- `status: refine` RETIRED because it carried both meanings at once, so a held
  iteration with an approved spec (language 18, 26) was indistinguishable from
  one nobody had thought about. status is now purely where the WORK is:
  done | in-progress | pending | hold — `pending` was already the board's own
  rendering word, so nothing new was invented
- all 47 iterations classified from EVIDENCE in their own text, not by guess:
  "the four forks are SETTLED" / "spec + plan approved" / "Approved spec:" for
  ready; "Forks the spec must settle" / "no spec exists yet" for refine. Every
  shipped iteration is ready by definition. 19 done, 5 in-progress, 15
  pending, 8 hold; 27 ready, 20 refine
- two iterations moved refine -> in-progress rather than -> pending: language
  31 and 34 are absorbed into 24 and work on them is literally happening, which
  the board already showed as 🔄 while their frontmatter said otherwise. That
  disagreement is now gone
- board legend, board-views' frontmatter contract, and two new Dataview
  queries updated — the useful one being `readiness: ready AND status:
  pending`, the startable set

WHAT THE NEW AXIS IMMEDIATELY SURFACED: of 15 pending iterations, exactly ONE
is startable — databasev2 4, io_uring group-commit, whose forks were confirmed
settled 2026-08-20. Everything else pending needs a brainstorm first. That was
invisible while one key carried both meanings, and it is now on the board.

Also caught by the sweep, unrelated to readiness but found by cross-checking
frontmatter against the board: SIX duplicate rows. Every iteration moved into
databasev2 was still listed in the LANGUAGE pending table under its retired id
(23, 32, 33, 20, 21, 27) as well as its new one. Stale copies removed. And two
databasev2 rows made claims the sweep contradicts — iteration 1 was billed
"startable today" while its forks are open, and 6 still called itself the
ceiling-raiser after 2 took that role.

Docs only. linkcheck 0 broken / 0 anchors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 16:54:45 +02:00

113 lines
5 KiB
Markdown

---
iteration: "31"
status: in-progress
readiness: ready
chain: 3
---
# Iteration 31 — actor lifecycle: request/response, backpressure, death, timers
> Format: `product/story-iteration-template`. Part of
> [Story — one language, one runtime, one database, one binary](00-story.md).
>
> **Inserted 2026-08-21** (concurrency-chain re-sequence; the iteration
> was named as "new 31" in the 2026-08-20 code-review re-sequence — this
> is its story file). Third in the chain,
> **stage 3 → 22 → 31 → 24 → 23 → 32**: chat
> ([iteration 24](24-chat-websocket-workload.md)) cannot be written
> honestly without these four mechanisms.
## Why this iteration exists
The arc's stages 1+2 shipped `spawn`/`send` mechanism without lifecycle:
`send` is one-way and callers `sleep`-poll to await an answer; the
mailbox FIFO grows without bound (a hot sender can exhaust a shard's
memory); an actor that traps dies silently (nobody learns, nothing
restarts, its mailbox rots); and there is no timer surface beyond a
fiber blocking in `time.sleep`. Every real serving program — chat first —
is made of request/response turns, bounded queues, death notices, and
deadlines. Without this iteration the arc is a demo, not a runtime.
## Goals
- **Request/response over one-way sends.** A caller can send and park
until the reply arrives — one surface, no `sleep`-polling, no second
concurrency vocabulary. Ownership rules unchanged: the request moves,
the reply moves back.
- **Bounded mailboxes with a stated backpressure policy.** A mailbox has
a cap; what happens at the cap (park the sender vs error) is decided by
the spec, one policy, doctrine-pure — no silent unbounded growth
anywhere in the runtime.
- **Actor death is observable.** A trap or normal exit produces a signal
another actor can receive; a fiber-trap already isolates (stage 1) —
this makes the fact of death deliverable, so a supervisor CAN be
written in `.wo`.
- **Timers as messages.** A deadline or interval delivers to a mailbox
like any other send, riding the shard's existing io_uring/epoll
timeout plumbing (arc T4) — no new event loop.
## Acceptance Criteria (draft — the spec refines)
- What to achieve?
- **Given** an actor that answers requests,
- **when** a caller awaits the reply,
- **then** the caller's fiber parks (the shard serves other fibers,
TID-verified), resumes with the moved reply, and never busy-waits.
- What to achieve?
- **Given** a mailbox at its cap,
- **when** another send arrives,
- **then** the stated backpressure policy fires deterministically,
memory stays bounded (RSS flat under a hot-sender soak), and no
message is silently dropped.
- What to achieve?
- **Given** an actor that traps mid-message,
- **when** it dies,
- **then** its drop maps run (ASan zero leaks), a death signal
reaches the observer that asked for one, and a supervisor written
in `.wo` can respawn it.
- What to achieve?
- **Given** a timer armed by an actor,
- **when** it fires,
- **then** the actor receives it as an ordinary message on its own
shard, and cancelling before expiry means it never delivers.
## Out Of Scope
- Supervision TREES / OTP-scale restart policy — a `.wo` library once
death signals exist, not runtime policy.
- Priorities and custom scheduling — the reduction budget stays the only
fairness mechanism.
- Distributed (cross-process) supervision — no network layer exists.
- Changing the ownership-move rule or the unified address surface —
iteration 8's decisions stand.
## Info
Forks the spec must settle:
1. **The request/response surface.** A reply-address baked into the
message shape vs a runtime-level call that parks — and what the
compiler checks (does a request type name its reply type?).
2. **The backpressure policy at the cap.** Park the sender (natural with
fibers, risks deadlock cycles) vs fail the send (explicit, pushes
handling to the program). One policy, stated; not configurable per
mailbox in v1.
3. **The death-signal shape.** Erlang's link (bidirectional, dies
together) vs monitor (one-way notice) — likely monitor-only v1.
4. **The timer surface.** Builtin (`time.after` delivering a message) vs
actor-spawned sleeper fiber — and cancellation semantics.
Sources: the 2026-08-20 code-review findings (the gaps this iteration
answers), the arc plan's stage-2 deviations
([`2026-08-20-shard-fiber-arc.md`](../../superpowers/plans/2026-08-20-shard-fiber-arc.md)
— the unbounded FIFO is deviation 4's mutex inbox), and BEAM precedent
already surveyed in
[`docs/plan/exploration/fibers/00-fibers.md`](../../plan/exploration/fibers/00-fibers.md).
## Proposed Solution
Brainstorm → spec → plan after the arc's stage 3 lands and iteration 22
has its baseline (the mailbox-cap and inbox-ring decisions want 22's
mutex number). The spec is written against iteration 24's needs — chat
names the lifecycle mechanisms it consumes, this iteration names chat as
its first honest consumer.