Emergency services
Last reviewed against the codebase: 2026-08-08 (Phase 16.5 backend — agency delegated administration + jurisdictional authority, flag-gated; on 16.4 broadcast / area alerting + escalation, 16.3 operator exclusion zones, 16.2 Command Center, and 16.1-A..E agency control-plane).
Fire Path AI includes an emergency-services / agency persona alongside the resident-facing app. It is
a decision-support + values-at-risk + prediction layer designed to support agency decision-making —
never to replace official warnings or an agency’s own incident-management system. The authoritative,
always-current status lives in the repo: PREDICTION-STATUS.md (prediction claims) and
EMERGENCY-SERVICES-PLAN.md (the Phase 16 workstream), with the grounded audit in
EMERGENCY-SERVICES-AUDIT-2026-08-07.md.
What exists today
- RBAC foundation (production-grade). Four roles —
homeowner,agency_officer,council_admin,system_admin— withmemberships(user → org → role).requireRole()re-reads the user’s roles fresh from the DB on every request, fails closed, and audits denials. See Security. - Role-gated emergency-services spread preview (
GET /predictions/spread-preview) — a temporal footprint readout for planning. Homeowner tokens receive 403; ops tokens receive a response flaggedinDevelopment: true. Not alert-trusted. - Prediction-accuracy scorecard (
GET /predictions/accuracy-scorecard+ the “Prediction accuracy” screen) — the “prove-it” artifact grading the modelled corridor against observed fire perimeters. Honest by construction: it never says “validated” and shows an explicit “not yet a skill claim” state until enough real fires are scored and an SME signs off. Ops/agency only; homeowner tokens 403. - Command Center (preview, incident-command IA — Phase 16.2). A desktop three-pane ops view (map · situational awareness · command rail) and a mobile field scroll, reachable from the welcome screen. The situational rail is an incident list → incident detail flow (sharing the map’s pane-toggle pattern): each incident shows an ops-framed card (behaviour, movement, total FRP, recency — deliberately not the resident “distance from you”), and the selected incident drives the operator-selectable temporal spread preview. It is honestly labelled a preview and distinguishes live data (fires, tracked incidents, at-risk assets, telemetry) from illustrative sample data (crew positions + dispatch actions, grouped under one “Dispatch — preview” block). Values-at-risk are honestly scoped to the operator’s own registered assets.
- Service Telemetry — a role-gated operational dashboard (ingest health, API usage, sim runs, cost).
- Agency control-plane API (
/admin/agencies/*, system_admin) — create an agency/council org, invite an officer (one-time hashed token), verify an officer, and list members/invitations. Every mutation writes an un-sampledadmin_actionsaudit row. - In-app system-admin console — an “Agencies” screen (create / invite / verify / revoke) under a system_admin-only “Administration” nav section.
- Invitee accept / sign-in (
/auth/agency/*) — an invited officer redeems their token, sets a password, and gets a real account + membership in their agency org. Flagged off (AGENCY_ACCOUNTS_ENABLED) until onboarding; identity is pluggable (local now, per-agency SSO later). - Operator exclusion zones (
/admin/zones/*, Phase 16.3) — a verified officer declares a no-go / restricted / prescribed-burn area (advisory overlays) or asuppress-hotspotzone (a known false-positive). Three stacked gates (requirePermission("zones.write")+requireVerifiedOfficer+assertZoneOrgAccess), the whole surface behindEXCLUSION_ZONES_ENABLED, and every write audited toadmin_actions. Asuppress-hotspotzone excludes its hotspots from the tracked-fire model and — only behind the separateZONE_ALERT_SUPPRESSION_ENABLEDflag, and never for an Emergency Warning — from the alert paths. Reason + expiry are required for suppression; expiry restores the fire. A non-silent “N active zones” banner + aFireMapmap overlay keep suppression visible. - Broadcast / targeted area alerting (
/admin/notifications/*, Phase 16.4) — a verified officer composes a warning and sends it to the people who own a registered asset inside a drawn radius or polygon. There is no boundary data (no LGA / postcode / state polygons), so the audience is exactly “registered asset-owners intersecting the shape” — never “everyone in the suburb”. Compose → preview the exact recipient count → confirm → send; the send fans out one per-useralertsrow (source="broadcast") that rides the existing push + inbox paths. Four stacked gates (requirePermission("broadcast.send")+requireVerifiedOfficer+assertBroadcastAuthority+ an explicitconfirmfor Emergency tier), the whole surface behindBROADCAST_ENABLED(off), every send audited. Emergency Warning is the only quiet-hours bypass.assertBroadcastAuthorityis the single authority seam: today own-org, with a wide audience (over a tunable recipient/km² cap) restricted tosystem_admin; Phase 16.5 swaps its internals for State/region jurisdiction + per-staff grants without touching the route. - Delivery escalation queue (
/admin/notifications/escalations, Phase 16.4-D) — an undelivered Emergency Warning past the escalation window now records a durable, ack/resolve-ableescalation_eventsrow in addition to the CloudWatch alarm log, so a life-safety non-delivery is never just a log line. This safety-net is not behindBROADCAST_ENABLED— it is always available to ops. - Delegated administration + jurisdictional authority — backend (
/admin/authority/*, Phase 16.5, behindDELEGATED_AUTHORITY_ENABLED) — the authority model beneath the 16.4 seam. A newagency_adminrole administers only their own agency: define ajurisdictionstree (Federal → State → region → …, arbitrary depth, extensiblelevel, optional admin-drawn boundary — decoupled from orgs so one org can span many jurisdictions and one jurisdiction can be shared by several), assign an officer to a jurisdiction, and grant/revoke per-staff authority (membership_grants— broadcast audience ceilings, zone/fire management). When the flag is on,assertBroadcastAuthorityuses each officer’s grant ceiling (falling back to the flat cap when un-granted) and requires the drawn area to sit within their jurisdiction boundary (falling back to no containment where un-mapped) — so an un-configured deploy behaves exactly like the 16.4 interim rule.system_adminunbounded; every mutationassertAgencyOrgAccess-confined + audited. The in-app Agency Admin console (/admin/authority, 16.5-E) lets an admin define jurisdictions- draw boundaries, assign officers to a jurisdiction, and grant/revoke per-staff authority — surfaced under
the ops nav for
agency_admin+system_admin, server-gated the same way. Phase 16.5 is complete (A–E).
- draw boundaries, assign officers to a jurisdiction, and grant/revoke per-staff authority — surfaced under
the ops nav for
What is planned (Phase 16)
The staged plan (EMERGENCY-SERVICES-PLAN.md) closes the gaps, each phase RBAC-gated, audited, and
reversible:
| Phase | Adds | Status |
|---|---|---|
| 16.0 | Security + honesty hardening; un-orphan the scorecard; shared access card | built |
| 16.1-A/B | Durable-core schema (invitations, admin_actions, membership verification) + agency control-plane API (create org, invite, verify) + requirePermission() | built |
| 16.1-D | In-app system-admin console (create / invite / verify / revoke) | built |
| 16.1-E | Invitee accept / minimal flagged sign-in (AGENCY_ACCOUNTS_ENABLED off) | built |
| 16.1-C | Real per-agency tenancy (agency control-plane, council_admin org-confined, flagged) | built |
| 16.2 | Command Center redesign (incident-command IA, operator-selectable preview) | built (Scope A) |
| 16.3 | Operator exclusion zones + audited writes + flag-gated alert suppression | built (A–E; annotation split out) |
| 16.4 | Broadcast / area alerting + real escalation | built (flagged; web/email need keys) |
| 16.5 | Agency delegated administration + jurisdictional authority (agency_admin role, jurisdictions tree, membership_grants, Agency Admin console; the seam consumes grants + jurisdiction behind DELEGATED_AUTHORITY_ENABLED) — supersedes the 16.4 interim authority seam | built (A–E, flagged) |
| 16.6 | Moderation queue; places curation, test-fire injection, audit review | planned |
| 16.7 | Agency SSO/SAML federation + state incident feeds | needs partnerships |
Identity is pluggable. How an officer authenticates (local invite→set-password now; per-agency
OIDC/SAML later — the auth_provider enum already carries both) is deliberately separated from who they
are (orgs, roles, memberships, verification). Tenancy and verification key off org membership, so they are
independent of the sign-in mechanism — an agency’s own SSO slots in at token-minting only, with the RBAC
and tenancy model unchanged. The agency control-plane (/admin/agencies/*) is system_admin-gated and
every mutation writes an un-sampled admin_actions row.
Access model
Ops surfaces are gated server-side by requireRole(["system_admin", "agency_officer"]) (defence in
depth: the client also hides them from non-ops sessions); the agency control-plane adds
requirePermission("agency.manage") (system_admin only), and the operator write surfaces add
requirePermission("zones.write") / requirePermission("broadcast.send") + requireVerifiedOfficer. A
homeowner token is 403 on every ops endpoint. Verified-officer identity — the precondition for any
suppression or broadcast authority — is built (manual verification + a flagged invitee sign-in), but the
invitee flow ships dark until onboarding.
Broadcast authority is a single seam. assertBroadcastAuthority decides who may send how wide in one
place, so the interim rule (own-org; a wide audience — over a tunable recipient/km² cap — is system_admin-only)
can be replaced by the Phase 16.5 model (an officer’s State/region jurisdiction bounding their reach + an
Agency Admin granting per-staff broadcast / fire-management authority) without touching the broadcast
route. The same design now also governs the 16.3 zone writes: assertZoneAuthority (16.5-D) gates the
exclusion-zone route so that, when DELEGATED_AUTHORITY_ENABLED is on, a non-system_admin officer needs an
active grant (fires.manage for suppress-hotspot, zones.write for advisory kinds) and must draw
within their jurisdiction — un-granted is denied (a zone is a binary capability, deliberately stricter than
broadcast’s flat-cap fallback). Both operator surfaces now run through one delegated-authority model.
Tenant isolation is enforced on the agency control-plane: a delegated council_admin is confined to
their own org (assertAgencyOrgAccess — roles read fresh from the DB, fail-closed, audited — behind
AGENCY_DELEGATION_ENABLED, off by default so today’s behaviour is system_admin-only); system_admin is
unconfined. Note the deliberate scope: the only tenant-specific data today is agency membership. Shared
national data (fires, predictions, the accuracy scorecard) is intentionally not org-partitioned — every
agency sees the same fires — so tenancy is localized to the control-plane, not applied to shared reads.