# Data model atlas

One source of truth for the canonical agent-bom data model and how every
field flows from `models.py` dataclasses → DB schemas → output formats →
graph taxonomy. Use this when you need to know:

- which dataclass field becomes which DB column
- which output format includes a given finding attribute
- how the graph taxonomy maps onto the canonical model
- which fields are populated by which scan type

If you change a dataclass field, **update this doc in the same PR**. It
is wired into the docs site so drift produces a visible regression.

<!-- DATA_MODEL_ATLAS:START -->

## Generated Data Model Index

> Generated by `python scripts/regenerate_data_model_atlas.py`. Run with `--check` in CI or before release claims.

### Canonical Dataclasses

| Class | Field count | Fields |
|---|---:|---|
| `Vulnerability` | 25 | `id: str`, `summary: str`, `severity: Severity`, `severity_source: Optional[str]`, `confidence: float | None`, `cvss_score: Optional[float]`, `fixed_version: Optional[str]`, `references: list[str]`, `epss_score: Optional[float]`, `epss_percentile: Optional[float]`, `is_kev: bool`, `kev_date_added: Optional[str]`, `kev_due_date: Optional[str]`, `published_at: Optional[str]`, `modified_at: Optional[str]`, `nvd_published: Optional[str]`, `nvd_modified: Optional[str]`, `nvd_status: Optional[str]`, `cwe_ids: list[str]`, `aliases: list[str]`, `exploitability: Optional[str]`, `vex_status: Optional[str]`, `vex_justification: Optional[str]`, `compliance_tags: dict[str, list[str]]`, `advisory_sources: list[str]` |
| `PackageOccurrence` | 6 | `layer_index: int`, `layer_id: str`, `package_path: Optional[str]`, `layer_path: Optional[str]`, `created_by: Optional[str]`, `dockerfile_instruction: Optional[str]` |
| `Package` | 50 | `name: str`, `version: str`, `ecosystem: str`, `purl: Optional[str]`, `source_package: Optional[str]`, `distro_name: Optional[str]`, `distro_version: Optional[str]`, `vulnerabilities: list[Vulnerability]`, `is_direct: bool`, `parent_package: Optional[str]`, `dependency_depth: int`, `dependency_scope: str`, `reachability_evidence: str`, `resolved_from_registry: bool`, `registry_version: Optional[str]`, `version_source: str`, `declared_version: Optional[str]`, `resolved_version: Optional[str]`, `version_confidence: Optional[str]`, `version_resolved_at: Optional[str]`, `version_evidence: list[dict]`, `version_conflicts: list[dict]`, `floating_reference: bool`, `floating_reference_reason: Optional[str]`, `is_malicious: bool`, `malicious_reason: Optional[str]`, `license: Optional[str]`, `license_expression: Optional[str]`, `deps_dev_resolved: bool`, `supplier: Optional[str]`, `author: Optional[str]`, `description: Optional[str]`, `homepage: Optional[str]`, `repository_url: Optional[str]`, `download_url: Optional[str]`, `copyright_text: Optional[str]`, `scorecard_score: Optional[float]`, `scorecard_checks: dict[str, int]`, `scorecard_repo: Optional[str]`, `scorecard_lookup_state: Optional[str]`, `scorecard_lookup_reason: Optional[str]`, `integrity_verified: Optional[bool]`, `provenance_attested: Optional[bool]`, `provenance_source: Optional[str]`, `auto_risk_level: Optional[str]`, `auto_risk_justification: Optional[str]`, `maintainer_count: Optional[int]`, `source_repo: Optional[str]`, `occurrences: list[PackageOccurrence]`, `discovery_provenance: Optional[dict]` |
| `MCPTool` | 8 | `name: str`, `description: str`, `discovery_source: Optional[str]`, `discovery_confidence: Optional[str]`, `input_schema: Optional[dict]`, `declared_capabilities: list[str]`, `schema_findings: list[str]`, `schema_rule_findings: list[dict]` |
| `MCPResource` | 5 | `uri: str`, `name: str`, `description: str`, `mime_type: Optional[str]`, `content_findings: list[str]` |
| `PermissionProfile` | 8 | `runs_as_root: bool`, `container_privileged: bool`, `tool_permissions: dict[str, str]`, `capabilities: list[str]`, `network_access: bool`, `filesystem_write: bool`, `shell_access: bool`, `security_opt: list[str]` |
| `MCPServer` | 22 | `name: str`, `command: str`, `args: list[str]`, `env: dict[str, str]`, `transport: TransportType`, `url: Optional[str]`, `tools: list[MCPTool]`, `resources: list[MCPResource]`, `prompts: list[MCPPrompt]`, `packages: list[Package]`, `config_path: Optional[str]`, `working_dir: Optional[str]`, `mcp_version: Optional[str]`, `registry_verified: bool`, `registry_id: Optional[str]`, `permission_profile: Optional[PermissionProfile]`, `security_blocked: bool`, `security_warnings: list[str]`, `security_intelligence: list[dict[str, object]]`, `surface: ServerSurface`, `discovery_sources: list[str]`, `discovery_provenance: Optional[dict]` |
| `Agent` | 14 | `name: str`, `agent_type: AgentType`, `config_path: str`, `mcp_servers: list[MCPServer]`, `version: Optional[str]`, `source: Optional[str]`, `status: AgentStatus`, `discovered_at: str`, `last_seen: Optional[str]`, `parent_agent: Optional[str]`, `metadata: dict`, `automation_settings: list`, `discovery_provenance: Optional[dict]`, `discovery_envelope: Optional[dict]` |
| `BlastRadius` | 41 | `vulnerability: Vulnerability`, `package: Package`, `affected_servers: list[MCPServer]`, `affected_agents: list[Agent]`, `exposed_credentials: list[str]`, `exposed_tools: list[MCPTool]`, `risk_score: float`, `ai_risk_context: Optional[str]`, `owasp_tags: list[str]`, `atlas_tags: list[str]`, `attack_tags: list[str]`, `nist_ai_rmf_tags: list[str]`, `owasp_mcp_tags: list[str]`, `owasp_agentic_tags: list[str]`, `eu_ai_act_tags: list[str]`, `nist_csf_tags: list[str]`, `iso_27001_tags: list[str]`, `soc2_tags: list[str]`, `cis_tags: list[str]`, `cmmc_tags: list[str]`, `nist_800_53_tags: list[str]`, `fedramp_tags: list[str]`, `pci_dss_tags: list[str]`, `ai_summary: Optional[str]`, `suppressed: bool`, `suppression_id: Optional[str]`, `suppression_state: Optional[str]`, `suppression_reason: Optional[str]`, `unsuppressed_risk_score: Optional[float]`, `impact_category: str`, `all_server_credentials: list[str]`, `all_server_tools: list[MCPTool]`, `attack_vector_summary: Optional[str]`, `hop_depth: int`, `delegation_chain: list[str]`, `transitive_agents: list[dict]`, `transitive_credentials: list[str]`, `transitive_risk_score: float`, `graph_reachable: Optional[bool]`, `graph_min_hop_distance: Optional[int]`, `graph_reachable_from_agents: list[str]` |
| `AIBOMReport` | 46 | `agents: list[Agent]`, `blast_radii: list[BlastRadius]`, `generated_at: datetime`, `scan_id: str`, `tool_version: str`, `executive_summary: Optional[str]`, `ai_threat_chains: list[str]`, `mcp_config_analysis: Optional[dict]`, `skill_audit_data: Optional[dict]`, `trust_assessment_data: Optional[dict]`, `prompt_scan_data: Optional[dict]`, `model_files: list[dict]`, `model_manifests: list[dict]`, `model_provenance: list[dict]`, `model_hash_verification_data: Optional[dict]`, `model_supply_chain_data: Optional[dict]`, `enforcement_data: Optional[dict]`, `context_graph_data: Optional[dict]`, `license_report: Optional[dict]`, `vex_data: Optional[dict]`, `toxic_combinations: Optional[list]`, `prioritized_findings: Optional[list]`, `sast_data: Optional[dict]`, `cis_benchmark_data: Optional[dict]`, `snowflake_cis_benchmark_data: Optional[dict]`, `azure_cis_benchmark_data: Optional[dict]`, `gcp_cis_benchmark_data: Optional[dict]`, `databricks_cis_benchmark_data: Optional[dict]`, `aisvs_benchmark_data: Optional[dict]`, `vector_db_scan_data: Optional[list]`, `gpu_infra_data: Optional[dict]`, `iac_findings_data: Optional[dict]`, `runtime_correlation: Optional[dict]`, `delta_data: Optional[dict]`, `scan_performance_data: Optional[dict]`, `training_pipelines: Optional[dict]`, `dataset_cards: Optional[dict]`, `serving_configs: Optional[list]`, `browser_extensions: Optional[dict]`, `ai_inventory_data: Optional[dict]`, `project_inventory_data: Optional[dict]`, `introspection_data: Optional[dict]`, `health_check_data: Optional[dict]`, `runtime_session_graph: Optional[dict]`, `findings: list['Finding']`, `scan_sources: list[str]` |

### Canonical Enums

| Enum | Count | Values |
|---|---:|---|
| `Severity` | 6 | `critical`, `high`, `medium`, `low`, `none`, `unknown` |
| `AgentType` | 30 | `claude-desktop`, `claude-code`, `cursor`, `windsurf`, `cline`, `vscode-copilot`, `cortex-code`, `codex-cli`, `gemini-cli`, `goose`, `snowflake-cli`, `continue`, `zed`, `openclaw`, `roo-code`, `amazon-q`, `docker-mcp`, `jetbrains-ai`, `junie`, `copilot-cli`, `tabnine`, `sourcegraph-cody`, `aider`, `replit-agent`, `void`, `aide`, `trae`, `pieces`, `mcp-cli`, `custom` |
| `TransportType` | 4 | `stdio`, `sse`, `streamable-http`, `unknown` |
| `ServerSurface` | 10 | `mcp-server`, `container-image`, `oci-tarball`, `filesystem`, `sbom`, `external-scan`, `os-packages`, `sast`, `ai-inventory`, `other` |
| `AgentStatus` | 2 | `configured`, `installed-not-configured` |
| `EntityType` | 27 | `agent`, `server`, `package`, `tool`, `tool_call`, `model`, `dataset`, `container`, `cloud_resource`, `resource`, `vulnerability`, `misconfiguration`, `credential`, `credential_ref`, `org`, `account`, `user`, `group`, `role`, `policy`, `service_account`, `service_principal`, `federated_identity`, `provider`, `environment`, `fleet`, `cluster` |
| `RelationshipType` | 34 | `hosts`, `uses`, `depends_on`, `provides_tool`, `exposes_cred`, `reaches_tool`, `serves_model`, `contains`, `affects`, `vulnerable_to`, `exploitable_via`, `remediates`, `triggers`, `shares_server`, `shares_cred`, `lateral_path`, `manages`, `owns`, `part_of`, `member_of`, `assumes`, `trusts`, `attached`, `inherits`, `can_access`, `cross_account_trust`, `acted_as`, `invoked`, `called`, `used_credential`, `accessed`, `delegated_to`, `correlates_with`, `possibly_correlates_with` |

### Live Schema Cross-Checks

| Schema | Check | Value |
|---|---|---:|
| `config/schemas/inventory.schema.json` | root required fields | `agents` |
| `config/schemas/inventory.schema.json` | `Agent.agent_type` enum values | 30 |
| `config/schemas/inventory.schema.json` | `Package.ecosystem` enum values | 9 |
| `config/schemas/inventory.schema.json` | `MCPServer.transport` enum values | 3 |
| `docs/openapi/v1.json` | paths | 180 |
| `docs/openapi/v1.json` | component schemas | 55 |

<!-- DATA_MODEL_ATLAS:END -->

---

## 1. Canonical in-memory model (`src/agent_bom/models.py`)

The canonical model is dataclass-based, strongly typed, and is the
single source of truth that every other surface serializes from.

| Class | Purpose | Key fields |
|---|---|---|
| `Severity` enum | Severity ladder | `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `NONE`, `UNKNOWN` |
| `AgentType` enum | Discovered agent kind | `CURSOR`, `CLAUDE_DESKTOP`, `CLAUDE_CODE`, `CODEX`, `WINDSURF`, `CORTEX`, `CONTINUE`, `ZED`, `KIRO`, … |
| `TransportType` enum | MCP transport | `STDIO`, `HTTP`, `SSE`, `WEBSOCKET` |
| `ServerSurface` enum | Where the MCP server runs | `LOCAL`, `CONTAINER_IMAGE`, `IN_CLUSTER`, `CLOUD_HOSTED` |
| `AgentStatus` enum | Lifecycle state | `DISCOVERED`, `APPROVED`, `QUARANTINED`, `RETIRED` |
| `Vulnerability` | One CVE/OSV/GHSA advisory | `id`, `summary`, `severity`, `cvss_score`, `epss_score`, `is_kev`, `cwe_ids`, `aliases`, `vex_status`, `compliance_tags`, `advisory_sources` |
| `PackageOccurrence` | Where a package was found in a container layer | `layer_index`, `layer_id`, `package_path`, `dockerfile_instruction` |
| `Package` | One dependency or OS package | `name`, `version`, `ecosystem`, `purl`, `vulnerabilities`, `is_direct`, `dependency_depth`, `license`, `scorecard_score` |
| `MCPTool` | A tool exposed by an MCP server | `name`, `description`, `input_schema`, `output_schema`, `dangerous`, `network`, `filesystem`, `risk_level` |
| `MCPResource` | A resource exposed by an MCP server | `uri`, `name`, `description`, `mime_type` |
| `PermissionProfile` | What an MCP server can touch | `can_read_files`, `can_write_files`, `can_execute_commands`, `network_access`, `requires_credentials` |
| `MCPServer` | One discovered server | `name`, `command`, `transport`, `surface`, `packages`, `tools`, `resources`, `permissions`, `verified`, `runs_as_root`, `env` |
| `Agent` | One discovered agent | `name`, `agent_type`, `source`, `mcp_servers`, `config_path`, `version`, `metadata` |
| `BlastRadius` | The chain package → vulnerability finding → MCP server (exposed tools + credential env names) → connected agents | `vulnerability`, `package`, `affected_servers`, `affected_agents`, `exposed_credentials`, `exposed_tools`, `risk_score`, `compliance_tags`, `*_tags` (15 framework-specific tag fields) |
| `AIBOMReport` | Top-level scan output | `scan_id`, `agents`, `blast_radius`, `summary`, `framework_catalogs`, `scan_sources`, `tenant_id` |

### Compliance tag fields on `BlastRadius`

These are the lookup keys the report endpoint and dashboards use to map
a finding to a control. Adding a framework means adding a tag field
here AND a tag-population step in the relevant scanner / enricher.

| Field | Framework |
|---|---|
| `owasp_tags` | OWASP LLM Top 10 |
| `owasp_mcp_tags` | OWASP MCP Top 10 |
| `owasp_agentic_tags` | OWASP Agentic Top 10 |
| `atlas_tags` | MITRE ATLAS |
| `nist_ai_rmf_tags` | NIST AI RMF |
| `nist_csf_tags` | NIST CSF |
| `nist_800_53_tags` | NIST 800-53 |
| `iso_27001_tags` | ISO 27001 |
| `soc2_tags` | SOC 2 |
| `cis_tags` | CIS Controls |
| `cmmc_tags` | CMMC |
| `fedramp_tags` | FedRAMP |
| `eu_ai_act_tags` | EU AI Act |
| `pci_dss_tags` | PCI DSS |

---

## 2. Finding model (`src/agent_bom/finding.py`)

Used for unified findings outside the SCA / blast-radius path
(secrets, IaC misconfigs, K8s posture, cloud misconfigs).

| Class | Purpose |
|---|---|
| `FindingType` enum | `vulnerability`, `secret`, `misconfiguration`, `policy_violation`, `runtime_event` |
| `FindingSource` enum | Which scanner produced it (`osv`, `ghsa`, `iac`, `secrets`, `cloud_aws`, `runtime_proxy`, …) |
| `Asset` | Target of a finding (file, package, cloud resource, K8s resource, container image) |
| `Finding` | The finding itself — `id`, `type`, `source`, `severity`, `title`, `description`, `asset`, `evidence`, `remediation`, `compliance_tags` |

---

## 3. Graph taxonomy (`src/agent_bom/graph/types.py`)

The unified graph projects the canonical model into a node/edge form
used for blast-radius traversal, dashboards, and OCSF export.

### Entity types (27)

`AGENT`, `SERVER`, `PACKAGE`, `TOOL`, `TOOL_CALL`, `MODEL`, `DATASET`,
`CONTAINER`, `CLOUD_RESOURCE`, `RESOURCE`, `VULNERABILITY`,
`MISCONFIGURATION`, `CREDENTIAL`, `CREDENTIAL_REF`, `ORG`, `ACCOUNT`, `USER`,
`GROUP`, `ROLE`, `POLICY`, `SERVICE_ACCOUNT`, `SERVICE_PRINCIPAL`,
`FEDERATED_IDENTITY`, `PROVIDER`, `ENVIRONMENT`, `FLEET`, `CLUSTER`.

### Relationship types (34)

| Group | Relationships | Direction |
|---|---|---|
| Composition | `HOSTS`, `USES`, `DEPENDS_ON`, `PROVIDES_TOOL`, `EXPOSES_CRED`, `REACHES_TOOL`, `SERVES_MODEL`, `CONTAINS` | source → target |
| Risk | `AFFECTS`, `VULNERABLE_TO`, `EXPLOITABLE_VIA`, `REMEDIATES`, `TRIGGERS` | mostly bidirectional |
| Lateral movement | `SHARES_SERVER`, `SHARES_CRED`, `LATERAL_PATH` | symmetric |
| Ownership and identity | `MANAGES`, `OWNS`, `PART_OF`, `MEMBER_OF`, `ASSUMES`, `TRUSTS`, `ATTACHED`, `INHERITS`, `CAN_ACCESS`, `CROSS_ACCOUNT_TRUST` | hierarchical / access path |
| Runtime | `ACTED_AS`, `INVOKED`, `CALLED`, `USED_CREDENTIAL`, `ACCESSED`, `DELEGATED_TO` | source → target, time-stamped |
| Cross-environment correlation | `CORRELATES_WITH`, `POSSIBLY_CORRELATES_WITH` | local ↔ cloud agent correlation |

`EXPLOITABLE_VIA` is a capability-impact edge from a vulnerability to a
tool when the affected package is connected to the MCP server that provides
that tool. Because package-to-function call stacks are not always observable,
the edge evidence records `mapping_method` and `confidence`; conservative
server-scope mappings must not be presented as exact function-level proof.

### Canonical class → graph node mapping

| Canonical class | EntityType | Node id pattern |
|---|---|---|
| `Agent` | `AGENT` | `agent:{name}` |
| `MCPServer` | `SERVER` | `server:{name}` |
| `Package` | `PACKAGE` | `pkg:{ecosystem}/{name}@{version}` |
| `MCPTool` | `TOOL` | `tool:{server_name}/{tool_name}` |
| `Vulnerability` | `VULNERABILITY` | `vuln:{id}` |
| Credentials (env keys) | `CREDENTIAL` | `cred:{server_name}/{key}` |
| Cloud findings | `CLOUD_RESOURCE` / `MISCONFIGURATION` | `cloud:{provider}/{resource_id}` |
| Container layers | `CONTAINER` | `container:{image_ref}` |

The mapping lives in `src/agent_bom/graph/builder.py`. If a new
entity type is added to the enum, the builder must learn to emit it
or the dashboard will silently miss it.

`src/agent_bom/context_graph.py` still exposes `NodeKind` and `EdgeKind` for
backward-compatible reachability scoring and the legacy `context_graph` MCP
tool. Those enums are a strict subset of the canonical taxonomy above and are
bridged through `src/agent_bom/graph/compat.py` before data reaches the richer
API/UI graph. They must not be documented as the complete graph schema.

---

## 4. Persistence layers

Four backends, one canonical model. Each store enforces tenant
isolation either via a `tenant_id` column (Postgres / ClickHouse /
Snowflake) or via per-tenant SQLite paths.

For the operator-facing logical entity → store/table contract, see
[`site-docs/deployment/control-plane-data-model.md`](../site-docs/deployment/control-plane-data-model.md).

### SQLite — `src/agent_bom/db/schema.py`

Single-file local store. Used for the offline vuln DB and the
single-node `agent-bom serve` deployment.

| Table | Purpose | Tenant-scoped? |
|---|---|---|
| `vulns`, `affected`, `epss_scores`, `kev_entries` | Bundled offline OSV/EPSS/KEV catalog | n/a (read-only catalog) |
| `jobs` | Scan job state | yes (per-tenant SQLite path or in-row column) |
| `fleet_agents` | Fleet inventory | yes |
| `gateway_policies` | Gateway policy rules | yes |
| `policy_audit_log` | Gateway policy mutation audit | yes |
| `sources` | Source registry | yes |
| `audit_log` | HMAC-chained audit | yes (in `details.tenant_id`) |
| `idempotency_keys` | idempotent write tracking | yes |
| `api_rate_limits` | shared runtime rate-limit state | yes |

### Postgres — `src/agent_bom/api/postgres_store.py`

Primary transactional control plane. Per-tenant filters on every
list/put plus session-scoped `app.current_tenant` for RLS.

| Table | Source dataclass | Notes |
|---|---|---|
| `scan_jobs` | `ScanJob` (api/models.py) | global `job_id` PK + tenant column (`team_id` / `tenant_id`) |
| `cis_benchmark_checks` | CIS benchmark JSON blobs normalized via `analytics_contract.build_cis_benchmark_check_rows` | indexed cloud/status/priority remediation rows for `/v1/cis/checks`; backfilled by `scripts/migrations/backfill_cis_benchmark_checks.py` |
| `fleet_agents` | `FleetAgent` (api/fleet_store.py) | trust score + lifecycle state |
| `api_keys` | `ApiKey` (api/auth.py) | scrypt hashes, expiry, role, tenant |
| `exceptions` | `Exception` (api/exceptions.py) | suppression workflow |
| `gateway_policies` + `policy_audit_log` | `GatewayPolicy` | RBAC-gated mutations, tenant-native reads |
| `scan_schedules` | `ScanSchedule` (api/schedule_store.py) | cron-driven |
| `audit_log` | `AuditEntry` (api/audit_log.py) | HMAC-chained, tamper-evident |
| `trend_history` | aggregated severity counts | for `/v1/trends` |
| `graph_nodes`, `graph_edges`, `graph_snapshots`, `attack_paths`, `interaction_risks`, `graph_filter_presets` | `UnifiedGraph` family | per-scan snapshots + delta alerts |
| `osv_cache` | OSV API response cache | TTL-bounded |
| `api_rate_limits` | Sliding-window rate limit state | per-tenant bucket key |

### ClickHouse — `src/agent_bom/cloud/clickhouse.py`

OLAP-only. Append-only analytics for trends, runtime events, posture.
This is not a transactional control-plane replacement.

| Table | Source dataclass | tenant_id |
|---|---|---|
| `vulnerability_scans` | `BlastRadius` flattened | yes (#1501) |
| `runtime_events` | runtime detector output | yes (#1501) |
| `posture_scores` | analytics_contract.posture_snapshots | yes (#1501) |
| `scan_metadata` | analytics_contract.scan_metadata | yes (#1501) |
| `fleet_agents` | `FleetAgent` snapshot | yes (native) |
| `compliance_controls` | per-control measurement | yes (#1501) |
| `cis_benchmark_checks` | normalized cloud CIS check rows + remediation | yes |
| `audit_events` | `AuditEntry` denormalized | yes (native) |

Every `query_*` method on the analytics store accepts `tenant_id` and
injects a `WHERE tenant_id = '<scope>'` predicate. Absent `tenant_id`
is deliberately cross-tenant and reserved for admin surfaces.

### Snowflake — `src/agent_bom/api/snowflake_store.py`

Warehouse-native deployment for governance-heavy customers. Today it
persists `scan_jobs`, `fleet_agents`, `gateway_policies`, and
`policy_audit_log` with Snowflake-specific column types. It also supports
`scan_schedules` and `exceptions`, so the warehouse-native contract is
broader than the original jobs/fleet/policy slice. It is not yet at full
control-plane parity:

- no source registry persistence
- no API-key / RBAC persistence
- no graph persistence
- no trend/baseline persistence
- no full `audit_log` transactional replacement

For the operator-facing backend matrix and current parity boundaries, see
`site-docs/deployment/backend-parity.md`.

### Neptune — design-only candidate

Neptune is not wired as a graph backend today. The current implementation
contract remains SQLite for local graph persistence and Postgres/Supabase for
the transactional control plane.

The candidate Neptune lane maps to the API `GraphStoreProtocol` rather than
the analysis-only `GraphBackend` abstraction:

| Graph concept | Candidate Neptune shape | Required invariant |
|---|---|---|
| graph snapshot | snapshot metadata vertex or side table keyed by `tenant_id` + `scan_id` | immutable per-scan reads |
| node | vertex keyed by `tenant_id`, `scan_id`, stable graph node ID | tenant predicate on every query |
| edge | relationship keyed by `tenant_id`, `scan_id`, source, target, relationship | tenant predicate and bounded traversal |
| attack path | materialized path vertex/edge payload | no ad hoc unbounded traversal in API calls |
| temporal edge fields | relationship attributes (`valid_from`, `valid_to`, confidence, provenance) | preserve replay/diff semantics |
| saved presets | stay in the transactional API store unless a graph-only deployment is designed | no silent feature loss |

No production SLO, support claim, Helm value, dependency extra, or adapter class
is implied by this mapping. See
`docs/decisions/008-pluggable-neptune-graph-backend.md` for the design boundary.

### Deployment-context posture contract — `GET /v1/posture/counts`

This endpoint is the lightweight capability contract used by the UI to
adapt navigation and deployment-specific surfaces without fetching full
scan payloads. It must be derived from **tenant-scoped state only**:
completed scan jobs, fleet inventory, and gateway policy/audit stores.

| Field | Meaning | Source |
|---|---|---|
| `deployment_mode` | `local`, `fleet`, `cluster`, or `hybrid` | derived from the booleans below |
| `has_local_scan` | direct/local scan evidence exists | `scan_sources` + MCP/agent context |
| `has_fleet_ingest` | governed fleet inventory exists | fleet store |
| `has_cluster_scan` | cluster/GPU/K8s evidence exists | `scan_sources` + fleet agent environment |
| `has_ci_cd_scan` | CI/CD workflow scanning exists | `scan_sources` (`github_actions`) |
| `has_mesh` | mesh/topology views are meaningful | fleet + MCP/agent/runtime evidence |
| `has_gateway` | central gateway policy plane is configured | policy store |
| `has_proxy` | proxy/runtime enforcement evidence exists | policy audit + runtime signals |
| `has_traces` | trace/timeline views are meaningful | runtime correlation/session graph |
| `has_registry` | image/SBOM/registry-oriented scans exist | `scan_sources` |

If a new scan mode or deployment surface is added, update this table in
the same PR as the route and nav change.

---

## 5. Agent, runtime, and intel schema envelopes

These API, CLI, MCP, and UI contracts are not separate data models. They are
typed envelopes over the same inventory, finding, graph, runtime, and local
intel state described above. New envelopes must name their `schema_version`,
tenant boundary, persistence behavior, and redaction behavior here.

| Envelope | Producers | Consumers | Key fields | Persistence / tenant boundary |
|---|---|---|---|---|
| `agent-bom.manifest/v1` | `agent-bom manifest`, `GET /v1/agent-bom/manifest`, `/manifest` dashboard | local users, fleet control planes, graph previews, downstream governance jobs | `summary`, `agents`, `mcp_servers`, `visibility`, `blueprint_drift`, `graph` | Local mode reads discovered config; control-plane mode reads tenant-scoped fleet and MCP observation stores. Credential values are never emitted; only credential reference names are allowed. |
| `agentic_identity_graph.v1` | MCP proxy audit records from normalized `event_relationships` | runtime graph, audit export, ExposurePath enrichment, SIEM/export hooks | `nodes` and `edges` for actor, `tool_call`, tool, credential reference, and resource refs | Derived only from redacted runtime relationship refs. Raw prompts, argument values, and credential material are not graph inputs. |
| `runtime.production_index.v1` | `GET /v1/runtime/production-index`, `runtime_production_index` MCP tool | runtime cockpit, deploy/runbooks, drift evaluator, SIEM/export hooks | `traffic`, `policy_decisions`, `authorization_trace`, `alerts`, `active_sources`, `active_sessions`, `freshness`, `retention_posture` | Derived from tenant-scoped proxy/gateway metrics and alerts. Raw prompts and tool arguments are `no_persist`; the endpoint is metadata/redaction-first. |
| `runtime.blueprints.v1` | `GET /v1/runtime/blueprints`, `runtime_blueprints` MCP tool | admins, runtime policy authors, drift evaluator, docs | role/profile blueprint list with `allowed_tool_categories`, `restricted_tool_categories`, `approval_required_for`, `retention_mode`, `evidence_required` | Static canonical catalog in `runtime_blueprints.py`; custom blueprint persistence is not shipped yet. |
| `runtime.blueprint_drift.v1` | `GET /v1/runtime/blueprints/{blueprint_id}/drift`, `runtime_blueprint_drift` MCP tool | runtime operators, agents, alerting, release evidence | `status`, `drift_score`, `observed.categories`, `violations`, `warnings`, selected `blueprint`, `retention` | Compares the selected blueprint against the tenant-scoped production index. Current behavior is report-only and fail-closed to metadata retention. |
| `intel.sources.v1` | `GET /v1/intel/sources`, `intel_sources` MCP tool | analysts, agents, freshness monitors, docs | canonical source catalog with `source_id`, tier, kind, URL, license, redistribution policy, `feed_run` | Reads local vuln DB `sync_meta`; source metadata is shared catalog data, feed-run freshness is local deployment state. |
| `intel.lookup.v1` | `GET /v1/intel/advisories/{advisory_id}`, `intel_lookup` MCP tool | finding enrichment, analyst lookup, agent investigation | `found`, `query`, `advisory.canonical_ids`, severity, CVSS, EPSS, KEV, CWE, affected packages, `evidence_links` | Read-only local vuln DB lookup. It can resolve CVE, GHSA, and OSV aliases and returns source links without implying every link was fetched. |
| `intel.match.v1` | `POST /v1/intel/match`, `intel_match` MCP tool | inventory enrichment, CI gates, agent posture checks | submitted packages, matched package count, advisory matches, evidence links | Read-only package-coordinate matching. Inputs use purl when present; otherwise `ecosystem` + `name` + optional `version`. |

### `agent-bom.manifest/v1`

The Agent BOM manifest answers "what AI agents, MCP servers, tools, credential
references, owners, and runtime observations are present?" in one portable
document. It deliberately emits credential **names** such as `API_KEY`, never
values.

| Field | Meaning | Source |
|---|---|---|
| `summary.agents` / `summary.mcp_servers` / `summary.tools` | high-level local or tenant-scoped inventory counts | local discovery or fleet + MCP observation stores |
| `summary.credential_refs` | unique credential environment names referenced by servers | sanitized MCP server env metadata |
| `summary.runtime_observed_servers` | servers seen through runtime observations | MCP observation store |
| `summary.gateway_registered_servers` | servers registered with the gateway | MCP observation store |
| `agents[]` | local or fleet agent records | `Agent` dataclass or `FleetAgent` |
| `mcp_servers[]` | configured and observed MCP server records | `MCPServer` dataclass or `MCPObservation` |
| `visibility` | ownership, shadow runtime, untracked runtime, warning, and risky credential-reference counts | derived from manifest agents + servers |
| `blueprint_drift` | observation-only manifest posture signals | derived from manifest agents + servers |
| `graph` | nodes/edges over agents, users, environments, servers, tools, and credential refs | manifest graph projection |

`visibility.risk_signals` contains identifiers for the counted posture gaps:
`unowned_agent_ids`, `shadow_runtime_server_ids`,
`untracked_runtime_server_ids`, and `risky_credential_refs`. The embedded
`blueprint_drift` block is intentionally observation-only; enforcement happens
in runtime proxy/gateway policy and the runtime blueprint drift endpoint.

### `agentic_identity_graph.v1`

The runtime proxy projects normalized event relationships into a graph-ready
identity path:

```text
agent/user -> tool_call -> tool
                      \-> credential_ref
                      \-> resource
```

The projection starts from `event_relationships`, not raw MCP payloads. That
means persisted graph nodes are stable references such as tool names,
credential-reference names, and sanitized resource labels; prompts, argument
values, and credential values are not graph inputs. The graph schema exposes
`tool_call`, `credential_ref`, and `resource` node types plus runtime edges
`acted_as`, `invoked`, `called`, `used_credential`, and `accessed`.

### Runtime blueprint drift

`runtime.blueprint_drift.v1` is a runtime comparison, not a manifest-only
heuristic. It classifies live production-index tool names into deterministic
categories, compares those categories against the selected role/profile
blueprint, and returns:

- `status`: `no_runtime_activity`, `aligned`, `review`, or `drift_detected`
- `drift_score`: bounded 0-1 score derived from high and medium-weight signals
- `observed.categories`: category counts by runtime activity
- `violations`: restricted categories observed at runtime
- `warnings`: approval-required, outside-allowed, unclassified, or audit-signal
  cases that need review

The first shipped version is report-only. A future enforcement mode must update
this section, the runtime route docs, MCP tool metadata, and proxy/gateway
fail-open/fail-closed notes in the same PR.

### Intel lookup and matching

The threat-intel envelopes are local, source-attributed lookup surfaces over
the existing vulnerability database. They are not yet the full pluggable
IntelItem corpus proposed for future connector work.

| Join key | Preferred use |
|---|---|
| `purl` | package ecosystems where OSV/GHSA-style package matching is most accurate |
| `ecosystem` + `name` + `version` | local inventory rows without a purl |
| `CVE-*` / `GHSA-*` / OSV ID | advisory lookup and evidence links |
| `CWE-*` | weakness explanation and compliance/remediation mapping |

When full IntelItem provenance lands, it must preserve this read-only lookup
shape while adding raw-artifact provenance, signatures, corroboration, citation
spans, and connector health as additional fields or linked resources.

### `inventory_snapshot.packages`

JSON reports now expose flattened packages in both the top-level `packages`
array and `inventory_snapshot.packages`. The invariant is:

```text
top-level packages == ai_bom_entities.packages == inventory_snapshot.packages
```

Nested `agents[].mcp_servers[].packages` remains present so consumers can
reconstruct where a package was found. The flattened
`inventory_snapshot.packages` list exists so importers can validate and replay
inventory without walking every nested agent/server relationship.

### Graph/cache hot-path indexes

The Postgres graph and cache backends are tuned for the query shapes
the UI and API actually execute:

| Table | Query shape | Required indexes |
|---|---|---|
| `graph_nodes` | tenant + snapshot search ordered by severity/risk | `idx_pg_graph_nodes_scan`, `idx_pg_graph_nodes_scan_order` |
| `graph_edges` | neighbor expansion by source/target in one snapshot | `idx_pg_graph_edges_scan`, `idx_pg_graph_edges_scan_source`, `idx_pg_graph_edges_scan_target` |
| `attack_paths` | fix-first path lists ordered by composite risk | `idx_pg_attack_paths_scan`, `idx_pg_attack_paths_scan_risk` |
| `graph_snapshots` | latest/previous snapshot lookup | `idx_pg_graph_snapshots_recent` |
| `osv_cache` | TTL cleanup and expiry sweeps | `idx_cache_age` |

Any new backend should preserve these access patterns or document the
replacement plan explicitly.

---

## 6. Output formats (`src/agent_bom/output/`)

18 formats, all derived from `AIBOMReport`. The conversion contract is
**lossy in one direction**: every format strips fields it cannot
represent, but the canonical report keeps everything.

| Format | Module | What it includes | What it drops |
|---|---|---|---|
| JSON | `json_fmt.py` | Full report, lossless | nothing |
| SARIF 2.1 | `sarif.py` | Findings, locations, fix recommendations | runtime trace, fleet membership |
| CycloneDX 1.6 (+ ML BOM) | `cyclonedx_fmt.py` | Components, vulnerabilities, services, ML-BOM `model` blocks | dashboard-only attributes |
| SPDX 3.0 | `spdx_fmt.py` | Packages, relationships, licenses | runtime + agent context |
| HTML | `html.py` | Interactive dashboard view | suitable for emailed PDF |
| Graph JSON | `graph_export.py` | `UnifiedGraph` nodes/edges/paths | textual narratives |
| Graph HTML | `graph.py` | Cytoscape-rendered graph | — |
| GraphML | `graph_export.py` | XML graph for analysis tools | — |
| Neo4j Cypher | `graph_export.py` | `CREATE` statements | — |
| JUnit XML | `junit.py` | Findings as test failures (CI gating) | severity-below-threshold drops |
| CSV | `csv_fmt.py` | One finding per row | nested objects flattened |
| Markdown | `markdown.py` | Human-readable report | tables only |
| Mermaid | `mermaid.py` | Blast-radius graph as Mermaid | layout details |
| SVG | `svg.py` | Static graph render | interactivity |
| Prometheus | `prometheus.py` | Severity counts as gauges | finding details |
| Badge | `badge.py` | shields.io badge | everything except status |
| Attack Flow | `attack_flow.py` | MITRE Attack Flow JSON | non-attack-path nodes |
| Plain text | `__init__.py` | Console summary | everything except top-line stats |
| **OCSF** (event delivery) | `ocsf.py` | Findings as OCSF events for SIEM / security-lake | Used for streaming, not as a `-f ocsf` report option |

---

## 7. Per-scan-type populated-fields matrix

Different scan types populate different subsets of `AIBOMReport`. A
field empty for scan type X is intentional, not a bug.

| Scan type | `agents` | `blast_radius` | `mcp_servers` | `iac_findings` | `cloud_findings` | `secret_findings` |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| `agents` (default) | ✅ | ✅ | ✅ | — | — | optional `--secrets` |
| `image` | synthetic single agent | ✅ | n/a | — | — | optional |
| `iac` | — | — | — | ✅ | optional `--k8s-live` | — |
| `cloud aws/gcp/azure/snowflake` | — | — | — | — | ✅ | — |
| `secrets` | — | — | — | — | — | ✅ |
| `proxy` (runtime) | — | streaming events | n/a | — | — | — |
| `check` (single package) | — | one BlastRadius row | — | — | — | — |

When a consumer asserts on a field that should be populated for the
scan type, refer to this table first.

---

## 8. Trust + tenancy invariants

These hold across every layer. If you change them, this doc must
change in the same PR.

1. **Tenant propagation** — `request.state.tenant_id` is set by
   `APIKeyMiddleware` from the resolved API key, OIDC claim, or SAML
   assertion. Every store accepts it as a kwarg or context-var; every
   query enforces a filter.
2. **Audit chain** — `AuditEntry` carries `prev_signature` and
   `hmac_signature`. Tampering with any field breaks the chain on the
   next `verify_integrity` call.
3. **Signed exports** — `/v1/audit/export` and
   `/v1/compliance/{framework}/report` both ship
   `X-Agent-Bom-*-Signature` headers so the bundle is verifiable
   off-server with the operator's HMAC key.
4. **Read-only by default** — the scanner never writes configs, never
   executes MCP servers, never persists credential values. Outbound
   network calls are limited to the package metadata, version
   resolution, and CVE enrichment endpoints documented in the README
   trust table.
5. **Tenant_id default value** — `"default"`. Used only when no
   middleware ran (CLI, single-tenant deployments). Never a sentinel
   for missing data.

---

## 9. Pointers

- README sections that reference these flows: `## How a scan moves through the system`, `## Deploy in your own AWS / EKS`, `## Trust & transparency`, `## Compliance`
- Architecture deep-dive: [site-docs/architecture/how-agent-bom-works.md](../site-docs/architecture/how-agent-bom-works.md)
- Canonical vs OCSF: [site-docs/architecture/canonical-vs-ocsf.md](../site-docs/architecture/canonical-vs-ocsf.md)
- Data ingestion modes: [site-docs/architecture/data-ingestion-and-security.md](../site-docs/architecture/data-ingestion-and-security.md)
- Governance and retention evidence: [docs/DATA_GOVERNANCE_RETENTION.md](DATA_GOVERNANCE_RETENTION.md)
- Capture protocol for screenshots: [docs/CAPTURE.md](CAPTURE.md)
- Visual language for diagrams: [docs/VISUAL_LANGUAGE.md](VISUAL_LANGUAGE.md)
