Contain the discharge before it becomes a watershed incident.
Fenced is a bounded environmental-response agent for airports. When AFFF or contaminated firewater escapes engineered containment, it combines the airport's authoritative drainage graph, interlocks, storage capacity, and response inventory with cited Mireye receptor context. It then selects a pre-approved response, acts through a simulator, verifies physical readback, opens sampling work, and routes unresolved regulatory decisions to a responsible human.
Conceptual illustration created for Fenced. It is not historical incident photography.
Airports already have alarms, valves, spill plans, contractors, and incident-management systems. The difficult gap begins when a fixed containment route fails, lacks capacity, or cannot be verified. Fenced turns that moment into a bounded agent loop:
detect → join context → test interlocks → choose an approved plan
→ act → verify independent readback → verify boundary or escalate
It is an incident workspace, not a map or a plume model. The intended buyer is an airport operator or environmental-response team that already owns the physical network and needs faster, auditable coordination across controls, field resources, sampling, and notification.
- Replays the public August 19, 2024 Brunswick Executive Airport AFFF discharge while clearly separating public history from synthetic topology and counterfactual controls.
- Fetches 50 explicit physical-world fields from Mireye on the server, validates freshness/location/status/provenance, and preserves upstream notes separately from Fenced's claim boundary.
- Converts wetland, groundwater, housing, utility, and evidence-quality signals into visible advisory plan deltas while keeping physical-control authority entirely site-owned.
- Includes nine selectable exercise pathways: the captured Brunswick context; isolated wetland, groundwater, community, utility, and degraded-evidence variants; and capacity, rainfall, and unknown-readback control failures. Every derived variant is labeled synthetic in the interface.
- Runs the typed state machine
RECEIVED → CONTEXTUALIZING → PLANNING → DECIDED → ACTIONING → VERIFYING → BOUNDARY_VERIFIED | ESCALATED. - Simulates a configured outlet isolation, vacuum-truck dispatch, manual boom staging, PFAS sampling packet, and notification draft.
- Fails closed when life safety is active, storage lacks a safety buffer, rainfall leaves the approved playbook, or an acknowledgement lacks explicit independent position readback.
- Tracks hydraulic isolation, mobile recovery, sampling, and reportability as separate objectives so a closed gate never reads as incident closure.
- Keeps reportability unresolved when the chemical formulation or concentration is missing.
Fenced treats acknowledgement and physical proof as separate events. This illustration is conceptual.
| Input or action | Authority | How Fenced uses it |
|---|---|---|
| Drain connectivity, gate identity, capacity, interlocks | Airport GIS, as-builts, PLC/SCADA | Decides which configured paths are physically eligible |
| Terrain, wetlands, water, soils, nearby infrastructure | Mireye with source metadata | Prioritizes response and sampling; never infers a pipe or plume |
| Formulation, SDS, field and lab results | Airport inventory and qualified responders | Supports regulatory and exposure decisions outside the autonomous loop |
| Gate command, dispatch, sampling, notification | Simulator in this repository | Demonstrates bounded action without touching physical equipment |
| Gate position | Independent simulated readback | Required before the hydraulic objective may enter BOUNDARY_VERIFIED |
Mireye does not supply airport drain topology, live valve state, PFAS transport, chemical concentration, or exposure conclusions. A production deployment would additionally require site-owned network data, approved PLC/SCADA interlocks, incident-command authority, local manual override, independent sensors, formulation-specific inventory, and field/lab confirmation.
Requirements: Node.js 24 and npm.
npm ci
npm run devOpen http://127.0.0.1:4173. The deterministic cited replay needs no credentials.
To enable live Mireye context, inject a newly issued credential only into the server process through your secret manager or OS credential store. .env.example documents the supported variables; it is intentionally empty.
MIREYE_API_KEY="$(security find-generic-password -s fenced-mireye-api -w)" npm run devNever expose the key through a VITE_ variable, client code, fixture, build argument, or commit. Environment files are ignored except for the empty template.
docker build -t fenced .
docker run --rm -p 4173:4173 fencedLive mode can be enabled at runtime without baking a secret into the image:
docker run --rm -p 4173:4173 --env-file .env fencedThe production image contains compiled server JavaScript and production dependencies only. Source, tests, Git metadata, environment files, build tooling, and local reports are excluded.
The campaign runner exercises the current catalog, pricing, usage, batch, Ask, and geocode contracts without persisting raw responses. Its default mode is a free plan preview:
npm run campaign:mireyeThe extended campaign has a fixed preflight estimate of 47,225 credits. It samples four screening positions at each of 25 airports, runs three repeatability passes over three 50-field packs, adds a nine-point configured Brunswick screen, audits 175 Ask answers across sampling/groundwater/utility/community questions, and exercises 25 address-resolution cases. Execution requires both an explicit spend ceiling and a runtime credential:
MIREYE_API_KEY="$(security find-generic-password -s fenced-mireye-api -w)" \
npm run campaign:mireye -- --execute --max-credits 47225Before spending, the runner checks the live field and plan catalogs, rejects parcel-priced fields, verifies available credits when the usage response exposes them, and aborts if pricing changes. Requests are paced below 180 per minute and billed calls are never retried automatically. The aggregate report is created with mode 0600 under /private/tmp; raw values, answers, addresses, responses, and tokens are not written.
npm run verifyThe verification pipeline runs strict TypeScript, ESLint, adversarial agent/API/Mireye-adapter tests, client and server production builds, and a high-severity dependency audit. CI repeats the checks on Node.js 24, builds the container, and inspects its runtime boundary. The final image runs as a non-root user.
server/ bounded agent, API, Mireye adapter, replay fixtures
src/ responsive incident workspace and shared types
tests/ action, safety, API, and credential-boundary tests
scripts/ bounded live-contract validation campaign
mcp/ optional Mireye MCP companion configuration and policy
public/ conceptual field-response illustrations
Dockerfile Node.js 24 production image
The replay is anchored to the Maine DEP incident record and initial response update. Drain nodes, gate identities, equipment state, rainfall, commands, receipts, and all counterfactual outcomes are synthetic and labeled in the interface.
EPA's PFOA/PFOS release-reporting requirements depend on chemical mass, not gallons alone. Fenced therefore refuses a below-threshold conclusion without formulation-specific evidence; see the EPA reporting factsheet and AFFF mixture FAQ.
For the optional MCP surface and its safety policy, see mcp/README.md. Security and credential handling are documented in SECURITY.md. Mireye's current machine-readable contracts are published at field catalog, plan catalog, batch fetch, and Ask.

