# Asset Inventory & System Spec

Auditors require five "system spec" artifacts: an asset inventory plus four
snapshot kinds (data flow, network topology, threat model, hardware/software
baseline). The platform exposes these as typed entities so the inventory can
evolve over time and each snapshot can be attested independently.

This page covers the CLI / MCP surface for the asset inventory specifically.
The four snapshot kinds are authored in the platform UI today; toggling them
required/optional is the only CLI touchpoint.

## CLI commands

All commands live under `pretorin scope artifacts`. They wrap the public
`/api/v1/public/systems/{id}/spec/*` endpoints and require a token with the
`system_spec.read` (reads) or `system_spec.write` (writes) scope.

```bash
# 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 threat_model --optional \
    --rationale "single-tenant scope; threat model lives in linked vendor SSP"
```

## 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

Three tools mirror the read + diff endpoints for AI agents:

- `list_artifact_requirements(system_id)` — wraps the kinds endpoint.
- `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
  being produced inside an active recipe context opened via `start_recipe`.

The diff endpoint accepts `recipe_context_id` cleanly but does **not** require
it, unlike the `create_evidence` write surface. There's no 11-field audit
metadata envelope required for inventory diffs.
