|
|
|
|
@@ -473,6 +473,11 @@ surface would ever show them: `EVENT_STEP_MAX_ATTEMPTS` (3), `EVENT_STEP_RETRY_M
|
|
|
|
|
action's declared `budgetMs` plus a minute, because a registry that lets an action declare an hour
|
|
|
|
|
would otherwise have its steps reclaimed and re-dispatched fifty-nine minutes before they answered.
|
|
|
|
|
|
|
|
|
|
**A run's status is re-read between steps, not only at the top of a tick** (Phase 3). One tick drains
|
|
|
|
|
up to `EVENT_STEPS_PER_TICK` steps from one run, so without this a pause pressed mid-batch would be
|
|
|
|
|
answered by dispatching another two dozen steps — which is not a pause. It is one indexed read per
|
|
|
|
|
step, against a control whose entire value is that it takes effect at once.
|
|
|
|
|
|
|
|
|
|
**Serial within a phase.** The runner works the lowest-`seq` step of the current phase that is not
|
|
|
|
|
terminal, and does nothing with the one after it until that one finishes. This is the only reading
|
|
|
|
|
under which `core.wait` means anything, and the only one under which a cue can gate what follows it.
|
|
|
|
|
@@ -851,11 +856,40 @@ dimensions' labels. `check:modules` already fails core's build on a UO identifie
|
|
|
|
|
- **A GM cue step.** A core action whose implementation is "post the instruction, wait for a human to
|
|
|
|
|
confirm they did it in-client, then advance". No module, no protocol, no world write — and it makes
|
|
|
|
|
the entire system useful on day one, with a GM doing the target-driven parts in-client exactly as
|
|
|
|
|
`ADMIN_CONTROLS.md` §8 argued they should.
|
|
|
|
|
`ADMIN_CONTROLS.md` §8 argued they should. **Both halves exist as of Phase 3**: the action parks
|
|
|
|
|
the step in Phase 2, the run console's confirm ends it.
|
|
|
|
|
- **Live controls that are honest.** Pause, resume, skip a step, force a phase advance,
|
|
|
|
|
cancel-with-cleanup, cancel-without-cleanup — each logged with the actor. *Editing* a running event
|
|
|
|
|
is not one of them (see [Versioning](#versioning-and-editing-a-live-event)).
|
|
|
|
|
|
|
|
|
|
> **Six of those exist as of Phase 3, and two do not — for reasons, not for scope.** Pause, resume,
|
|
|
|
|
> cancel, and a step's confirm, skip and retry are built and gated to `admin` + `moderator`.
|
|
|
|
|
> **`advance` — force a phase forward — is not**, because it has no honest meaning yet: a phase today
|
|
|
|
|
> advances when its steps go terminal, and the per-step skip already does that one step at a time.
|
|
|
|
|
> Phase 5 gives a phase an advance *condition*, and that is the first moment "force it anyway" names
|
|
|
|
|
> something an operator could predict. **Cancel takes `{ reason }` and not `{ cleanup }`**, because
|
|
|
|
|
> the resource ledger a cleanup would work over arrives in Phase 8; a flag that changes nothing is
|
|
|
|
|
> the "control that answers 200 and does nothing" this plan has refused twice already.
|
|
|
|
|
|
|
|
|
|
> **Retry is one control, not two.** A step may be retried only while its run is `paused`, and a
|
|
|
|
|
> paused run is paused *at* that step — so re-queueing without resuming would leave the run in
|
|
|
|
|
> precisely the state it was already in, with a second button the operator now has to find.
|
|
|
|
|
> Splitting them would read as honesty and behave as a trap. The single action re-queues the step and
|
|
|
|
|
> resumes the run, and its log line says both.
|
|
|
|
|
>
|
|
|
|
|
> Two guards make it safe. The step must be `failed`, and it must be **the furthest step its phase has
|
|
|
|
|
> reached** — `MAX(seq) WHERE status <> 'pending'`. The near miss is worth recording because the
|
|
|
|
|
> obvious rule is the wrong one: "the lowest step that is not settled" looks equivalent and is not,
|
|
|
|
|
> because `nextOpenStep` selects `pending` and `running` only and the runner therefore steps *over* a
|
|
|
|
|
> failed step. Under that rule a phase whose second step failed-and-skipped and whose fifth then
|
|
|
|
|
> failed-and-paused would offer retry on the second, re-queueing a row behind the runner's own cursor
|
|
|
|
|
> where it would sit `pending` for ever.
|
|
|
|
|
>
|
|
|
|
|
> `attempts` returns to zero, and that is not the rule Engagement Phase 14 arrived at being broken.
|
|
|
|
|
> That rule is about *sweeps*: an automatic path that reset a counter made the ceiling unreachable and
|
|
|
|
|
> the row immortal. `EVENT_STEP_MAX_ATTEMPTS` bounds what the runner does **unattended**, and a named
|
|
|
|
|
> person deciding once is the thing it is unattended from.
|
|
|
|
|
|
|
|
|
|
**Public surface.** An upcoming-events calendar with series and arcs, a live-status page, and
|
|
|
|
|
published results. It must tolerate a run with `health: 'degraded'` without saying so — "the shard is
|
|
|
|
|
having trouble" is operator information, and the existing shard pages already model exactly this by
|
|
|
|
|
@@ -966,7 +1000,9 @@ controller stamps it from the session.
|
|
|
|
|
| **A GM changes a leased property in-client** | Restore is compare-and-set: current value ≠ what the event applied, so nothing is written. The resource becomes `drifted` and is surfaced beside the unreverted ones. |
|
|
|
|
|
| **A step would exceed its cap** | `refused`, with the dimension and the numbers, surfaced to the author. Not a retry and not a failure — it is an authoring error. |
|
|
|
|
|
| **An action fails** | Per-step `on_failure`, defaulted from the risk class: `retry(n) → skip` for `notify`, `retry(n) → pause` for `change`, `retry(n) → abort_run` for `irreversible`. `pause` stops the run advancing and waits for a human — the right default when the world is half-changed. `n` is `EVENT_STEP_MAX_ATTEMPTS`, 3 by default. **All three dispositions write the STEP `failed`**: `on_failure` says what happens to the run, and a step attempted three times that never worked is `failed` under every one of them. `skipped` is reserved for a step a human skipped from the run console — a status meaning both "nobody ran this" and "this failed and we moved on" would make the console's summary line unreadable. |
|
|
|
|
|
| **A run is cancelled** | Pending steps `cancelled`; a running one is left to finish or time out (nothing can recall a sent command); cleanup steps are generated from the ledger and run. Cancelling *without* cleanup is a separate, logged, admin-only action. |
|
|
|
|
|
| **A run is cancelled** | Pending steps `cancelled`; a running one is left to finish or time out (nothing can recall a sent command); cleanup steps are generated from the ledger and run. Cancelling *without* cleanup is a separate, logged, admin-only action. **A PARKED step is cancelled with the pending ones** (Phase 3): a cue is not a command already sent, it is an instruction nobody is holding, and leaving it `running` would have the console claim a cancelled event is still waiting for someone. The live lease is what tells the two apart, and it is in the `WHERE` clause. |
|
|
|
|
|
| **A human skips a step** | `skipped`, which is what that status is reserved for. Legal for a `pending` step and for a parked cue; refused for a step with a live lease, and unnecessary for a `failed` one — `nextOpenStep` already passes over it, so resuming the run carries the phase past it. |
|
|
|
|
|
| **A human retries a step** | Only while the run is `paused`, and only on the furthest step its phase has reached. `attempts` returns to zero and the run resumes in the same action; both facts are in the log line with the actor. |
|
|
|
|
|
| **Cleanup itself fails** | The run reaches `completed` with `cleanup_status = 'incomplete'`, the unreverted resources listed and a manual retry offered. It does **not** stay `running` — an event whose world changes are still up is a real state, and pretending the event is in progress hides it. |
|
|
|
|
|
|
|
|
|
|
### The ledger's two rules
|
|
|
|
|
@@ -1020,8 +1056,9 @@ no URL moved.
|
|
|
|
|
| `GET /admin/events/runs` | staff | run history across definitions |
|
|
|
|
|
| `GET /admin/events/runs/:runId` | staff | status, phase, steps, caps, resources, cleanup |
|
|
|
|
|
| `GET /admin/events/runs/:runId/log` | staff | the diagnostic log |
|
|
|
|
|
| `POST /admin/events/runs/:runId/pause\|resume\|advance\|cancel` | admin, moderator | live control of a run in flight; `cancel` takes `{ cleanup, reason }` |
|
|
|
|
|
| `POST /admin/events/runs/:runId/steps/:stepId/skip\|retry\|confirm` | admin, moderator | `confirm` resolves a GM cue step |
|
|
|
|
|
| `POST /admin/events/runs/:runId/pause\|resume\|cancel` | admin, moderator | live control of a run in flight (Phase 3); `cancel` takes `{ reason }`, and gains `cleanup` with the ledger in Phase 8 |
|
|
|
|
|
| `POST /admin/events/runs/:runId/advance` | admin, moderator | force a phase forward — **not built**; it has no honest meaning until Phase 5 gives a phase an advance condition |
|
|
|
|
|
| `POST /admin/events/runs/:runId/steps/:stepId/skip\|retry\|confirm` | admin, moderator | Phase 3. `confirm` resolves a GM cue step; `retry` re-queues the step a paused run is stopped at and resumes it |
|
|
|
|
|
| `POST /admin/events/runs/:runId/cleanup` | admin | re-run cleanup over unreverted resources |
|
|
|
|
|
| `GET /admin/events/catalog` | staff | registered actions, param schemas, risk classes, budget dimensions |
|
|
|
|
|
| `GET /admin/events/catalog/options/:sourceId` | staff | a module's option list for a param |
|
|
|
|
|
@@ -1059,6 +1096,26 @@ the shipped demo of Phase 2 is a run that announces, waits and completes without
|
|
|
|
|
which is exactly the thing that needs no control. `core.cue`'s confirm is the first of them that has
|
|
|
|
|
something to act on, and it arrives with the console that shows the cue.
|
|
|
|
|
|
|
|
|
|
**Phase 3 added six routes, and they are the live controls.** `pause`, `resume` and `cancel` on a
|
|
|
|
|
run; `confirm`, `skip` and `retry` on one of its steps. All six are `admin` + `moderator` — the one
|
|
|
|
|
gate in this feature wider than `admin`, and deliberately so (§N2). Every one of them is a
|
|
|
|
|
compare-and-set against the status it is allowed to act from rather than a read-then-write: the
|
|
|
|
|
runner ticks every fifteen seconds, so a console rendered thirty seconds ago describes a run that has
|
|
|
|
|
since moved, and a control that checked in JavaScript and then wrote would race the tick it exists to
|
|
|
|
|
interrupt. A refusal is a `409` naming the status the run is actually in.
|
|
|
|
|
|
|
|
|
|
Still absent, and still for reasons rather than for scope: `advance` (no advance condition until Phase
|
|
|
|
|
5), `cleanup` and cancel's `cleanup` flag (no resource ledger until Phase 8), `verify` and `GET/PUT
|
|
|
|
|
/admin/events/actions` (no caps to price against and no switchboard to serve until Phase 6).
|
|
|
|
|
|
|
|
|
|
Two response fields arrived with the console and are worth naming because both are **derived, not
|
|
|
|
|
columns**. A run carries `waitingSteps`, the number of its steps parked on a human, so the run LIST
|
|
|
|
|
can say so — a cue nobody notices is a run that never advances while looking perfectly healthy from
|
|
|
|
|
the outside. A step carries `parked`, a boolean, because the console has to tell a cue waiting on a
|
|
|
|
|
person from a step some process is mid-dispatch on, and it must not do so by being shown
|
|
|
|
|
`claimed_by` and `claim_expires_at`: those are the runner's business, and a UI that reasoned about
|
|
|
|
|
leases would be a second opinion about who owns a row.
|
|
|
|
|
|
|
|
|
|
A module registers actions server-side and adds **no routes** for them beyond its option endpoints,
|
|
|
|
|
which is what keeps the browser from being able to name a transport.
|
|
|
|
|
|
|
|
|
|
|