This document describes the production architecture and the boundaries that preserve router isolation, observation attribution, database safety, and API security.
The application core is cloud-neutral. See Deployment profiles for the local stdio, private HTTP, private OAuth, and public OAuth boundaries, including the planned air-gapped profile.
flowchart LR
subgraph Network[Home / SOHO network]
Y[Yamaha RTX routers]
C[Cisco IOS routers]
A[Optional ASUS AP]
L[Optional dnsmasq / syslog]
M[Optional Agent for Mac<br/>carries process names]
W[Optional Agent for Windows<br/>carries process names]
end
subgraph Server[EgressView Node.js process]
RM[Router manager and registry]
PS[Independent poll scheduler]
IN[Agent ingest<br/>authentication / idempotency / correlation]
N[Normalized sessions and runtime deduplication]
EN[DNS / RDAP / GeoIP / threat enrichment]
DB[(SQLite WAL\nconnections + observations + devices\n+ agent_observations)]
HTTP[Express REST API]
WS[Socket.IO updates]
MCP[MCP stdio / HTTP]
end
Y -->|SSH NAT / ARP| RM
C -->|SSH NAT / ARP| RM
RM --> PS --> N
A -->|HTTP client data| N
L -->|log events| N
M -->|HTTPS ingest<br/>agent initiates| IN
W -->|HTTPS ingest<br/>agent initiates| IN
IN --> N
IN --> DB
N --> DB
N --> EN --> DB
DB --> HTTP --> UI[Browser UI]
N --> WS --> UI
DB --> MCP --> AI[AI assistant]
Agent batches join the same normalization path, and that is a requirement rather than a convenience. Threat matching, enrichment, device tracking, and notifications all operate over connections; an observation that does not become one is listed without ever being checked, which on screen is indistinguishable from having been checked and found safe.
Each configured router has an immutable routerId, a Yamaha or Cisco adapter that implements the common poller contract, and an independent scheduler state. EgressView supports up to 10 routers in any Yamaha/Cisco mix.
The generic scheduler staggers initial polls, enforces a per-cycle timeout, backs off repeated failures, and lets healthy routers continue when another router is unavailable. Adapters translate vendor-specific SSH commands and NAT/ARP output into the same normalized session shape before shared processing begins.
The runtime natural key is (src, dst, dport, proto). When multiple routers see the same communication, EgressView deduplicates the connection while preserving every observer in connection_observations and exposing those stable IDs as observedBy. Deleted router IDs remain as tombstones so history never changes ownership.
A router shows what left the house but not which application sent it. Agents for Mac and Windows fill that gap on their own computers.
batchId creates no duplicates, so a machine that lost an acknowledgement simply sends the batch again.connection_agent_observations; neither observer is lost.connections.process. A later router poll writes NULL there, because a router cannot know it, and the upsert keeps the value the agent supplied.An Agent supplements a router rather than replacing one. It observes its own computer as connections occur, rather than waiting for the router’s poll cycle, but sees nothing else on the LAN. Collection coverage differs between the macOS Network Extension and Windows service; an Agent does not claim to capture every connection.
POST /api/agent/ingest). An accepted batch enters the same normalization path a router poll uses.EgressView uses one SQLite database in WAL mode with separate module connections for history, sessions, devices, enrichment, and beacons. db-bootstrap.js is the explicit startup boundary: history owns schema migration and opens first; all other consumers open only after migration succeeds.
Migrations are append-only and fail-closed. Before a data-changing migration, EgressView checks free space, creates and validates a consistent backup, runs the migration transaction, and verifies the resulting database. Backup restore follows the same principle: validate source, require a safety backup, replace, reopen all consumers, validate the result, and roll back if any stage fails.
Backup cleanup preview and execution run in a dedicated worker thread. Planning reads only each recognized backup’s fixed 100-byte SQLite header, so runtime is based on the number of generations rather than database size. Unusable files do not count toward retention; the newest usable restore points are protected, and candidate metadata plus header state is revalidated immediately before deletion. Full integrity checks remain at creation and restore boundaries.
Schema v5 stores router ownership only in connection_observations; the legacy connections.source column has been removed. API responses still expose a compatibility source value derived from the observer router kinds. The observation-consistency diagnostic checks for missing or orphaned observations and missing router metadata. Schema v6 stores append-only AI conversations; v7 stores provider-reported token usage and the USD estimate calculated at request time; v8 stores append-only scheduled, threat-triggered, manual, and delivery-test AI notification events. The validated src/data/ai-pricing.json catalog supplies versioned rates and source metadata, while each usage row preserves its invocation-time rates. EgressView does not infer costs for older conversations, unknown models, or responses without usage.
/api, plus minimal /healthz and /readyz; see the REST API reference for the current route list.2025-11-25 initialize flow and the stateless
2026-07-28 discover flow without tool drift. Public OAuth staging is
protected by a fail-closed dual-era publication gate that does not modify
DNS or infrastructure; see the MCP setup guide. The selected
runtime boundary is validated by src/deployment-profile.js; AWS is one
public-profile adapter rather than a core dependency.0600 configuration file; API responses expose only passSet/enablePassSet flags.X-Admin-Token as appropriate to the route. Cookie-authenticated mutations also require X-CSRF-Token. Agent endpoints use a separate Agent bearer credential, and public MCP uses OAuth. Health/readiness checks return no sensitive detail; see the API reference for route-level permissions.| Responsibility | Main implementation |
|---|---|
| Process wiring, readiness, and lifecycle | server.js, src/health-state.js |
| HTTP composition and protections | src/http-app.js, src/routes/ |
| Multi-router lifecycle | src/router-manager.js, src/router-registry.js |
| Poll scheduling | src/router-poll-scheduler.js |
| Vendor adapters | src/pollers/yamaha-adapter.js, src/pollers/cisco-adapter.js |
| Runtime normalization/deduplication | src/runtime.js |
| Agent enrolment and approval | src/agent-identities.js, src/routes/agents.js |
| Agent ingest storage and correlation | src/agent-ingest-store.js, src/agent-correlation.js, src/agent-ingest-schema.js |
| macOS agent | apps/agent-macos/ |
| History and observation reads/writes | src/history.js |
| DB bootstrap and migrations | src/db-bootstrap.js, src/db-migrate.js |
| Backup inventory, worker jobs, prune, and restore | src/backup-inventory.js, src/backup-prune-runner.js, src/backup-prune-worker.js, src/backup.js, src/routes/backup.js |
| Browser modules | public/js/ |