Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

Kindevidence_typeLifecycleHow it’s connected
asset_inventorysystem_spec_inventory_attestationliving rowsdiff → attest_spec_inventory
boundary_diagramsystem_spec_boundary_diagramsnapshotupload → link_spec_snapshotattest_spec_snapshot
network_dfdsystem_spec_network_dfdsnapshotupload → link → attest
ppsmsystem_spec_ppsmsnapshotupload → link → attest
interconnectionsystem_spec_interconnectionsnapshotupload → 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 by link_spec_snapshot) and last_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:

  1. Compose the artifact with the scope-artifact-compose recipe. 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.
  2. Upload it: upload_evidence(file_path=…, evidence_type=<system_spec_…>).
  3. Link: link_spec_snapshot(system_id, kind, evidence_id).
  4. 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>/:

SourceRecipeWhat it reads
awsasset-inventory-aws-baselineLive AWS API (boto3) — EC2 instances in v1
azureasset-inventory-azure-baselineLive Azure Resource Manager — Compute VMs in v1
k8sasset-inventory-k8s-baselinekubectl get against the active context — nodes + Deployment/StatefulSet/DaemonSet
iac-workspaceasset-inventory-iac-workspaceStatic 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_id is a free-form string (the platform records cli:<recipe_id> as per-row provenance). idempotency_key defaults to sha256(system_id, recipe_id, scan_timestamp)[:32]; pass your own when replaying a scan. recipe_context_id is optional — set it when the diff is produced inside an active recipe context opened via start_recipe. The diff endpoint accepts recipe_context_id but does not require it, unlike the create_evidence write 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 its last_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 rowsasset_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 envelopecanonical_source_id must reference a source already bound to the system (see list_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.