Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Engagement Datastore

Behind every run is a local SQLite database that turns the assessment from a pile of one-shot az / Resource Graph queries into a queryable, incremental system. Inventory, resource configuration, relationships, findings, and coverage are loaded once and read back from the database — so agents look things up instead of re-hitting Azure, attack-path reasoning becomes a SQL join, and the report can say exactly what changed since last time.

Why a database

This is deliberately not a hosted database. The unit of work is one engagement; a per-engagement file matches the blast radius, the retention story, and the gitignore boundary.

What it stores

Two databases, both gitignored, both living under engagements/:

engagements/<session>/engagement.db — per-run store + cache

Schema: tools/datastore/schema.sql. Core tables:

TableHolds
metaengagement id, tool version, created-at, schema version
subscriptionssubscription metadata in scope
resourcesone row per Azure resource (id, type, name, rg, location, subscription)
resource_factsper-resource configuration facts (key/value/json) with a collected_at timestamp for freshness — config only, never secrets
relationshipstyped edges between resources — the graph attack-path reasoning walks
findingsdeduplicated finding classes (by dedupe_key, fallback id)
affected_resourcesthe instance list unioned onto each finding
evidence, findings_controls, attack_paths, attack_path_stepssanitized evidence, control mappings, attack chains
coverageper-check coverage (assessed / skipped / failed / permission-denied / sampled / partial) — the honest-gaps ledger
tasksdurable task-manifest state (pending / running / done / failed / …) for resume

engagements/_history/<engagement.id>.db — longitudinal store

Schema: tools/datastore/history.schema.sql. Tables runs, finding_history, resource_history; views v_finding_lifecycle (latest state per finding identity) and v_trend_by_severity. This is where new / persisting / resolved / regressed lives.

Cache-on-read

The datastore is a read-through cache in front of Azure, not a second crawler:

  1. Inventory & Scope runs the paged census once and ingests it into the database.

  2. A domain agent asks the database first (query.mjs facts --resource <id>); the fresh subcommand is a freshness probe that exits 0 on a fresh hit and exits 3 on a miss or stale entry.

  3. On a hit, the agent uses the cached config and does not call Azure.

  4. On a miss/stale, the agent runs the targeted check, then the result is ingested so the next reader hits the cache.

Every resource_facts row carries collected_at; a fact older than the engagement’s TTL is treated as a miss and re-collected.

Single-writer model

To avoid write contention under the orchestrator’s parallel fan-out:

Cross-run lifecycle

promote.mjs is the last step of a run: it folds the finished run into the history database and classifies every finding against its prior state — new, persisting, regressed (was resolved, now back), or resolved (was active, absent this run). It emits a reports/delta.json that the report’s executive summary leads with (“What changed”). The first run has no prior, so everything is new. See Reporting.

The toolbelt

All under tools/datastore/, dependency-free Node ESM:

ToolRole
db.mjscore helpers + CLI (init / info / migrate / query); read-only-guarded query
ingest.mjsfiles → DB (the single writer); auto-discovers resources, relationships, coverage, tasks, findings; supports --replace-findings for report-safe snapshots
query.mjsread-only cache API: resources / facts / fresh / findings / coverage / neighbors / next-tasks / stats
export.mjsDB → canonical findings.json artifacts
promote.mjsrun → history + lifecycle; emits delta.json
init      node tools/datastore/db.mjs   init    --db engagements/<session>/engagement.db --engagement <id>
ingest    node tools/datastore/ingest.mjs       --db engagements/<session>/engagement.db --session engagements/<session>
refresh   node tools/datastore/ingest.mjs       --db engagements/<session>/engagement.db --session engagements/<session> --findings engagements/<session>/findings/raw --replace-findings
query     node tools/datastore/query.mjs facts  --db engagements/<session>/engagement.db --resource <id>
export    node tools/datastore/export.mjs       --db engagements/<session>/engagement.db --session engagements/<session> --what all
promote   node tools/datastore/promote.mjs      --db engagements/<session>/engagement.db --history engagements/_history/<id>.db --out engagements/<session>/reports/delta.json

Safety & git