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 24hSet 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 thenThe 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 --jsonExit 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 --jsonExit 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<delegation.json>— grants in thenovafabric.trust.delegationshape:{grants: [...]}(one ordered chain, root first) and/or{chains: [{grants: [...]}, ...]}(one chain per root-to-leaf path; their union is the tree). Onlygranter_id/grantee_id(and public keys, for linkage) are read.<capsule|attribution.json>— a capsule directory or run id, or a file holding the facet (bare, undercost_attribution, or underfacets.cost_attribution).
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 --jsonExit 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 --jsonThe 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.jsonas the S3 key, while the writer storedaudit/<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 instorage/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 validateExits 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=pendingfor 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 (exit2).
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: standardnova 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.