feat(events): enablement, per-run caps and mayInvoke (Phase 6)
Two new tables — event_action_settings (the deployment switchboard) and event_run_budget (what a run has spent and the most it may) — plus verified_at and verified_by on event_versions. The whole authorisation decision moves behind one function, events/authorize.js: role, enablement, cap, and the shard's own switch named as the layer core deliberately does not duplicate. Three routes, none moved: GET/PUT /admin/events/actions (admin in both directions) and POST /admin/events/:id/verify (admin, editor — a dry run dispatches nothing). Four decisions, settled by the org lead 2026-09-03: - The default-off line falls between inspect and change, not between notify and inspect. Read literally, §K shipped core.wait disabled. The same line is the role floor. - The tightest cap wins where two actions spend one dimension, pinned into the run at creation with the action it came from. - A refusal follows the step's on_failure and takes health to degraded — its own status and its own log kind, because a refusal is not an outage. - The verify gate is enforced for scheduled starts only: a human pressing Start now is the review the gate exists to require. Derived and flagged for review: a dry run fails rather than warns on a disabled action or an over-cap plan, and the unattended path does not re-check the starter's role. +111 tests (1921/1847/73/1 — the one failure pre-existing and environmental), including a 403 walk over the real router and two concurrent spends against one cap on a real MariaDB. The live walk found two defects, both fixed here: the run console route dropped the budget it was handed, and the role refusal used a plural verb over a one-item list. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6t8mrAWhZU5vnyYgZTMtL
This commit is contained in:
@@ -53,6 +53,7 @@ import EventsAdmin from './routes/admin/views/EventsAdmin.jsx'
|
||||
import EventsCalendar from './routes/admin/views/EventsCalendar.jsx'
|
||||
import EventEditor from './routes/admin/views/EventEditor.jsx'
|
||||
import EventRun from './routes/admin/views/EventRun.jsx'
|
||||
import EventActions from './routes/admin/views/EventActions.jsx'
|
||||
import TeamsAdmin from './routes/admin/views/TeamsAdmin.jsx'
|
||||
import AccountAdmin from './routes/admin/views/AccountAdmin.jsx'
|
||||
import Moderation from './routes/admin/views/Moderation.jsx'
|
||||
@@ -206,6 +207,10 @@ export default function App() {
|
||||
literal segment is never read as a definition id. */}
|
||||
<Route path="events" element={<EventsAdmin />} />
|
||||
<Route path="events/calendar" element={<EventsCalendar />} />
|
||||
{/* The switchboard (Phase 6). A literal segment, declared before
|
||||
`events/:id` the way the router declares `/actions` before
|
||||
`/:id` — the same collision, on the other side of the wire. */}
|
||||
<Route path="events/actions" element={<EventActions />} />
|
||||
<Route path="events/runs/:runId" element={<EventRun />} />
|
||||
<Route path="events/new" element={<EventEditor />} />
|
||||
<Route path="events/:id" element={<EventEditor />} />
|
||||
|
||||
Reference in New Issue
Block a user