Trust Surfaces
Four read-only commands that turn a capsule's trust evidence into something a human can read at a glance: the Merkle proof tree, the trust-attestation radar, the redaction/secret-scan X-Ray, and the portable agent passport. All four are pure projections — they read verification output NovaFabric already produces, mutate nothing, change no schema, and add no dependency.
Status: experimental (v0.61, shipped 2026-07-16).
Updated 2026-07-31 (v0.98.0): two of the three interactive dashboard views
the ADRs describe now exist — the SVG radar glyph (ADR-0173) and the
redaction heat-overlay (ADR-0174), both in the dashboard's Seal tab
(Provenance & Trust group), reading the already-shipped
GET /api/runs/{run_id}/trust-radar and …/redaction-xray endpoints. The
proof-tree explorer (ADR-0172, nova merkle-tree) remains future
design: it has no serve endpoint yet, so there is nothing for a view to read.
The visual half preserves the same honesty contract as the CLI: a guarantee the capsule cannot evidence is drawn as a hollow, dashed tick and is excluded from the filled claim polygon — an unsealed capsule can never be made to look like a failed one, and the shaded area only ever claims what was actually verified. The X-Ray renders paths and states only; per ADR-0174 §1 the payload type carries no field value to render.
nova trust-radar and nova redaction-xray also accept --capsule <dir> and
derive their input from the capsule directly. nova merkle-tree and
nova passport take a small JSON document you assemble from existing outputs
(seal metadata, log-entry.json).
| Command | Shows | ADR |
|---|---|---|
nova merkle-tree |
Which sealed fields roll up to which root — the proof structure | ADR-0172 |
nova trust-radar |
The seven verification guarantees as one shape + verdict | ADR-0173 |
nova redaction-xray |
Per-field protection state + a conservative coverage meter | ADR-0174 |
nova passport |
A portable green/amber/red identity summary for an agent | ADR-0149 |
All four honor the redaction invariant (ADR-0009) at the type level: they carry field paths, hash prefixes, and refs — never field values, finding bodies, or component contents.
nova merkle-tree (ADR-0172)
Renders the Evidence Provenance Merkle proof tree from a sealed capsule's
leaf hashes — leaf → intermediate → seal-root → tsr. The layers are
enumerated by the NovaSeal log's own tree code (trust/novaseal/merkle.py — not
the Evidence Bundle root in evidence/merkle.py), so the projected tree matches
what was signed provided you pass the right root_version. A
supplied sealed_root is compared against the recomputed root: match ⇒
verified, mismatch ⇒ flagged.
Input: a JSON object with leaf_hashes (required) plus optional
leaf_labels (field paths, never values), sealed_root, tsr_hash,
root_version (1 = legacy pairwise-pad, 2 = RFC 6962) and tree_size.
⚠ An absent root_version means 1. Every NovaSeal log created since ADR-0313
writes v2 roots, so copy root_version from log-entry.json — otherwise a genuine
v2 root reports a mismatch, which reads like tampering.
$ nova merkle-tree tree.json --capsule-id run-2041
Merkle proof tree: sealed capsule: run-2041
✓ leaf d2dbf0..6c8188 (verified) metadata.command
✓ leaf 4140bf..00e855 (verified) env_lock.python
✓ leaf 649837..fe0f9a (verified) events[0].digest
✓ leaf 9fde56..502454 (verified) outputs.stdout_sha256
✓ intermediate c76c13..8d9831 (verified)
✓ intermediate 6d001d..2254bf (verified)
✓ seal-root 1313c9..66e0be (verified)--json emits the ProofTree model instead. Exit codes: 0 — rendered
(sealed+verified, or unsealed); 1 — seal-root mismatch (tamper); 2 —
missing/malformed input. Without a sealed_root the tree renders unsealed
with every node unverified. A tsr_hash node renders as unverified in
v0 — RFC 3161 verification is out of scope for this projection.
nova trust-radar (ADR-0173)
Projects a capsule's seven verification guarantees onto a fixed-axis radar —
signature · timestamp · log_integrity · redaction_coverage · secret_scan · policy · eval_gate — in a fixed order so shapes are comparable across
capsules. Booleans plot as 0/1; redaction_coverage is a clamped ratio; an
absent/null guarantee becomes an n/a axis, kept distinct from a fail
(an unsealed capsule has no signature_ok; that is not a failure).
Input: a JSON object of the guarantees (signature_ok, timestamp_ok,
log_integrity_ok, redaction_coverage, secret_scan_clean, policy_pass,
eval_gate_pass), or --capsule <dir>.
⚠ Given a JSON object, the radar is a projection of those seven keys only —
it is not verification. Whoever wrote the JSON chose the values. With
--capsule <dir> (and in the dashboard Seal tab) the seal axes come from
nova verify's own checks, below. For a release gate, use nova verify's exit
code; attested describes the axes, not --strict anchoring.
$ nova trust-radar verify.json --capsule-id run-2041
Trust radar: PARTIAL capsule: run-2041
● Signature 1.00 (ok)
● Timestamp 1.00 (ok)
● Log integrity 1.00 (ok)
◐ Redaction coverage 0.97 (warn)
● Secret scan 1.00 (ok)
● Policy 1.00 (ok)
· Eval gate n/a (na)Verdicts: attested (sealed, every applicable axis ok), partial (sealed,
some axis short of 1), critical (a seal-integrity anchor — signature or
log-integrity — failed), unsealed (no signature guarantee; an unsealed
capsule can never be attested, however clean its policy and eval axes).
Exit codes: 0 for attested/partial/unsealed (informational), 1 only on
critical, 2 on bad input.
--capsule <dir> runs nova verify's own checks (works today). The radar
with --capsule, GET /api/runs/{id}/trust-radar, the dashboard Seal tab and
POST /api/runs/{id}/verify all call the same function that
nova verify <capsule-dir> reports from. A capsule that nova verify rejects is
therefore never attested. The three seal axes mean:
| Axis | ok only when |
|---|---|
| Signature | the DSSE signature verifies, and binds this directory (ADR-0251): the manifest the capsule was sealed with (capsule.yaml) exists and equals the signed payload, no other manifest-named file (capsule.json) disagrees with it, every evidence_digests file still hashes to its signed digest, no file a signed *_ref names was added after sealing, and log-entry.json names this payload. A signer chain configured in novaseal.yaml (ca_bundle) must also validate. A capsule edited after sealing, whose capsule.yaml was renamed or replaced by a capsule.json, carrying another capsule's .seal/, or whose .seal/manifest.dsse is malformed, fails this axis and the verdict is critical. |
| Timestamp | an RFC 3161 token is present, intact, and its TSA chain reaches an anchor you supplied (tsa_ca_certs in novaseal.yaml): timestamp_trust is anchored. An intact but unanchored token plots 0.5 (warn), no token is n/a, a failed token is 0 (fail). See Timestamp trust. |
| Log integrity | Merkle inclusion was established by the carried proof or the sealer's log. An entry that neither could check (log_inclusion: not-checked, which nova verify accepts) plots 0.5 (warn, verdict partial); an entry that failed the check is 0 (fail, verdict critical). |
Like nova verify, the radar verifies from the capsule alone when this machine
has no novaseal.yaml; the profile adds the sealer's log and the operator
anchors. A novaseal.yaml that fails to load leaves the seal axes n/a
(verification could not run), never ok. POST /api/runs/{id}/verify reports
the binding as capsule_binding_ok, and its valid is the verdict nova verify
exits on.
Timestamp trust: anchored, unanchored, absent, invalid
Works today. A timestamp has two separate questions: is the RFC 3161 token
intact (timestamp_ok), and is its time trusted (timestamp_trust)? Anyone
can mint a TSA certificate and sign any genTime, so an intact token alone
proves nothing about when the capsule was sealed. timestamp_trust takes one
of four values:
timestamp_trust |
Meaning | nova verify prints |
Radar axis |
|---|---|---|---|
anchored |
The token verified and its TSA chain reached a TSA anchor you supplied (--tsa-ca-bundle, or tsa_ca_certs in novaseal.yaml). Trusted time. |
Timestamp (RFC 3161): OK |
1.00 (ok) |
unanchored |
The token is intact and bound to this envelope, but no TSA anchor was consulted. Its genTime is an unverified claim. |
UNANCHORED, with the claimed genTime and TSA subject |
0.50 (warn) |
absent |
No token. Timestamping is opt-in (ADR-0292), so no time is claimed. | NOT PRESENT |
n/a |
invalid |
A token is present and failed integrity, or failed the anchored chain check. | Timestamp (RFC 3161): FAIL |
0.00 (fail) |
unanchored never counts as ok:
- On the radar it is a
warnaxis, so a capsule with an unanchored token readspartial, neverattested. nova verifyexits 0 on an unanchored token.nova verify --strictexits 1, becausetimestamp-unanchoredis listed inunanchored_claims.nova verify --json,POST /api/runs/{id}/verifyand the seal-verify route returntimestamp_trustnext totimestamp_ok.timestamp_ok: truewithtimestamp_trust: unanchoredmeans "intact, time not trusted".nova evidence bind-custody --timestamp-okandnova evidence check-admissibility --timestamp-okassert ananchoredtimestamp, not merely an intact token.
With TSA anchors configured, a missing or empty token fails, because the token
is not covered by the DSSE signature. Until 2026-10-22, nova verify printed an
intact but unanchored token as OK.
Before 2026-10-22 the radar ran only the signature, timestamp and log checks,
so a capsule tampered after sealing could show attested. Until 2026-10-23 a
sealed capsule whose capsule.yaml was renamed to capsule.json and edited
still read attested, while nova query and the runs list served the edited
status: the missing capsule.yaml counted as "not present" and capsule.json
as an extra file. Both now fail the Signature axis, and every reader of a sealed
capsule's status takes it from the signed payload.
nova redaction-xray (ADR-0174)
Projects a capsule's field-protection state — which fields are clear,
redacted, secret_scrubbed, never_captured, or unknown — with
per-state counts and a coverage meter. No field value is ever printed;
the report model has no value field at all, so a value handed in alongside a
record cannot reach the output.
Input: a JSON object with either fields ([{path, state}]) or raw
findings (MaskingPipeline finding records, adapted automatically).
$ nova redaction-xray xray.json --capsule-id run-2041
Redaction X-Ray: coverage 67% of sensitive surface capsule: run-2041
counts: clear=1 redacted=1 secret_scrubbed=1 never_captured=1 unknown=1
■ request.headers.authorization (secret_scrubbed)
▨ request.body.messages[0].content (redacted)
· response.body.usage.total_tokens (clear)
░ env.OPENAI_API_KEY (never_captured)
? response.body.choices[0].text (unknown)Coverage is deliberately conservative:
(redacted + secret_scrubbed) / (redacted + secret_scrubbed + unknown) —
a field with absent scan metadata (unknown) lowers coverage instead of
being asserted clear. Exit codes: 0 rendered, 2 bad input.
nova passport (ADR-0149)
Projects the identity, lineage, AIBOM, eval-card, package, and delegation references NovaFabric already produces for an agent into one portable passport document, verifiable offline:
- green — every component present and resolvable (carried as a ref);
- amber — the identity anchor exists but a component is absent or opaque (present but unattestable, e.g. an opaque lineage ancestor);
- red — the identity anchor is absent: no basis for a passport.
The passport never claims ancestry NovaFabric cannot attest — an opaque
ancestor is amber, never dressed up as green. Components carry a
ref/digest only, never the component body.
Input: {"agent_ref": ..., "present": {component: ref}, "opaque": [...]}.
$ nova passport issue refs.json
agent: agent:support-triage@1.4.0
status: (amber)
identity present sha256:2f4a1c
lineage opaque —
aibom present sha256:77aa0b
card present sha256:e01d9f
package present sha256:5b2c44
delegation present sha256:0d3e21
$ nova passport issue refs.json --json > agent-passport.json
$ nova passport verify agent-passport.json
...
verified — status (amber) matchesverify re-derives the verdict offline and confirms it matches the
document. Exit codes: issue — 0 rendered, 2 malformed input; verify —
0 match, 3 status mismatch, 2 malformed input.
Honest limits: this slice emits an unsigned projection
(signed: false). Sealing the passport through the shipped seal path,
loading component refs directly from a sealed capsule (--asset), and the
broader ADR-0149 interop facets (NF-171..178, NF-180) are future design.
See also
- User guide — trust layer —
nova verify,nova redact,nova export-evidence, the sources these projections consume - NovaSeal configuration — sealing, Merkle log, timestamps
- Assurance cases — binding sealed evidence into a machine-checkable argument
- Human-oversight evidence — what a human reviewer was shown and whether they corrected the agent, as sealed capsule evidence (experimental)
- Frontier-safety evidence — threshold evals, indicators
and AI-control decisions recorded in
facets.frontier_safety(experimental)