writeonce/docs/examples/porch/middleware/store.wo
shoney.arickathil aa13b2125f refactor(porch-store): re-scope porch 1 to the limiter, revert idempotency
- idempotency built, reviewed, then reverted WHOLE to the tag
  archive/porch-idempotency. Not a design failure: it passed its gates.
  It provokes a C-runtime SIGSEGV in wo_arena_alloc/wo_str_new under
  concurrent call()-parked callers
- the evidence for that attribution: over ten gate runs every failure
  was an idempotency leg and none was the limiter's, which drives the
  same pool through the same call/park machinery. The begin arm has 5x
  the allocation sites inside receive and moves a whole Req plus a
  Handler through the mailbox
- before the split the suite reported 0 to 6 failures run to run; after
  it, five consecutive runs at 56 checks, 0 failures
- PoolMsg loses digest/req/handler, and NullHandler/dummy_req/fresh_req
  go with them — every rate-limit count used to allocate a throwaway
  Req it never read
- IdempotencyKey is KEPT and commented: the schema is settled and the
  digest-as-column decision cost a review round to get right
- the limiter's saturation 503 has no leg of its own now (§19 drove
  Idempotent). Stated in the README rather than papered over — a
  deterministic leg needs a slow actor, and only the reverted arm was
- new: porch 9 (idempotency, on hold) and language 41 (the arena crash,
  with the reproduction harness and the evidence that localises it)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 79e6da4465133dc555e913c960d544ef1c7bedd8)
2026-09-15 01:25:00 +02:00

47 lines
No EOL
2.2 KiB
Text

-- porch/middleware/store.wo — store-backed middleware tables.
-- Two purpose-shaped @table classes for rate limiting and idempotency.
-- Iteration 1 of the porch track.
-- Rate limiter: fixed-window counter.
-- Key format: "ip:192.168.1.1" or "principal:alice"
-- Window = start of current window in time.ticks (µs monotonic)
@table(name: "rate_limit_counters", index: [key])
class RateLimitCounter {
key: Text @unique
count: Int
window: Int
}
-- ============================================================================
-- KEPT DELIBERATELY, UNUSED TODAY.
--
-- Nothing in porch reads or writes this table right now. The idempotency
-- middleware that did was reverted on 2026-08-30 — not because the design was
-- wrong (it was built, reviewed and works) but because it provoked a C-runtime
-- crash: a SIGSEGV in wo_arena_alloc / wo_str_new under concurrent
-- call()-parked callers allocating heavily inside an actor's receive. Over ten
-- gate runs every failure belonged to an idempotency leg and none to the rate
-- limiter's, which drives the same pool through the same machinery but
-- allocates a fifth as much.
--
-- The table stays because the schema is settled and re-adding it would be
-- churn, not design: `digest` as its own column (never folded into the key, or
-- "same key, different body" becomes undetectable) is the one decision that
-- cost a review round to get right. The middleware, its actor arm and its gate
-- legs are whole in the tag `archive/porch-idempotency`, which is also the
-- reproduction harness for the runtime bug.
--
-- If the runtime bug is fixed and idempotency is NOT resumed, delete this.
-- ============================================================================
-- Idempotency: stored response for replay.
-- Key format: "idem:keyheader" or "idem:keyheader:sha256(method|path|body)"
-- Response = JSON-encoded Resp {status, headers, body} (headers allowlist:
-- content-type, location, etag, cache-control)
-- created_at = time.ticks when stored (µs monotonic) for lazy expiry
@table(name: "idempotency_keys", index: [key])
class IdempotencyKey {
key: Text @unique
response: Text
created_at: Int
digest: Text
}