Evidence Fabric: schema, cost and storage commands

Part of the NovaFabric CLI reference. Both nova and novafabric run the same binary.

Evidence Fabric v1.0 commands (ADR-0066)

Requires pip install novafabric[scale] for full infrastructure support.

nova schema list

List all canonical capsule event type names (cap-001, ADR-0066). The vocabulary is 25 baseline types plus 8 extended span-taxonomy types (ADR-0082, gap-011): StateTransition, MemoryOperation, GuardrailEvaluated, EvaluatorScored, RerankerApplied, VectorRetrievalStarted, VectorRetrievalCompleted, VectorRetrievalFailed — 33 in total.

nova schema list
# RunStarted
# RunCompleted
# RunFailed
# ... (33 types total)

The extended span-taxonomy events have matching Pydantic models in novafabric.capture.events (StateTransitionEvent, MemoryOperationEvent, GuardrailEvent, EvaluatorEvent, RerankerEvent, VectorRetrievalEvent). Identifiers, digests, scores, and counts are captured by default; raw content payloads are opt-in (ADR-0021).

nova cost report

Print a per-run LLM cost report for a tenant (cap-002, requires ClickHouse).

nova cost report --tenant acme --period 24h

Set NOVA_CLICKHOUSE_URL to enable ClickHouse integration.

Token usage-type accounting (ADR-0132 — works today). nova capture automatically records the full provider-reported token usage breakdown on each model-call record as an additive, optional nova.usage block — cached_tokens (prompt-cache read), cache_write_tokens, reasoning_tokens, audio_input_tokens/audio_output_tokens, image_input_tokens/image_output_tokens, total_tokens, plus an open extra map for provider usage types NovaFabric does not yet name. Values are copied verbatim from the provider payload (never re-tokenized locally); an absent field means not reported, never zero. At capsule finalize the per-type sums are rolled up into an optional usage_totals block in capsule.yaml — all offline, no server required. The ClickHouse-backed /api/cost/report totals and per-model rows additionally include cached_tokens. Per-usage-type pricing is provided by the local pricing catalog (ADR-0133 — works today; see nova pricing and nova cost estimate below).

nova cost estimate (experimental, ADR-0133)

Offline cost for one capsule's model calls — no ClickHouse, no server, no network. Each call's recorded nova.cost block is reported verbatim (basis=recorded; it is never overwritten or recomputed). Calls without a recorded cost are priced from the merged local pricing catalog and labeled basis=estimated; models absent from every catalog layer stay unpriced (cost 0.0, exactly the pre-catalog behavior).

nova cost estimate ~/.novafabric/capsules/<run_id>
nova cost estimate ./capsule --pricing-catalog ./pricing.yaml --format json
nova cost estimate ./capsule --at 2026-03-02      # price with the rate in force then

The output carries the merged catalog's sha256: digest so the estimated figures are reproducible against the exact pricing that produced them. Estimated amounts are derived from user-asserted catalog prices — estimates, never billing records (ADR-0066 wording stands).

nova cost attribute (experimental, ADR-0146)

Read-only. Splits recorded spend into productive vs wasted outcomes with a per-status breakdown. Reads a JSON document {runs: [{run_id, status, cost}], productive_statuses?} (a run's status is productive when it is in productive_statuses, default ["success"]; everything else is wasted). This is descriptive evidence, never a verdict — there is no threshold, quota, or over-budget field; whether the wasted spend was acceptable is the operator's call.

nova cost attribute runs.json
nova cost attribute runs.json --json

Exit codes: 0 rendered; 2 the input is missing or malformed. This is the CLI half of NF-148; a collector that derives per-run cost + status from the records a capsule already holds is a documented follow-on.

nova cost fairness (experimental, ADR-0146)

Read-only. Loads per-agent resource totals per dimension (cost / energy / calls) from a JSON document {totals: {dimension: {agent: total}}} and prints a fairness statistic per dimension — each agent's share, the Gini coefficient, and the max/mean ratio. Descriptive evidence, never a verdict: no threshold, quota, or pass/fail.

nova cost fairness totals.json
nova cost fairness totals.json --json

Exit codes: 0 rendered; 2 the input is missing or malformed. This is the CLI half of NF-150; a collector that derives the per-agent totals from a capsule is a documented follow-on.

nova cost rollup (experimental, ADR-0146)

Read-only, report-only (NF-142). Rolls the per-agent spend of an NF-141 cost_attribution facet up the acted-as delegation chain (ADR-0106 §NF-084) so each granter carries its grantees' spend.

nova cost rollup delegation.json ./my-capsule           # reads facets.cost_attribution
nova cost rollup delegation.json 01KZ8Q... --json        # a run id works too
nova cost rollup delegation.json attribution.json        # or a JSON/YAML facet file

Per hop the report gives self_cost (null when nothing was attributed — absent is not zero), subtree_cost, grantees, granter, and depth. The conservation block reports root_subtree_cost, run_total_cost, unchained_cost (attributed agents in no grant), unattributed_cost, and ok (root subtree == run total, exact Decimal equality, no epsilon). Amounts are decimal strings in the run total's currency.

Structural problems are findings, not crashes, and set basis: partial: no_chain, broken_linkage (the hop and the rest of that chain are dropped), self_delegation, cycle (grants inside the cycle are ignored), multiple_granters (rolled up under the smallest granter id only, so nothing is counted twice), unchained_agent, no_run_total, attribution_not_conserved. Grant signatures are not re-verified (signatures_verified: false) — the rollup reads identities only. Nothing is written to the capsule: cost_rollup is not a registered facet. Record-only — no threshold or budget verdict.

Exit codes: 0 report rendered (findings included); 2 an input is missing, oversized (8 MiB / 10,000 grants / depth 256), or malformed (including a cross-currency facet).

nova cost usage-breakdown (experimental, ADR-0132)

Read-only. Reports the composition of a capsule's token volume — each usage type's share of the counted tokens — plus the cached-read ratio and the factual has_reasoning_tokens / is_multimodal flags. It accepts a capsule manifest.json (reading its usage_totals) or a bare usage_totals object. It reports token composition only: no cost/dollars (pricing is ADR-0133) and no efficient/within-budget verdict. It honours the ADR-0132 "absent != zero" rule — a usage type that was never reported is absent from the composition, never zero-filled.

nova cost usage-breakdown my-capsule/manifest.json
nova cost usage-breakdown usage.json --json

Exit codes: 0 rendered; 2 the input is missing or malformed.

nova pricing list|show|add (experimental, ADR-0133)

Local, user-extensible model-pricing catalog for offline cost accounting of self-hosted, fine-tuned, and private models. A catalog is a single local YAML/JSON file merged over the built-in price table; layers (lowest to highest precedence): built-in PRICE_TABLE < user (~/.config/novafabric/pricing.yaml, honors $XDG_CONFIG_HOME) < project (./.novafabric/pricing.yaml) < --pricing-catalog PATH. A higher layer's entries fully replace a lower layer's for the same model_id. Fully offline: no remote registry, no price fetch — NovaFabric never ships live vendor prices as truth; the built-in table is a convenience default you can override.

# Show the whole merged catalog and where each entry came from
nova pricing list
nova pricing list --json | jq .pricing_catalog_digest

# Resolve the effective price for one model (optionally as of a date)
nova pricing show mistral-7b-local
nova pricing show claude-opus-4 --at 2026-03-02

# Price a self-hosted model (writes ./.novafabric/pricing.yaml)
nova pricing add mistral-7b-local --input 0.10 --output 0.30 --unit per_1m \
  --source "internal GPU chargeback 2026-Q3"

# Effective-dated price history (idempotent per model_id + effective-from)
nova pricing add claude-opus-4 --input 0.012 --output 0.060 \
  --effective-from 2026-07-01 --source "negotiated enterprise rate H2 2026"

Prices are keyed by the ADR-0132 usage types (--input, --output, --cached, --reasoning, --audio, --image) with per-unit math (per_1k, per_1m; images are per_image), an ISO-4217 --currency (default USD, never converted), and an optional --effective-from date — the resolver picks the entry in force at the capsule's capture time. nova capture consults the merged catalog automatically when estimating cost_usd_estimated, so self-hosted models get real figures instead of 0.0; a malformed catalog is skipped with a warning and never fails a capture. Catalog file schema: schemas/pricing-catalog.schema.json (schema_version 0.1.0).

nova storage inspect

Show where a run's dual-object split is stored, under the layout in effect.

nova storage inspect --run-id 01HXAY7MZPQRSTUVWXYZ
nova storage inspect --run-id 01HXAY7MZPQRSTUVWXYZ --json

The two layouts are not the same shape, and which one applies depends on whether the S3 backend is configured (NOVA_S3_ENDPOINT_URL or NOVA_S3_BUCKET):

Layout audit object PII object
S3 audit/<run_id>/audit.json pii/<run_id>/pii.json
local <run_id>_audit.json <run_id>_pii.json

The PII object is reported only when NOVA_CAP003_ENABLED=true.

This command computes names; it does not contact the store. An unknown run id yields keys just as readily as a real one, so existence_checked is always false in the JSON output. Use it to write a lifecycle or Object Lock policy, not to prove an object exists.

Corrected 2026-09-02. This command, GET /api/storage/inspect/{run_id}, and the dashboard Storage Operations card all reported <store>/<run_id>_audit.json as the S3 key, while the writer stored audit/<run_id>/audit.json. A lifecycle rule, Object Lock retention policy, or bucket policy scoped to the reported prefix would have matched no object — silently governing nothing. All three now read the writer's own helpers in storage/dual_object_store.py, and a cross-surface guard test pins the agreement.

nova storage validate

Validate that an S3 backend supports Object Lock COMPLIANCE mode (cap-009).

nova storage validate --endpoint http://minio:9000 --bucket nova-capsules
# or via env vars:
NOVA_S3_ENDPOINT_URL=http://minio:9000 nova storage validate

Exits 0 on success, 1 if Object Lock is absent or disabled.

nova erasure request — NOT IMPLEMENTED

Corrected 2026-09-02. This command was documented as queueing an erasure request. It did not. It printed GDPR erasure request queued for run_id=… and exited 0 while doing nothing — no queue row, no key destroyed — including for a run id that did not exist. On a GDPR Art.17 surface a false success is the most damaging behaviour available: the operator records an erasure as started and nothing started.

It now fails loudly (exit 2) and names the working path.

Use instead: nova pii erase <subject_id> for Art.17 crypto-shredding (ADR-0069), or POST /v0/erasure on the server, which uses the persisted queue in pii/erasure_queue.py behind an explicit confirmation and a fail-closed cap-003 gate.

nova erasure status — NOT IMPLEMENTED

Corrected 2026-09-02. This returned status=pending for any id, including ids that were never created — it consulted nothing. A status surface that cannot distinguish "pending" from "never existed" is worse than none on a compliance path. It now fails loudly (exit 2).

The erasure receipts written by nova pii erase are the record of what actually happened.

nova policy capture-level get

Print the current capture level (reads NOVA_CAPTURE_LEVEL env var, default standard).

nova policy capture-level get
# Current capture level: standard

nova policy capture-level set

Print instructions for setting a new capture level (restart required).

nova policy capture-level set --level forensic
# Set NOVA_CAPTURE_LEVEL=forensic (restart required)

Valid levels: minimal, standard, forensic, air_gapped.

Level Description
minimal Run metadata only; no model IDs, tool names, or any PII-adjacent fields
standard Run + model call metadata; redacted PII (default)
forensic Full capture including prompts and responses
air_gapped Full capture; no external network calls permitted

Reference: src/novafabric/runners/_pbs.py, src/novafabric/runners/_lsf.py.