docs(events): Phase 8 as built — the resource ledger, leases and cleanup #216

Merged
whitlocktech merged 1 commits from docs/events-phase-8 into edge 2026-09-04 05:11:39 +00:00
Member

The docs half of Event System Phase 8. Code: RunicGateway/website#190.

EVENTS.md

§D's ledger row is corrected in two places, and §L's two rules gain a new subsection recording what the build settled:

  • Rule 1 needed a mechanism, because a spawn's ref does not exist until the module answers. What goes in before the dispatch is a PLACEHOLDER keyed by the step's idempotency key — and that is why §F's revert takes the key at all. It is written after the permission check and before the module is reached: a refused step created nothing and must ledger nothing.
  • A LEASE does not use it, and gets rule 1 in a stronger form. Its target is the lease id the step already names, so core.lease writes the real row first — which is also the only moment the two-events-one-target refusal can happen before the world has been written to.
  • Recording is idempotent because the database makes it so. A retry re-sends the same key and a module may honestly re-report the same resources; the unique key refuses the second insert and core reads that as "already recorded".
  • The unique key is held by three statuses and released by three (amended 2026-09-03). "Among non-reverted rows" was written before the six statuses had their meanings; taken literally it makes drifted and orphaned hold a target for ever, so one bad night would disable a lease permanently with no control able to clear it. Both stay LOUD by the other mechanism — cleanup_status and a row on the console — which is what rule 2 actually asks for.
  • MariaDB has no partial index, so the encoding is a STORED generated column reading status aloneTEAMS.md §2.5's correction, because MariaDB refuses ON DELETE SET NULL on a foreign key whose column is a base column of one, and step_id must stay SET NULL.
  • Cleanup is one sweep, not synthetic step rows, and it runs from one place: a fifth leg of the tick.
  • What that sweep SELECTS cost two live-walk defects in opposite directions, and the pair is the finding worth keeping. Deriving "is there anything to do" from a summary column instead of from the rows stranded a lease outright, and then made the retry bound mean one attempt. Both are recorded with the reasoning, including the third defect: the first fix made the run console render "3 attempts" beside resources nothing had ever tried, because a counter that means two things is a counter a screen cannot render.
  • Drift is a third outcome, not a failure with a flag, and orphaned is only ever reached through reconcile — a revert that finds nothing there is a success, whereas a resource the module reports missing is a thing that vanished while nobody was looking. Two different sentences to the operator reading the console the morning after.

§F gains the lease's verb — core's, with the reason — and the reconcile bullet, including the sentence that governs it: core has no concept of the game being up, so the module says when. §I's "cancel takes { reason } and not { cleanup }" becomes what shipped, and names the eighth control. §K's role table puts re-running cleanup in the admin-only row. §L's cancel row and cleanup-fails row are updated. Observability names the six new log kinds. § API surface: the cleanup row is filled in with why it is narrower than the seven controls beside it, cancel gains its flag, and the absent-routes list is now empty — nothing in that table is missing any more. The phase table: P8 ticked.

EVENTS_PLAN.md

Phase 8 marked complete, in the shape Phases 0–7 use: the four org-lead decisions, the four things the build settled, the throwaway-module proof, the verified numbers, and the live walk.

Including the findings worth reading before the diff:

  • The three defects only the walk could find, and why no unit test could: a test that calls the sweep directly never asks what would have selected the run.
  • A bound has two halves. The test covering EVENT_REVERT_MAX_ATTEMPTS asserted <= 3, which the defect's value of 1 satisfied. Both halves — it retried, and it stopped — are asserted now.
  • The two properties that needed the process to die, walked as the plan asks: a placeholder standing over an empty list while a dispatch hangs, then a kill, a restart, the same idempotency key re-sent, and everything given back; and a boot-time reconcile marking a vanished resource orphaned rather than reverted.
  • A latent bug in eventRunnerSql.test.js, found on the way: its model-backed tests were reaching the developer's real schema while asserting against fixtures written into the throwaway database next door.

MODULE_API.md

1.10.0 gains reconcile() and ctx.events.reconcile() IN PLACE rather than a new version (org lead, 2026-09-03). A protocol owes a bump once it has landed on main; while it is on edge it is amended in place — the rule the Teams workstream arrived at, applied to a module API for the first time. That is stated in the entry itself, so nobody later reads the missing 1.11.0 as an oversight.

The rule list grows from six to ten, and each is a rule rather than a field: the lease's verb is core's and why; until goes down the wire and a module that treats it as advisory has produced a lease that outlives an outage; revert must be idempotent and reverting something that does not exist is a success; revert is also called with the key and an empty list, which is the lost-answer case and the whole reason it takes the key; and reconcile is optional where revert is required, with the asymmetry explained.

§2.4 grows from four contract items to six: core records before it dispatches, and core owns cleanup — derived, never authored, with no on_teardown on an action and no cleanup phase in a spec.

Checked by hand

docs has no CI, so: every anchor in EVENTS.md resolves — checked by generating the heading slugs and diffing the link targets against them, not by eye — and every route, identifier, env var and file path named here exists on website#190: core.lease, core.options.leases, ctx.events.reconcile, EVENT_REVERT_MAX_ATTEMPTS, EVENT_CLEANUP_RUN_BATCH, event_run_resources, live_marker, uq_evres_target, the six log kinds, unresolvedResources, and the route in both routes.manifest.json and the OpenAPI spec.

Diffs are 144/21, 139/16 and 67/9 with no CRLF artefact, verified by comparing --numstat against the real content diff rather than trusting it.


  • AI-assisted: authored with Claude Code (Claude Opus).
The docs half of Event System **Phase 8**. Code: **RunicGateway/website#190**. ## `EVENTS.md` **§D's ledger row is corrected in two places**, and **§L's two rules gain a new subsection** recording what the build settled: - **Rule 1 needed a mechanism**, because a spawn's `ref` does not exist until the module answers. What goes in before the dispatch is a PLACEHOLDER keyed by the step's idempotency key — and that is why §F's `revert` takes the key at all. It is written after the permission check and before the module is reached: a refused step created nothing and must ledger nothing. - **A LEASE does not use it, and gets rule 1 in a stronger form.** Its target is the lease id the step already names, so `core.lease` writes the real row first — which is also the only moment the two-events-one-target refusal can happen *before* the world has been written to. - **Recording is idempotent because the database makes it so.** A retry re-sends the same key and a module may honestly re-report the same resources; the unique key refuses the second insert and core reads that as "already recorded". - **The unique key is held by three statuses and released by three** (amended 2026-09-03). *"Among non-reverted rows"* was written before the six statuses had their meanings; taken literally it makes `drifted` and `orphaned` hold a target for ever, so one bad night would disable a lease permanently with no control able to clear it. Both stay LOUD by the other mechanism — `cleanup_status` and a row on the console — which is what rule 2 actually asks for. - **MariaDB has no partial index**, so the encoding is a STORED generated column reading `status` **alone** — `TEAMS.md` §2.5's correction, because MariaDB refuses `ON DELETE SET NULL` on a foreign key whose column is a base column of one, and `step_id` must stay SET NULL. - **Cleanup is one sweep, not synthetic step rows**, and it runs from one place: a fifth leg of the tick. - **What that sweep SELECTS cost two live-walk defects in opposite directions**, and the pair is the finding worth keeping. Deriving *"is there anything to do"* from a summary column instead of from the rows stranded a lease outright, and then made the retry bound mean one attempt. Both are recorded with the reasoning, including the third defect: the first fix made the run console render *"3 attempts"* beside resources nothing had ever tried, because a counter that means two things is a counter a screen cannot render. - **Drift is a third outcome, not a failure with a flag**, and **`orphaned` is only ever reached through reconcile** — a revert that finds nothing there is a success, whereas a resource the module reports missing is a thing that vanished while nobody was looking. Two different sentences to the operator reading the console the morning after. **§F** gains the lease's verb — core's, with the reason — and the `reconcile` bullet, including the sentence that governs it: *core has no concept of the game being up, so the module says when.* **§I's** *"cancel takes `{ reason }` and not `{ cleanup }`"* becomes what shipped, and names the eighth control. **§K's** role table puts re-running cleanup in the `admin`-only row. **§L's** cancel row and cleanup-fails row are updated. **Observability** names the six new log kinds. **§ API surface**: the `cleanup` row is filled in with why it is narrower than the seven controls beside it, `cancel` gains its flag, and **the absent-routes list is now empty** — nothing in that table is missing any more. **The phase table**: P8 ticked. ## `EVENTS_PLAN.md` Phase 8 marked **complete**, in the shape Phases 0–7 use: the four org-lead decisions, the four things the build settled, the throwaway-module proof, the verified numbers, and the live walk. Including the findings worth reading before the diff: - **The three defects only the walk could find**, and why no unit test could: a test that calls the sweep directly never asks what would have *selected* the run. - **A bound has two halves.** The test covering `EVENT_REVERT_MAX_ATTEMPTS` asserted `<= 3`, which the defect's value of 1 satisfied. Both halves — it retried, and it stopped — are asserted now. - **The two properties that needed the process to die**, walked as the plan asks: a placeholder standing over an empty list while a dispatch hangs, then a kill, a restart, the same idempotency key re-sent, and everything given back; and a boot-time reconcile marking a vanished resource `orphaned` rather than `reverted`. - **A latent bug in `eventRunnerSql.test.js`**, found on the way: its model-backed tests were reaching the developer's real schema while asserting against fixtures written into the throwaway database next door. ## `MODULE_API.md` **1.10.0 gains `reconcile()` and `ctx.events.reconcile()` IN PLACE rather than a new version** (org lead, 2026-09-03). A protocol owes a bump once it has landed on `main`; while it is on `edge` it is amended in place — the rule the Teams workstream arrived at, applied to a module API for the first time. That is stated in the entry itself, so nobody later reads the missing 1.11.0 as an oversight. The rule list grows from six to ten, and each is a rule rather than a field: the lease's verb is core's and why; `until` goes down the wire and a module that treats it as advisory has produced a lease that outlives an outage; `revert` must be idempotent and reverting something that does not exist is a **success**; `revert` is also called with the key and an **empty list**, which is the lost-answer case and the whole reason it takes the key; and `reconcile` is optional where `revert` is required, with the asymmetry explained. **§2.4** grows from four contract items to six: core records before it dispatches, and core owns cleanup — derived, never authored, with no `on_teardown` on an action and no cleanup phase in a spec. ## Checked by hand `docs` has no CI, so: **every anchor in `EVENTS.md` resolves** — checked by generating the heading slugs and diffing the link targets against them, not by eye — and every route, identifier, env var and file path named here exists on website#190: `core.lease`, `core.options.leases`, `ctx.events.reconcile`, `EVENT_REVERT_MAX_ATTEMPTS`, `EVENT_CLEANUP_RUN_BATCH`, `event_run_resources`, `live_marker`, `uq_evres_target`, the six log kinds, `unresolvedResources`, and the route in both `routes.manifest.json` and the OpenAPI spec. Diffs are **144/21, 139/16 and 67/9 with no CRLF artefact**, verified by comparing `--numstat` against the real content diff rather than trusting it. --- - [x] AI-assisted: authored with Claude Code (Claude Opus).
wtclaude added 1 commit 2026-09-04 02:21:34 +00:00
The docs half of Event System Phase 8. Code: RunicGateway/website#NNN.

EVENTS.md

Section D's ledger row is corrected in two places and section L gains a new
subsection recording what the build settled:

- Rule 1 needed a mechanism, because a spawn's ref does not exist until the
  module answers. What goes in before the dispatch is a PLACEHOLDER keyed by the
  step's idempotency key, and that is why revert() takes the key at all.
- A LEASE does not use it and gets rule 1 in a stronger form: its target is the
  lease id the step already names, so `core.lease` writes the real row first --
  the only moment the two-events-one-target refusal can happen before the world
  has been written to.
- The unique key is held by three statuses and released by three (amended
  2026-09-03). "Among non-reverted rows" was written before the six statuses had
  their meanings; taken literally it makes `drifted` and `orphaned` hold a target
  for ever, so one bad night would disable a lease permanently with no control
  able to clear it.
- MariaDB has no partial index, so the encoding is a STORED generated column
  reading `status` ALONE -- TEAMS.md 2.5's correction, because MariaDB refuses
  ON DELETE SET NULL on a foreign key whose column is a base column of one.
- Cleanup is one sweep, not synthetic step rows, and it runs from one place.
- What that sweep SELECTS cost two live-walk defects in opposite directions, and
  the pair is the finding worth keeping: deriving "is there anything to do" from
  a summary column instead of from the rows stranded a lease outright, and then
  made the retry bound mean one attempt. Both are recorded with the reasoning.

Section F gains the lease's verb and the reconcile bullet; section I's "cancel
takes { reason } and not { cleanup }" becomes what shipped; section K's role
table puts re-running cleanup in the admin-only row; section L's cancel and
cleanup-fails rows are updated; Observability names the six new log kinds; and
the API surface table's absent-routes list is now empty.

EVENTS_PLAN.md

Phase 8 marked complete in the shape Phases 0-7 use: the four org-lead
decisions, the four things the build settled, the verified numbers, and the live
walk -- including the three defects only it could find and the two properties
that needed the process to die.

MODULE_API.md

1.10.0 gains `reconcile()` and `ctx.events.reconcile()` IN PLACE rather than a
new version. A protocol owes a bump once it has landed on `main`; while it is on
`edge` it is amended in place, which is the rule the Teams workstream arrived
at, applied to a module API for the first time. The rule list grows from six to
ten: the lease's verb is core's, `until` goes down the wire, revert is
idempotent and reverting something that does not exist is a success, revert is
also called with the key and an empty list, and reconcile is optional where
revert is required.

Checked by hand

`docs` has no CI, so: every anchor in EVENTS.md resolves (checked by generating
the heading slugs and diffing), and every route, identifier, env var and file
path named here exists on the website branch -- `core.lease`,
`core.options.leases`, `ctx.events.reconcile`, `EVENT_REVERT_MAX_ATTEMPTS`,
`event_run_resources`, `live_marker`, `uq_evres_target`, the six log kinds, and
the route in both routes.manifest.json and the OpenAPI spec. Diffs are 144/21,
139/16 and 67/9 with NO CRLF artefact, verified by comparing --numstat against
the real content diff rather than trusting it.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 1581101e6e into edge 2026-09-04 05:11:39 +00:00
whitlocktech deleted branch docs/events-phase-8 2026-09-04 05:11:40 +00:00
Sign in to join this conversation.
No description provided.