docs(events): Phase 6 as built — enablement, caps and mayInvoke #214

Merged
whitlocktech merged 1 commits from docs/events-phase-6 into edge 2026-09-03 14:36:17 +00:00
Member

The docs half of Event System Phase 6. Code: RunicGateway/website#188.

EVENTS.md

§D — the two tables arrive. event_action_settings and event_run_budget move from "arrive with the phase that gives them a writer" to built, each with the fact that is not obvious from its column list. A missing settings row is not "disabled" — it is the default for the action's risk class, computed rather than stored, and nothing is seeded at boot because registration runs against a dead pool (MODULE_API §2.2), so a seed of one row per registered action would be exactly the write that forbids. A NULL cap is uncapped and still a row, which is what lets the meter count a dimension nothing bounds while a missing row keeps its one meaning: a step spending something its own run's version never priced.

event_versions gains verified_at / verified_by, with the note on why two writable columns do not break an immutable table — they describe something that happened to the version rather than changing the plan reviewed, which is also why a pass recorded against one stays true for ever.

The refused row in the lifecycle table now says what a refusal does, not only what it means: the same disposition a failure takes, and a different status and log kind, so an operator reading a stopped run at two in the morning can tell "raise the cap" from "the shard did not answer" at a glance.

§E — a new subsection, Enablement and caps, in front of every dispatch. Where the check sits and why (after the claim, before the dispatch), the retry that does not pay twice and the failure that gets no refund, the partial spend across dimensions that is unwound, the budget seeded from every phase at creation rather than growing as phases are entered, the tightest cap and why null never wins a minimum, and the one place a demoted user's access deliberately is not re-checked. The concurrency row for caps gains Phase 5's lesson applied rather than rediscovered: the guard must stay in the WHERE, because MariaDB evaluates a SET list left to right.

§K — the amendment, and it is the substantive one.

"Nothing above notify is enabled by default" becomes "nothing that CHANGES THE WORLD", with the reason written where the sentence is: read literally it shipped core.waitrisk: 'inspect' — disabled, so every published event that waits breaks on a fresh deployment. The same sentence is the role floor, and it moved with it.

Then mayInvoke as built: why user may be null and what that means, why run may be null and how the cap question changes, why the cap check can write, why the role floor lives in the model rather than in route middleware (the route is admin, editor and stays that way — which of the two you have to be depends on the body), why an unpriceable cost() is refused rather than free, and the example-pricing stand-in until registerEventBudgets lands in Phase 7 — stated with its limit rather than as a design.

The shard's own switches are now written down as named and deliberately not duplicated in core: a second copy of that decision could disagree with the shard about whether the shard is accepting writes, and naming the layer is what stops it being re-implemented.

§I, §L, § API surface, § Observability. The switchboard screen and the dry run marked built, each with the thing that makes it more than a form — the board says whether a row is a decision or a default, a cap may only name a dimension the action actually spends, and the run console's meter is shown on finished runs too. §L gains the disabled-action row and the held-occurrence row beside the cap row. The API surface loses two of its three absent routes and gains the note on why findings come back with a 200 and why the whole-plan cost check is the one finding no other path can make. Observability gains the three new log kinds and the run's fourth derived field.

The phase table. P6 ticked.

EVENTS_PLAN.md

Phase 6 marked complete, in the shape Phases 0–5 use: the four org-lead decisions, the two derived calls flagged for review, the three things the build settled, the one thing this phase deliberately cannot demonstrate with a module (registerEventActions is Phase 7's seam, so the cap machinery ships with no live consumer — proved by tests and by a rig action reverted before commit), the two defects the live walk found, the verified numbers and the walk itself.

Phase 6's own body text corrected in two places where it no longer matched what shipped: the default-off line, and the WHERE clause, which now carries cap IS NULL. One clause added on what P6 actually contributed to the role split — P3 had already gated every route, so what landed here is the two new routes' gates and the one gate that could not live in middleware.

Checked by hand

docs has no CI, so: every anchor this touches resolves (#k--security-model and #f--the-module-contract were already in use; #e--runtime-model is new and takes the same form), and every route, column, env var and file path named here exists on website#188. Diff is 151/23 and 118/2 across the two files with no CRLF artefact, verified against the real content diff rather than --numstat alone.


  • AI-assisted: authored with Claude Code (Claude Opus).
The docs half of Event System **Phase 6**. Code: **RunicGateway/website#188**. ## `EVENTS.md` **§D — the two tables arrive.** `event_action_settings` and `event_run_budget` move from "arrive with the phase that gives them a writer" to built, each with the fact that is not obvious from its column list. A **missing settings row is not "disabled"** — it is the default for the action's risk class, computed rather than stored, and nothing is seeded at boot because registration runs against a dead pool (MODULE_API §2.2), so a seed of one row per registered action would be exactly the write that forbids. A **NULL cap is uncapped and still a row**, which is what lets the meter count a dimension nothing bounds while a *missing* row keeps its one meaning: a step spending something its own run's version never priced. `event_versions` gains `verified_at` / `verified_by`, with the note on why two writable columns do not break an immutable table — **they describe something that happened *to* the version rather than changing the plan reviewed**, which is also why a pass recorded against one stays true for ever. The `refused` row in the lifecycle table now says what a refusal *does*, not only what it means: the same **disposition** a failure takes, and a different **status and log kind**, so an operator reading a stopped run at two in the morning can tell "raise the cap" from "the shard did not answer" at a glance. **§E — a new subsection, *Enablement and caps, in front of every dispatch*.** Where the check sits and why (after the claim, before the dispatch), the retry that does not pay twice and the failure that gets no refund, the partial spend across dimensions that *is* unwound, the budget seeded from every phase at creation rather than growing as phases are entered, the tightest cap and why `null` never wins a minimum, and the one place a demoted user's access deliberately is **not** re-checked. The concurrency row for caps gains Phase 5's lesson applied rather than rediscovered: the guard must stay in the `WHERE`, because MariaDB evaluates a `SET` list left to right. **§K — the amendment, and it is the substantive one.** > *"Nothing above `notify` is enabled by default"* becomes *"nothing that CHANGES THE WORLD"*, with the reason written where the sentence is: read literally it shipped `core.wait` — `risk: 'inspect'` — disabled, so every published event that waits breaks on a fresh deployment. **The same sentence is the role floor**, and it moved with it. Then `mayInvoke` as built: why `user` may be null and what that means, why `run` may be null and how the cap question changes, **why the cap check can write**, why the role floor lives in the model rather than in route middleware (the route is `admin, editor` and stays that way — *which* of the two you have to be depends on the body), why an unpriceable `cost()` is refused rather than free, and the example-pricing stand-in until `registerEventBudgets` lands in Phase 7 — stated with its limit rather than as a design. The shard's own switches are now written down as **named and deliberately not duplicated in core**: a second copy of that decision could disagree with the shard about whether the shard is accepting writes, and naming the layer is what stops it being re-implemented. **§I, §L, § API surface, § Observability.** The switchboard screen and the dry run marked built, each with the thing that makes it more than a form — the board says whether a row is a *decision* or a default, a cap may only name a dimension the action actually spends, and the run console's meter is shown on finished runs too. §L gains the disabled-action row and the held-occurrence row beside the cap row. The API surface loses two of its three absent routes and gains the note on why **findings come back with a 200** and why the **whole-plan cost check is the one finding no other path can make**. Observability gains the three new log kinds and the run's fourth derived field. **The phase table.** P6 ticked. ## `EVENTS_PLAN.md` Phase 6 marked **complete**, in the shape Phases 0–5 use: the four org-lead decisions, **the two derived calls flagged for review**, the three things the build settled, the one thing this phase deliberately cannot demonstrate with a module (`registerEventActions` is Phase 7's seam, so the cap machinery ships with no live consumer — proved by tests and by a rig action reverted before commit), the two defects the live walk found, the verified numbers and the walk itself. Phase 6's own body text corrected in two places where it no longer matched what shipped: the default-off line, and the `WHERE` clause, which now carries `cap IS NULL`. One clause added on what P6 actually contributed to the role split — P3 had already gated every route, so what landed here is the two new routes' gates and the one gate that could not live in middleware. ## Checked by hand `docs` has no CI, so: every anchor this touches resolves (`#k--security-model` and `#f--the-module-contract` were already in use; `#e--runtime-model` is new and takes the same form), and every route, column, env var and file path named here exists on website#188. Diff is 151/23 and 118/2 across the two files with **no CRLF artefact**, verified against the real content diff rather than `--numstat` alone. --- - [x] AI-assisted: authored with Claude Code (Claude Opus).
wtclaude added 1 commit 2026-09-03 10:53:19 +00:00
EVENTS.md

§D — the two tables move from "arrive with a later phase" to built, with the
two facts that are not obvious from the column list: a missing settings row is
the risk-class default rather than "disabled", and a NULL cap is uncapped and
still a row, so a MISSING row keeps its one meaning. event_versions gains
verified_at/verified_by, and the note on why two mutable columns do not break
an immutable table.

§E — a new subsection, "Enablement and caps, in front of every dispatch": where
the check sits and why, the retry that does not pay twice (and the failure that
gets no refund), the partial spend that is unwound, the budget seeded from every
phase at creation, the tightest cap, and the one place a demoted user's access
deliberately is not re-checked.

§K — the amendment. "Nothing above notify" becomes "nothing that changes the
world", with the reason: read literally it shipped core.wait disabled. The same
line is the role floor. Plus mayInvoke as built — why user and run may be null,
why the cap check can write, why the role floor is in the model rather than in
middleware, and the example-pricing stand-in until registerEventBudgets lands.

§I, §L, § API surface and § Observability — the switchboard and the dry run
marked built; the two refusal rows and the held-occurrence row; the three new
log kinds and the run's fourth derived field; why the whole-plan cost check is
the one finding no other path makes.

EVENTS_PLAN.md — Phase 6 complete in the shape Phases 0–5 use: the four org-lead
decisions, the two derived calls flagged for review, the three things the build
settled, the one thing this phase deliberately cannot demonstrate with a module
(registerEventActions is Phase 7's seam), the two defects the live walk found,
the verified numbers and the walk itself.

Checked by hand — docs has no CI: every anchor resolves, and every route,
column, env var and file path named here exists on website#XXX. Diff is
151/23 and 118/2 with no CRLF artefact, verified against the real content diff
rather than --numstat alone.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6t8mrAWhZU5vnyYgZTMtL
whitlocktech merged commit 9d05a56198 into edge 2026-09-03 14:36:17 +00:00
whitlocktech deleted branch docs/events-phase-6 2026-09-03 14:36:17 +00:00
Sign in to join this conversation.
No description provided.