Asset Inventory & System Spec
“Scope” is more than the questionnaire. Auditors expect five system-spec artifacts connected to the scope page: an asset inventory plus four snapshot kinds — authorization boundary, network data-flow diagram (DFD), ports/protocols/services matrix (PPSM), and interconnection. The platform exposes these as typed entities so the inventory can evolve over time and each snapshot can be attested independently.
| Kind | evidence_type | Lifecycle | How it’s connected |
|---|---|---|---|
asset_inventory | system_spec_inventory_attestation | living rows | diff → attest_spec_inventory |
boundary_diagram | system_spec_boundary_diagram | snapshot | upload → link_spec_snapshot → attest_spec_snapshot |
network_dfd | system_spec_network_dfd | snapshot | upload → link → attest |
ppsm | system_spec_ppsm | snapshot | upload → link → attest |
interconnection | system_spec_interconnection | snapshot | upload → link → attest |
All endpoints under /api/v1/public/systems/{id}/spec/* require a token with
the system_spec.read (reads) or system_spec.write (writes) scope.
Connecting is the step that’s easy to miss. The scope page reads each kind’s state from
system_spec_kinds.current_evidence_item_id(set bylink_spec_snapshot) andlast_attested_at(set by the attest tools). Uploading evidence alone leaves an orphaned row the scope page never shows — always produce → upload → link → attest.
Producing and connecting snapshot artifacts
The four snapshot kinds are produced and connected from the CLI/MCP — they are
no longer platform-UI-only. The flow is driven by the scope-artifacts
workflow (route to it with start_task intent scope_artifacts), which
walks each required kind:
- Compose the artifact with the
scope-artifact-composerecipe. It builds a self-contained, brand-consistent HTML document — authorization boundary and network DFD as inline SVG diagrams, PPSM and interconnection as tables — from sources the agent can reach (the asset inventory,az/aws/kubectl, workspace IaC), with secret redaction and a provenance footer. - Upload it:
upload_evidence(file_path=…, evidence_type=<system_spec_…>). - Link:
link_spec_snapshot(system_id, kind, evidence_id). - Attest:
attest_spec_snapshot(system_id, kind, evidence_id, sufficiency).
The asset inventory is a living kind (rows + an attestation, not a file
upload): scan or upload it (below), then attest_spec_inventory.
When start_task routes to scope-artifacts, it seeds the plan with the
all_required_spec_kinds_attested completion gate, so complete_plan refuses
until every required (non-toggled-off) kind is attested.
CLI commands
Asset-inventory commands live under pretorin scope artifacts:
# 1. List the 5 artifact kinds with their required / toggle / attest state.
pretorin scope artifacts list
# 2. Inspect the current asset inventory (or replay history).
pretorin scope artifacts inventory show
pretorin scope artifacts inventory show --as-of 2026-04-01T00:00:00Z
# 3. Upload a CSV.
# The CLI fetches the current platform inventory first, classifies your CSV
# rows into added / modified / decommissioned, shows a diff preview, and
# posts a single diff.
pretorin scope artifacts inventory upload assets.csv
# 4. Scan a source and apply the resulting diff.
pretorin scope artifacts inventory scan aws # live AWS account
pretorin scope artifacts inventory scan azure # live Azure subscription
pretorin scope artifacts inventory scan k8s # live K8s via kubectl
pretorin scope artifacts inventory scan iac-workspace # local .tf / k8s YAML files
pretorin scope artifacts inventory scan aws --dry-run # show diff without applying
# 5. Toggle an artifact kind required/optional.
# Rationale is required when toggling off.
pretorin scope artifacts toggle interconnection --optional \
--rationale "single-tenant; no external system interconnections in scope"
Scan sources
Each inventory scan <source> runs a built-in recipe that lives under
src/pretorin/recipes/_recipes/asset-inventory-<source>/:
| Source | Recipe | What it reads |
|---|---|---|
aws | asset-inventory-aws-baseline | Live AWS API (boto3) — EC2 instances in v1 |
azure | asset-inventory-azure-baseline | Live Azure Resource Manager — Compute VMs in v1 |
k8s | asset-inventory-k8s-baseline | kubectl get against the active context — nodes + Deployment/StatefulSet/DaemonSet |
iac-workspace | asset-inventory-iac-workspace | Static parse of .tf, .tf.json, K8s YAML, and cloudformation.{json,yaml} files in the cwd |
The iac-workspace recipe is the generalized “look at the IaC checked into
this repo” path — it does not call any cloud API and does not require any
cloud credentials. Use it when you want the inventory to match the desired
state in source control rather than current cloud state.
Adding a new source means adding a new recipe directory and updating
SCAN_SOURCE_TO_RECIPE_ID in src/pretorin/spec.py. Community recipes can
live under ~/.pretorin/recipes/ and override built-ins by id.
Decommission semantics
The diff endpoint never silently revives a decommissioned asset. If you
upload a CSV (or scan) that contains an external_id matching a
previously-decommissioned row, the platform returns an outcome: "not_found"
item with a message telling you to re-activate via the UI rather than re-scan.
The CLI surfaces this clearly in the diff response — look for the yellow
“Some rows did not apply cleanly” block.
MCP tools
Read + diff (inventory):
list_artifact_requirements(system_id)— wraps the kinds endpoint; the authoritative source for which kinds are required and their attest state.get_asset_inventory(system_id, as_of?)— wraps the inventory read.submit_asset_inventory_diff(system_id, recipe_id, added?, modified?, decommissioned?, idempotency_key?, recipe_context_id?)— posts the diff.recipe_idis a free-form string (the platform recordscli:<recipe_id>as per-row provenance).idempotency_keydefaults tosha256(system_id, recipe_id, scan_timestamp)[:32]; pass your own when replaying a scan.recipe_context_idis optional — set it when the diff is produced inside an active recipe context opened viastart_recipe. The diff endpoint acceptsrecipe_context_idbut does not require it, unlike thecreate_evidencewrite surface.
Connect (snapshots + inventory attestation) — workflow-tier writes that accept
optional plan_id / step_index and record a PlanArtifact on the active
plan:
link_spec_snapshot(system_id, kind, evidence_id)— link an uploaded evidence item as a snapshot kind’s current artifact.attest_spec_snapshot(system_id, kind, evidence_id?, sufficiency?)— attest a snapshot kind (boundary_diagram/network_dfd/ppsm/interconnection), setting itslast_attested_at.attest_spec_inventory(system_id, sufficiency?, attestation_name?)— attest the living asset inventory.
kind is a free-form string on these tools: call list_artifact_requirements
for the authoritative kind set for the system rather than hard-coding it.
Field constraints (platform-validated)
A few values are validated server-side; the wrong value comes back as a 422:
- Asset inventory rows —
asset_type∈{container, datastore, endpoint, network_device, other, saas, server, service};environment∈{dev, dr, other, prod, staging};data_classification∈{cui, fci, internal, other, phi, pii, public, secret}. Map your source’s vocabulary onto these (e.g. an AKS cluster →service, a VM →server, a storage account / Key Vault →datastore, “production” →prod). - Sufficiency envelope —
canonical_source_idmust reference a source already bound to the system (seelist_connected_sources); a free-form string is rejected. Omit it when there’s no bound source — the other sufficiency fields (capture_query,data_coverage_*,verification_state,producer_*,actor_role_snapshot) are accepted on their own.