sessions (3):
- six decisions: pure-auth-primitive row (no payload bag); wall-clock
time.now not monotonic time.ticks (restart durability); login always
mints a fresh id (fixation, no anon-session model); throttled last_seen
touch at idle/20 (not a WAL write per request); Session writes
req.principal; config refuses absolute < idle
- finding: no per-key actor pool, so NOT blocked on lang-41 (plain @table
CRUD, same path storefront uses); the no-bag rule closes the one place
fiber's Set(key,any)+msgp+RegisterType would have hit principle 13
csrf (4):
- five decisions: fiber's hybrid transport (session-stored CsrfToken
@table + double-submit cookie, both must pass; no CSRF for sessionless
apps); opt-in single-use (checkout example); double-click -> distinct
SPENT refusal, NOT coupled to lang-41-blocked idempotency; trusted
origin/referer/Sec-Fetch-Site second layer; refusal classes distinct in
logs, opaque in body
- no actor pool, not blocked on lang-41
both validated against .dev/reference/fiber (v3, 3ca9a9d); exactly ZERO
language enhancement needed beyond iteration 2's random_bytes. Board synced.
(cherry picked from commit 3a4fb4215b23d2516362e2dd0acc5bec6c9aebc0)
- five forks locked: cookies: multi SetCookie beside unchanged headers
map; bare-name random_bytes(n)->Bytes; structural-400 in parse_request
+ on-demand cookie() helper; base64(value).base64(mac) signing;
app-supplied key, no middleware (that is iteration 3)
- validated against .dev/reference/fiber (v3, 3ca9a9d): exactly ONE
language enhancement needed (the CSPRNG); repeated Set-Cookie, cookie
attributes, parsing and signing all map to existing primitives
- corrects phase A registry: random_bytes joins the crypto-family
bare-name table (emit.ml b_* + types.ml), NOT wob.h's module enum;
next free id 84/90, not 110
- board: story 2 marked ready, porch-2 row rewritten off the stale
wob.h/110 claim
(cherry picked from commit 4d31d5359436496aed40cb25611abc7ccd4d7875)
- five stories status: done; 00-story records the one-run landing
- spec History: three implementation amendments (Signal record not
scalar, caller-owned stdio fds, handler-latch instead of signalfd)
- board NEXT PLAN entry with measured findings (zero transport code
added; the tty-across-the-socket handover proven; the double-raw
refusal restoring the terminal — the "bug" that was the design
working); section rows flipped; graph nodes green
- CODE-LOGIC.md: the runtime-v2 section
- full belt quoted on the board: suites 0 fail both flavors (test_proc
193/0, test_term 60/0), woc 557/0, subprocess 12/0, site 23/0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit bc1b4f070693eb755ad6a9fd0c853fb3e2bda347)
- spec 2026-09-01-runtime-v2-design.md: the one principle (PULL — a
child is fds, the net verbs drive them; runtime-v2 adds acquisition
verbs, never transport), the full surface (ids 97+: spawn/spawn_pty/
wait_dl/signal/resize, signal.on delivering the sig number, term.raw/
restore with runtime-guaranteed restore, send_fd/recv_fd/connect_unix),
actor-owned lifecycle, mechanics notes, refusals by name
- push transport rejected with reasons recorded (mailbox-cap collision,
new delivery machinery); death-notice verb refused (a two-line fiber
composes wait_dl)
- five stories flip readiness: ready; fork sections rewritten as settled
- graph section 6 remapped: pull broke the 1->2->3 chain — only 1->2
remains; 3, 4, 5 and the VTE grid startable alone today
- board section + registry follow; linkcheck 0 broken
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit d313cdeebbe53c83b83f31f4480631568d9d0743)
- new docs/guides/updating-site.md: the developer loop deploying-site.md
deliberately does not cover — the submodule two-repo commit dance in
the order that cannot strand other clones (push writeonce-site first,
bump the pointer second), the gate living in the monorepo by design,
and framework changes being ordinary monorepo commits
- the schema section is measured against the built site, not inferred:
adding views: Int stopped the build with WO-E206 until all ten seed
inserts carried it (no field-default syntax — the seed cannot drift
from the schema), then the live WO_DATA migrated at boot, all ten
chapters rendered, and a live admin edit SURVIVED the migration;
retyping the field refused by name with the log intact
- states the one release combination that still needs the content wipe:
a schema change WITH new seed rows — migration handles the shape,
seeds still cannot reach a non-empty table
- deploying-site.md cross-links; site-update prefix registered
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a21a02f2c264a3fe1c31b3bfd9159415a594fa05)
- registry row corrected: it claimed "Not picked to master", and the
branches no longer differ structurally at docs/examples/site
- cherry-pick table gains the same row master's copy carries, so the
ledger reads identically from either branch
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit ab7df69e762cd516d3016b7e2703cb6928c7d835)
- same 37-row dev-to-master map the master copy carries, so the ledger
reads the same from either branch
- registry rows for db2-keys, db2-delta, db2-chains, db2-chain-review
and site marked landed on master
- lang41 registered explicitly as on dev and not picked, so its absence
from master is a recorded decision rather than an oversight
- porch-store and query-corpus rows untouched: still dev-only
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 22b5675ed1c873135e8cb7dd10e010c4a00350b7)
- root cause: a worker's runtime is initialised lazily on first fiber
adoption, and rt.shard_id is stamped only there — but INBOX_READY[i]
is set at thread creation. A shard that never adopts is still settled
at shutdown, carrying rt.shard_id 0 from the memset
- it then impersonated shard 0: wo_drop_obj saw 0 == 0 for anything the
primary allocated, took the "we are home" branch instead of routing,
and called class_free against rt->classes, which lazy init never
filled. &rt->classes[class_id] off a NULL base is the faulting read
- fix: stamp the runtime's real identity at thread creation. An
uninitialised shard owns nothing, so its true id makes every payload
correctly foreign and routes it to an owner that can free it
- ASan could not name this: the arena is one hand-managed malloc block,
so intra-arena reuse is invisible and it surfaces as a bare SEGV
- pinned by tests/regress/lang-41, driven from db-actor-accept. Needs
multiple shards (the corpus runner pins WO_SHARDS=1) and the ASan
build. SEGVs twice per run unfixed, clean fixed
- the HANG is a separate defect and is NOT fixed: with this in place the
harness stops losing whole sections, but idempotent-stop-2 still
fires ~1 run in 6. The story records where to look
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 9dca0b4b4727b976d326b29cb4c6522b62d48a73)
- records every decision made without stopping to ask, with what each
costs if wrong, since the SDD workspace is deleted on completion
- R9 is marked WRONG and overturned by R14: WO-E222 fires on the class
Pool, not on multi, so an actor can hold slots: multi PoolSlot. My
ruling shipped a README prescribing a permanent 1-actor pool
- R6 records that my own brief caused a security bug: trust_proxy with
an absent XFF collapsed every client onto one shared bucket
- R15 parks the one residual: pool_slots/pool_of have zero call sites,
so real N-actor sharding is compile-proven but gate-unproven
- measured the gate over 10 runs: it is NOT stably green. Most runs
fail idempotent-stop; one lost 6 checks with 000 status codes
- traces the flake to the C-runtime hang/segfault, now localised by gdb
to wo_arena_alloc / wo_str_new / vm_run
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a919ab104ce44754949d364e35c88b6964b81fee)
- bullet 2 wrongly told app authors to hold a bare actor handle and
re-wrap it as a forced ONE-slot Pool per connection -- that was my
own advice, not the previous implementer's, and the reviewer showed
WO-E222 fires on the class Pool, not on multi PoolSlot
- rewritten around the new pool_slots/pool_of pair: make_pool(n) once
at process start, multi PoolSlot held directly in connection-actor
state, a transient Pool rebuilt per use -- and states explicitly that
calling make_pool per connection restores the lost-increment race
- disclose that the gate's own ConnWorker fixtures still build a
deliberate one-slot Pool per leg, so no leg yet exercises real
N-actor sharding through pool_slots/pool_of
- soften the rate-limiting row: saturation-503 is gate-proven only via
Idempotent/pool_begin, not through Limiter's own try/catch arm
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6d48dbca98d4ea4c6d93f470ae3aad0b00acd2a1)
- catch (e) nil could not distinguish a real store failure (e.g. a
mod-by-zero from Pool { actors: [] }) from ordinary saturation
- print_err the trap message before answering 503, matching the same
fix in idempotent.wo's pool_begin catch
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit c53ad583051abeeeb302301fc3d91073f0a15fcc)
- stored/replayed headers widen from content-type only to an allowlist
(content-type, location, etag, cache-control), matched case-insensitively
-- a redirect() lost its Location on its own first response, not just replay
- add pool_slots(Pool) -> multi PoolSlot and pool_of(multi PoolSlot) -> Pool
- Pool is demand-promoted to traced (WO-E222) and can't live in actor
state; PoolSlot/multi PoolSlot never is, the same shape chat/main.wo's
Room already holds directly -- this is what lets an app actually shard
across N actors per connection instead of a forced one-slot pool
- log a genuine pool_select trap instead of silently folding it into 503
- fix stale comments: the prune below IS a delete-then-insert (of a
fresh row, not the same one) contradicting the doc comment above it;
the catch shape referenced in two comments had changed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit b738269314f01a95dee1341437c7661ce9e28730)
- normalise self.key_header via to_lower before the req.headers lookup
- req.headers keys are already lowercased on read (internal/parse.wo);
the documented key_header: "Idempotency-Key" never matched, silently
disabling idempotency (falls through to inner.handle) on every request
- key/digest lookups use the normalised name consistently
- digest now always includes method+path, body appended only when
include_body is set -- a bare "" digest under include_body:false
previously matched any other request reusing the same key
- log a genuine pool_begin trap instead of silently folding it into 503
- update the two doc comments describing the old, unsafe shape
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 91099cfbb2fb9a2d351894e444fed306b25557d5)
- Add saturation leg (scripts/web-app-accept.sh): one-actor pool,
WO_MAILBOX=2, 15 concurrent requests, exactly 3 served + 12 answer
503; execution count matches the 200 count, retry-after + real
cause verified on the 503s
- Guard make_pool(n<1) by clamping in make_pool itself, not
pool_select's division -- that trap runs inside the middleware's
own try/catch and would be swallowed as ordinary saturation forever
- README: rate limiting + idempotency ledger rows moved to done,
scoped to what the gate proves; documented Handler-decorator
shape, Pool aliasing (WO-E222), call's scalar-only reply (WO-E226),
pool size as a capacity decision
- Story: Progress table filled with real hashes, 7/9 acceptance
criteria marked verified with citations, 2 marked verified by
construction (never gated even in the original plan), status: done
- Status board: standup entry, porch 1 pending row updated
- Recorded a pre-existing runtime hang (main() returns cleanly, OS
process sometimes hangs under concurrent call()-parked callers)
that also reaches the new leg's teardown; contained with kill -9
rather than asserted, so it can't flake the leg's actual subject
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 21934b18910070a3f24b8bd4367fcb9d397dc1fb)
- The nonce naming an ephemeral (4xx/5xx) row is handed to exactly one
call() reply and nowhere else -- no other message can ever construct
that key, so idempotent.wo deleting it right after building the Resp
is safe by construction (unlike the earlier shared bare-key row,
which a second message COULD reach and made deleting it racy)
- Closes the leak AND a real correctness edge: the nonce is
time.ticks() % 1_000_000_000, wrapping every ~1000s -- with rows kept
forever, a later failed attempt on the same key could land on the
same nonce and either collide with the unguarded insert or resurface
a stale replay, exactly what rounds 1/2 removed
- Gate leg 18f: N ephemeral attempts against the same key must return
IdempotencyKey's row count to baseline, not grow it by N -- confirmed
failing (baseline+N) against the pre-fix code, passing after
- N picked at 3: the pre-existing runtime hang/segfault (out of scope,
being tracked separately) reproduces more often at higher sequential
insert+delete volume against the same key; 3 stayed clean across
many runs while still proving the property precisely
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 9ad594748e01a665ccaf29733a48c2b83a2749da)
- Root cause of the residual: a 4xx/5xx row lived under the bare key,
so a second message could delete-and-replace it before the FIRST
caller's own middleware-side read (necessarily outside receive,
WO-E226) ever ran -- the owner itself could read back a LATER
message's answer, not just a duplicate reading a stale one
- Fix: a 4xx/5xx miss is never stored under the bare key at all. Each
such attempt gets its own row, keyed by a nonce carried back in the
scalar reply's low digits, so no other message for the same bare key
ever touches it -- the decision AND the row's identity are both
fixed inside the one serialized receive call
- Disclosed trade-off: that row is never revisited by a bare-key
lookup, so it is never TTL-pruned either -- permanent per failed
attempt, the same no-sweeper trade-off this codebase already makes
elsewhere, not a new one
- Gate leg 18e: reran 20x in isolation against the fix with zero
500-500 or 200-200 outcomes (was reproducible before)
- §18's SIGTERM-stop check now force-kills on timeout before clearing
$SRV, instead of matching §14/§17b's own gap where a still-running
process escapes the exit trap too -- an orphan no longer survives
past this leg regardless of the assertion's own outcome
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 464147a9ddf3a53cb1946637c3f517ba615ee358)
- Miss path only marks a response a durable replay target (outcome 1)
when status is 2xx/3xx; a 4xx/5xx gets outcome 3 instead
- Outcome 3's row is a one-shot relay: the scalar reply still can't carry
a Resp (WO-E226), so the row exists only to hand the exact response
back once, then idempotent.wo deletes it -- a retry with the same key
is a genuine miss and re-executes, instead of caching a 500 for the
24h default TTL
- Reviewer finding: caching any status meant a transient failure was
replayed verbatim until TTL expiry, worse than no idempotency at all
- Gate leg 18d: FlakyHandler fails once then succeeds; same key twice
must answer 500 then 200 -- confirmed failing (500, 500) before the
fix, passing after
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e61015f2065a7c6aec6f5c2439e78b53f796bab3)
- Delete before/after flow: it stored on a miss, so two duplicates both
missed and both ran; its 10s "in flight" check fired on fast legit
replays and never on a real collision
- Idempotent now wraps the route's Handler and hands request + handler to
the pool; a duplicate waits in the actor's mailbox, not a held reply
- keypool.wo kind-2 arm: digest match replays, mismatch refuses (422), a
miss runs the handler inside receive and stores status/body/
content-type, all via the same pool_pack(count, remaining_ms) scalar
kind-1 uses (WO-E226 forces one return type)
- Outcome codes start at 1, never 0: idempotent.wo's try/catch cannot
tell a literal 0 reply apart from a trapped call
- fresh_req() copies a borrowed Req's map fields into a new Req before it
crosses the actor boundary (WO-E222: aliased graphs can't cross heaps)
- Reading a stored row back forces fresh Text via `.. ""` on every field
copied out of json.decode's result -- decoded Text does not survive
being handed onward once the decoded record goes out of scope
- insert is unguarded (kind 1's own convention): a swallowed failure
would answer "stored" for a response never written
- web-app-accept.sh: leg 18a/b/c -- byte-identical replay off an ExecMark
row count, digest mismatch is 422, genuinely parallel duplicates run
the handler exactly once
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit eae1b06cdc38e4766d4e27a66b02264f47164e99)
- limiter_key: an empty client_ip(req) under trust_proxy no longer keys
on the literal "ip:" -- falls through to net.peer(req.conn) instead,
same as the untrusted-default path
- the bug: every client omitting X-Forwarded-For shared ONE bucket,
so one could exhaust it and deny/hide the rest
- curl availability check added alongside the existing woc/wovm check
(the limiter gate legs drive the server with it)
- new gate leg: LIMIT+1 sequential no-XFF requests must all be 200
(own key per connection, via a fresh ephemeral port each time) --
confirmed it fails against the pre-fix code (6th comes back 429)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 831e9d8e6b1ef39cd938783dbc47c1abe6d53211)
- delete all RateLimitCounter access from limiter.wo: query, increment,
delete-then-insert, and the swallowing catch (e) nil -- the pool is now
the only writer, so its serialization guarantee actually holds
- Limiter gains pool/limit/trust_proxy fields; before() calls pool_count
and acts on the Verdict; make_limiter takes a pool
- key selection: req.principal first, else trust_proxy ? client_ip(req)
: net.peer(req.conn); delete the dead req.ctx["verified_proxy"] branch
- 429 on a spent window (Retry-After, X-RateLimit-*); 503 + Retry-After
on a caught actor trap (saturated pool), request never let through
- add Limiter.after(), registered alongside before() as both Mw and Aw
(Cors's own shape) so the allowed path's X-RateLimit-* headers reach
the response, not just req.ctx
- scripts/web-app-accept.sh: three new gate legs -- threshold (N pass,
N+1th 429), SIGTERM+restart (still limited from the WAL), and N
genuinely-parallel curl clients on one key with an exact-count assertion
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a653dd0aa64711c43461126d32b4632cc8f64c7a)
- keypool.wo:78,89 passed msg.window (µs) straight into pool_pack's
remaining_ms (ms) parameter on the first-hit and post-prune-reset
paths; the third call site already divided by 1000 and was correct
- fix: pool_pack(1, msg.window / 1000) at both sites — a 60s window
no longer reports reset_at ~16.7h away
- count/allowed were unaffected (computed independently); this only
hit the client-visible reset instant, on the two most common cases
(new key, window rollover)
- extended gate leg 16 to assert reset_at falls within a 5s band of
time.now() + window_ms, not just on count — verified the assertion
itself by reverting the fix, confirming leg 16 failed with the
exact defect shape, then restoring it and confirming green
- woc docs/examples/porch/ exits 0; web-app-accept.sh: 47 checks,
0 failures
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 153fd295d37905dd083823536c6781843699e455)
- WO-E226: call's reply must be a copyable scalar, and every receive
program-wide must declare the same return type. Verified by fixture:
"call's reply type `Out` is not a copyable scalar"
- the spec had the actor return the response object, which cannot cross
the mailbox. Corrected: the actor stores the response and returns an
outcome code; the middleware reads the row and builds the Resp
- owner and duplicate now read the SAME durable row, so byte-identical
replay is structural rather than careful copying
- blocking, exactly-once execution and the mailbox queue are unchanged
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 77e06c1690b92d456a9bc53503695fdaa2b4b44e)
- add docs/examples/porch/middleware/keypool.wo: one PoolMsg (kind 1 =
count, kind 2 = begin placeholder for task 4), a Verdict class, a
fixed-size actor pool with a byte-sum-mod-N selector
- KeyActor.receive implements kind 1: reads the row, writes the new
count via field assignment (writes through, never delete+insert),
prunes a fully-elapsed window's row instead of resetting it
- window arithmetic on time.ticks(); reset instant sent back is built
from time.now() only
- call's reply must be a copyable scalar (WO-E226), so the count and
remaining window time are packed into one Int by the actor and
unpacked into Verdict by pool_count — the packing stays inside this
file, callers only ever see Verdict
- gate leg in scripts/web-app-accept.sh: a flat copy of porch (manifest
stripped) with a driver dropped beside keypool.wo asserts two
sequential counts return 1 then 2; verified failing (E403, make_pool
undeclared) before this file existed, passing after
- woc docs/examples/porch/ exits 0; full web-app-accept.sh: 47 checks,
0 failures
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 676e651d808ef2c3619f88c3808adc372cd0be6c)
- Add digest field to store sha256(method|path|body) separately from key
- Enables detection of "same key, different body" in future tasks
- Update idempotent.wo insert to compute and store digest value
- Typechecker passes: exit 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3a9bddcd1e3c11f5b371ce54cafc373685ca08b6)
- one message class with a kind discriminator, not a receive per
message type: an actor handle is typed to one message class, so a
second receive compiles but is unreachable. chat/main.wo is the
precedent. Verified by fixture before amending
- Task 4's Files list omitted keypool.wo, which its step 4 edits
- clarified that the delete-then-insert ban targets using that pair as
an UPDATE; pruning an expired row is a plain delete and is required
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a96ebe20e92e2dc6dfecc59a955d4c6e75d74689)
- the spec's blocking design was unimplementable: call's reply IS the
return value of receive, so an actor cannot hold a waiter. Holding
means never returning, and an actor that never returns cannot process
the completion it waits for — deadlock
- corrected shape: the actor RUNS the handler inside its own receive, so
a duplicate waits in the mailbox and is served after the owner. The
queue blocking needs is the mailbox; nothing is held
- verified before adopting it, not after: an actor can receive a message
carrying an interface-typed value and invoke it, so the route's
Handler passes through the mailbox
- spec History records the reasoning error — "the primitives landed" was
taken as "blocking needs no new surface", which does not follow
- plan: 5 tasks. Counting and replay live in one new keypool.wo; both
middlewares become thin key-choosers, so porch 2 and 3 inherit one
serialization convention instead of re-implementing it
- self-review added two legs it was missing: exact counting under real
concurrency (the criterion the pool exists for), and pruning an
elapsed limiter row rather than resetting it, which otherwise leaks a
row per IP ever seen
- plan is code-free per house convention; the writing-plans skill wants
code blocks and the project rule overrides it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f079455755a189a86bb12e07cc11549ed7a78b91)
- docs/examples/porch/ did not typecheck: WO-E403 "cannot resolve the
receiver's type for the call to `encode`" on json.encode
- every other example that calls json.encode/decode imports it; this
file did not, so the whole porch library was uncompilable on dev
- woc docs/examples/porch/ now exits 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5c3544d524fa98dbb7a363600cd2eeb6dd1badac)
- supersedes Phases B and C as built: a store-after-completion
middleware cannot satisfy three of the story's seven criteria
- in-flight collision is undetectable (the row is written after the
handler ran, so concurrent duplicates both miss and both execute)
- the 10s in-flight heuristic is inverted: created_at is stamped at
store time, so it fires on legitimate fast replays and never on a
genuinely concurrent request
- "reused key, different body is refused" is unreachable while the
digest is folded into the key — nothing looks the bare key up
- design: sharded actor pool serializes per key, @table persists;
actors own volatile state, tables own durability. Inherited by
porch 2 and 3
- limiter joins the pool for exact counting, writes through instead of
delete+insert, keys on net.peer unless trust_proxy is declared, and
uses monotonic ticks for arithmetic but wall clock for the header
- idempotency blocks rather than answering 409: call parks the
duplicate until the owner reports. Digest becomes a column
- saturation fails closed with 503 for both: saturating the pool must
not become the limiter bypass
- records that the story's "time.after is still reserved" is stale;
spawn/send/call/monitor/time.after all landed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit fc09e94373db65837ff5eb620fec67bab02c1931)
- Idempotent middleware (aee7926) added to the porch-store row
- records that Phase C is unverified: no `use json` import despite
calling json.decode and json.encode
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit aa8abfb4b7a1dabaa0c68afa0ee7fdfccf1b4689)
- replays a stored response for a repeated Idempotency-Key: before()
checks the key, after() stores status/body/content-type on a 2xx/3xx
- key is "idem:<header>:<value>", optionally plus a sha256 digest of
method|path|body when include_body is set
- 409 while a key is in flight (stored within the last 10s), lazy TTL
expiry on access, default 24h
- replay allowlists content-type only — never Set-Cookie or Date
- backed by IdempotencyKey from Phase A (519d411)
Written by a parallel session and committed here as-is because its
branch was consolidated away. NOT verified: it calls json.decode and
json.encode without a `use json` import, which every other example that
uses json has. Left unedited rather than fixed blind.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit aee79264095eea3b2c38c92789c44b31f1c9ef8a)
- porch-store and query-corpus prefixes registered; both replayed onto
dev, so dev is a superset of porch-store-middleware
- three branches could not be replayed and are preserved as annotated
tags rather than merged or discarded:
- cleanup/pre-existing-changes carries crates/ + Cargo.toml, the Rust
runtime master deleted; replaying it would resurrect it
- ipc-attach refactors wo_row_insert/wo_row_update_field into
encoded cores, which db2-keys rewrote for keys-residency — two
overlapping refactors of one function
- keypair-auth builds on ipc-attach, blocked by the same overlap
- names the specific hazard: 9c transfers ownership of vals on failure,
dev's keys-resident arm returns early without freeing, so a merge
that compiles and passes could still leak or double-free
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit bc8fec01d565d0ad54696fb3d2f85a9d1e0531a1)
- resolved 9g's forks EMPIRICALLY against the running compiler:
- count(<query>) and len(<query>) already work (fork 2 collapses to
zero code)
- skillhost's correlated NOT EXISTS is a backlink emptiness in
writeonce (`where len(x.children) == 0`), using only 9b machinery
(fork 1) — verified on a self-referential ?ref/backlink table
=> corpus #1 forces NO new grammar; per the method ("add only what a
corpus uses"), exists/not-exists was NOT built
- docs/examples/skill-catalog: mirrors skillhost's `skills` table
(name @unique, description/location/root, parent ?ref Skill, children
backlink) and translates all five of its SQL statements 1:1
(insert+dup-trap, get-by-name, list, roots via backlink-emptiness,
count); scripts/skill-catalog-accept.sh 7/0, WAL-durable, dup trap
persists across restart
- fixture run/db-query-corpus (count(query) + backlink NOT EXISTS);
just skill-catalog module; target/ gitignored
- general exists/not-exists left unbuilt and recorded as "enters when a
corpus forces a non-relation correlation"
- gates: oop-e2e 80/0, woc-test 566/0, skill-catalog 7/0; story + board
record the finding
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4c82461634d45f11eca1a252702031c03d999c7f)
- story frontmatter status: done, Progress section records what landed
vs the spec (everything, same day as the brainstorm)
- board: NEXT PLAN entry with the six standup answers (deadlock proven
real: 5 s hang, 8192-byte truncation; 15 ms after; ping 2 ms during a
parked child; 1000 spawns fd-flat; SIGTERM leaves no child); pending
row flipped to DONE
- graph: node 42 class done, same change as the board row
- runtime/src/CODE-LOGIC.md: the bounded-subprocess section (bundle
park, slot registry, ownership sweeps, raw pidfd syscalls)
- full belt at close: 19 runtime suites 0 fail (test_proc 128/0),
woc-test 557/0, subprocess-accept 12/0, site-accept 23/0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docs/examples/subprocess: line-oriented TCP service, one Handler actor
per request — ping/run/slow/deadline/cap/long exercise the whole
bounded surface from .wo, traps caught with try/catch in the language
- scripts/subprocess-accept.sh + `just subprocess`: 12 checks, 0 failures
first run — deadline and cap messages verbatim, ping answered in 2 ms
while a sleep-2 child was parked, SIGTERM exit 0 with the sleep-30
child verifiably gone (pid checked from outside)
- service logs to /tmp/subprocess.log, banner-separated per run
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- seven commits dev to master, zero conflicts: the six db2-migrate
commits plus site-deploy, which the close-out edits and which had
been dev-only
- verified on master after the pick: 36 suites 0 fail (test_wal
5966/0), woc-test clean, residency-accept 14/0, site-accept 23/0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- crash-before-rename test: a COMPLETE valid migrated temp beside the
untouched original is discarded and the boot re-migrates — the
sharpest point on the crash timeline, deterministic, no fault
injection needed
- story: all six tasks done with commit hashes, all eight criteria met
with the test that proves each, plus the three deviations from the
plan and why (transcode over replay, lazy head, poison forces
transcode)
- CODE-LOGIC: migration section; also corrected limitation 3, which
still claimed unbounded hot-row chains — iteration 11 closed that
- status board row 12; deploy guide's rollback section gets its real
answer (rolling back across a migration is a migration backwards:
expect the refusal, restore the .bak)
- test_wal 5966 pass, 0 fail
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4bb6ece2531e2123eb958c91d9bef4a6528eab3b)
- brainstorm settled: declarative and automatic at boot; v1 verbs are
add and delete only; data/seed migrations deferred to v2
- added fields zero-fill by kind: the grammar has no field-default
syntax and v1 refuses to grow compiler surface for it
- same-kind delete+add refuses as a disguised rename; retype and
vanished classes refuse by name
- schema lives in the log itself: WO_WAL_SCHEMA head record, written by
fresh-log open and compaction; name-keyed diff also closes the
silent cid-renumbering hole
- story is iteration 12, board row added, db2-migrate prefix claimed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 072e007b144ff6689b6ff920ea10665900c1a2ef)
- new docs/guides/deploying-site.md: build, content refresh, systemd
unit, post-deploy verification, rollback, and the gaps behind each
workaround
- leads with the trap that costs the most: shipping a binary does NOT
update chapters. seed_if_empty only fills an EMPTY table and
AdminEdit answers not_found for an unknown slug, so a host with an
existing WO_DATA shows the old chapter list with no error anywhere
- that claim is measured, not argued: a 9-chapter build seeded a data
dir, then the 10-chapter binary against it still 404'd /ch/storage
and rendered 9 nav entries; wiping WO_DATA gave 200 and 10
- records two more blockers found while writing it: both site deps
(porch, writeonce-view) 404 on GitHub and wo.lock is untracked, so
the site submodule cannot build standalone; and the embedded wovm
sets the glibc floor (this machine: 2.38, above Ubuntu 22.04's 2.35)
- build recipe run verbatim before publishing; releasing.md points here
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 930a715c4a3d847529e3e341edc71c65a7e11d1c)
- 4b56348 -> 7d9d526, 4eead89 -> 653a91c
- the registry rows for lang41, porch-store and query-corpus came with
the pick and are kept: the registry is a global claim ledger, so a
copy that silently omits three claimed prefixes is worse than one
that names them and says they are dev-only
- site-submodule row corrected on the way in — it said "Not picked to
master", which this pick is precisely what falsifies
- verified on master after the pick: site-accept 23 checks, 0 failures
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- records that docs/examples/site is now a submodule on dev only
- states the consequence plainly: master still carries the site inline,
so the branches differ structurally at that path until this is
cherry-picked
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4eead8938c7292a130723aeabe7b74bdd1ba60f6)
- extracted to github.com/shoneyJ/writeonce-site with `git subtree
split`, so the site keeps its own 9 commits of history rather than
landing there as a flattened snapshot
- .gitmodules gains the third entry, alongside reference/writeonce-app
and reference/writeonce-api; path is unchanged, so every doc and
script that names docs/examples/site still resolves
- site-accept.sh fails early and says `git submodule update --init`
when the directory is empty. Without it a clone lacking submodules
copies an empty app and fails later as a build error naming nothing
- releasing.md: the steps that edit install/view.wo now say that edit
is a commit in the site repo plus a pointer bump here — editing and
committing only in this repo would record nothing
- gate re-run against the submodule: site-accept 23 checks, 0 failures
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4b5634801d5379890bad24c8129cfb23ab6b98df)
- first cherry-pick under this convention: 37 commits, dev to master,
mapped one-to-one with titles
- iteration 11 could not travel alone — its commits touch
wo_wal_fold_row_at, keys_fold_into and row_apply_field_keys, none of
which existed on master, so the whole db2-keys/db2-delta stack came
- porch-store (26 commits) deliberately left on dev: porch 1 was
re-scoped mid-flight, which is what "ready, not merely green" is for
- records the three docs conflicts and how each was resolved, including
keeping only the databasev2 half of a status entry that would
otherwise have had master claiming porch 1 was done
- records what is still outstanding: task 6's byte-budget refusal, a
missing guard rather than an unhonoured annotation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- records the workflow: develop on dev, feature prefix as the
conventional-commit scope, cherry-pick onto master when ready
- prefix registry so two features cannot claim the same prefix; the
prefix is claimed before the feature's first commit
- cherry-pick log maps dev hashes to the master hashes they produced —
they differ, and that mapping is what makes a feature traceable or
revertible as a unit after dev moves on
- notes the db2-keys seam: written pre-convention on
porch-store-middleware, replayed onto dev, replay verified identical
- work before 2026-08-29 landed by merge; git log --merges covers it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 41923eb1f9510ab53804ca8dc6fe30a9a7eac849)
- new chapter 7, "Storage modes: durable and resident", covering what
master gains with the databasev2 cherry-pick: durable: false for a
RAM-only table, resident: keys for a table that outgrows RAM
- states the parts a reader would otherwise hit as surprises: a
keys-resident table is REFUSED at startup without WO_DATA, an update
appends a delta rather than rewriting the row, and the chain is
bounded at 16 links so a hot row does not degrade reads or replay
- quotes the measured 2.55x smaller resident set, not an estimate
- actors/deps/serving shift to ord 8/9/10; seeding is ord-driven so an
existing WO_DATA keeps its rows and only a fresh boot reseeds
- home card says a table can be RAM-only or outgrow RAM
- two gate legs pin the new chapter; site-accept 23 checks, 0 failures
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3b503c0db50aa737dd06ef6d17151c654a42c59b)