feat(events): a trigger can start a run
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 35s
PR Checks / client-build (pull_request) Successful in 44s
PR Checks / server-tests (pull_request) Successful in 12m45s

Rust PLAN_REDESIGNS §14.3.12 (D370), stage 9 of §14.3. A definition's
published version may carry `starts`: a trigger, a condition and a
cooldown. A matching firing creates a run of it, now.

- spec: `starts` validated by the phase gate's own function (same
  grammar, same dormant rule; a dormant start refuses publish), a
  cooldown from 0m to 7d (30m default), at most ten, event.* refused.
- events/starts.js: the emit path's third subscriber, beside dispatch
  and the gate observer. An unverified version starts nothing; one run
  at a time per definition and the cooldown are one conditional UPDATE
  on the new event_trigger_starts table; the occurrence key makes one
  firing reported twice one run. Ignored firings are counted by why.
- event_runs.origin: the firing (trigger, label, time, subject, matched
  condition, declared variables), also on the run.created audit line.
- Editor: a "Started by the game" panel using the gate's condition
  builder, with the ignored-firings line. Run console: "Started because
  ... fired".
- No module contract change and no MODULE_API bump.

Tests: eventStarts (server 21, client 8); eventRunnerSql +7 against a
real MariaDB (75/75); Module-uo proof on uo.champ.boss_up against the
dev database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-16 01:51:55 -05:00
parent eb773693d3
commit c9e992588b
18 changed files with 1352 additions and 20 deletions

View File

@@ -4422,7 +4422,7 @@
"Admin · Events"
],
"summary": "One run: its status, health, cleanup state and every step with its params and idempotency key",
"description": "The run console. `counts` summarises the step list by status. Steps carry the idempotency key core minted at materialisation — stable across every attempt, which is what lets the game side recognise a repeat. `gates` is the diagnosis panel (Phase 5): one entry per phase that authored an advance condition, already rendered in the condition builder’s own words — `gte` as \"is at least\", `present` as \"is present\" — with the tally, how long it has waited, and the last related firing whether or not it matched. A phase is waiting on its gate only once every one of its steps is terminal; `stalled` means an `on` gate has waited past EVENT_PHASE_STALL_MS, which is visibility and never a timeout — nothing advances a phase but its condition or a human. `budget` is the cap meter (Phase 6), and `resources` is the cleanup ledger (Phase 8): every object this run created and every value it borrowed, with what became of each — `confirmed` is still out there, `reverted` came back, `drifted` means somebody moved it and core left it alone, `orphaned` means the module reports it is gone, and `expired` means the game ended it at its own deadline, as it was told to (terminal, like `reverted`). `unresolvedResources` counts the ones still wanting something, including a placeholder left standing by a lost acknowledgement, which is why it can exceed the length of the list. `participants` is who took part (Phase 10), best first, as a module reported them: `memberKey` is module-opaque, `userId` is filled in only where the module could link the player to an account, and `rank` is null until `core.results.publish` has ranked them — a run whose participants are collected but unranked is a real and visible state, not an error. The run itself carries `resultsPublishedAt`, which is when that table was last published.",
"description": "The run console. `counts` summarises the step list by status. Steps carry the idempotency key core minted at materialisation — stable across every attempt, which is what lets the game side recognise a repeat. `gates` is the diagnosis panel (Phase 5): one entry per phase that authored an advance condition, already rendered in the condition builder’s own words — `gte` as \"is at least\", `present` as \"is present\" — with the tally, how long it has waited, and the last related firing whether or not it matched. A phase is waiting on its gate only once every one of its steps is terminal; `stalled` means an `on` gate has waited past EVENT_PHASE_STALL_MS, which is visibility and never a timeout — nothing advances a phase but its condition or a human. `budget` is the cap meter (Phase 6), and `resources` is the cleanup ledger (Phase 8): every object this run created and every value it borrowed, with what became of each — `confirmed` is still out there, `reverted` came back, `drifted` means somebody moved it and core left it alone, `orphaned` means the module reports it is gone, and `expired` means the game ended it at its own deadline, as it was told to (terminal, like `reverted`). `unresolvedResources` counts the ones still wanting something, including a placeholder left standing by a lost acknowledgement, which is why it can exceed the length of the list. `participants` is who took part (Phase 10), best first, as a module reported them: `memberKey` is module-opaque, `userId` is filled in only where the module could link the player to an account, and `rank` is null until `core.results.publish` has ranked them — a run whose participants are collected but unranked is a real and visible state, not an error. The run itself carries `resultsPublishedAt`, which is when that table was last published, and `origin` when a game firing started it (Rust PLAN_REDESIGNS 14.3.12): the trigger, its label, when it fired, its subject, the start condition it matched in the builder words, and its declared variables. Null for a run started by hand or by the schedule.",
"parameters": [
{
"name": "runId",
@@ -5703,7 +5703,7 @@
"Admin · Events"
],
"summary": "One event definition, including its working spec",
"description": "The editor reads this. The list route serves a summary; this is the whole authored tree, phases and steps included.",
"description": "The editor reads this. The list route serves a summary; this is the whole authored tree, phases and steps included. The spec may carry `starts` (Rust PLAN_REDESIGNS 14.3.12): the triggers that begin a run of the published version, each a phase gate `on` and `where` plus a `cooldown` (0m to 7d, 30m when left out); core own event.* triggers are refused. `triggerStarts` is what they have done lately: the last run a trigger started and the firings ignored since, by why (a run was going, inside the cooldown, or the published version is not verified). Null until a trigger first tries.",
"parameters": [
{
"name": "id",