ADR-0016 — Belief Traceability Report v1¶
Status¶
Accepted (2026-06-07).
Context¶
ADR-0015 introduced the project's first deliberate producer of
belief: a VehicleState whose covariance_15x15 is not None,
distinct from the truth-bearing VehicleState published by
vehicle_state_from_ground_truth. Project Ghost now contains two
parallel realities for any captured run:
- Ground truth — the simulator's oracle.
- Belief — what the perturbation-based estimator (or, eventually, a real estimator) published.
The mission obligation from ADR-0009 §6 — the system must know when it knows — has, until now, been claimed but not auditable. An operator inspecting a captured run could read either reality but had no derived artifact answering the four questions this ADR commits to:
What was true. What was believed. How different were they. What covariance accompanied that belief.
This ADR commits to one specific, deliberately narrow mechanism:
A purely observational report that aligns truth and belief streams by
stamp_sim_ns, computes per-sample positional / orientation error and per-sample covariance diagnostics (trace and condition number), and emits an aggregated JSON document.
That is all the system does. The points listed under "Exclusions" below are not extension hooks — they are explicit non-goals.
The framing matches the one ADR-0014 took for behavior traceability: this is traceability, not explanation. It tells the operator what the two realities looked like; it does NOT interpret the discrepancy, score it, flag anomalies, or recommend corrective action.
Decision¶
Add project_ghost.analysis.belief_traceability module with:
BeliefTraceRecord— frozen dataclass for a single aligned (truth, belief) sample:timestamp_ns(int):stamp_sim_nsshared by the two inputs.truth_position_xyz,belief_position_xyz(tuple[float, float, float]): ENU position in meters.truth_orientation_xyzw,belief_orientation_xyzw(tuple[float, float, float, float]): quaternion in scipy order[x, y, z, w]. Internal computations use the codebase's Hamilton[w, x, y, z]convention; the conversion happens at the record boundary because scipy ordering is the external de-facto standard and the explicit suffix in the field name removes ambiguity for downstream consumers.position_error_norm_m(float): Euclidean norm ofbelief_position - truth_position.orientation_error_rad(float): angle between the two unit quaternions, computed as2 * arccos(|dot(q_truth, q_belief)|)in Hamilton space (the absolute value accounts for the double-cover of unit quaternions).covariance_trace(float | None): trace of thecovariance_15x15carried by the belief state, orNoneif belief has no covariance or the trace is non-finite.covariance_condition_number(float | None): spectral condition numberλ_max / λ_minof the covariance, orNoneif belief has no covariance or the value is non-finite (zero eigenvalue, ill-conditioned).covariance_available(bool):Trueiff the belief state carried a non-Nonecovariance_15x15.-
analysis_version(int): equalsBELIEF_TRACEABILITY_ANALYSIS_VERSION. -
BeliefTraceabilityReport— frozen dataclass: total_samples(int): length ofrecords.samples_with_covariance/samples_without_covariance(int): partition ofrecordsbycovariance_available.mean_position_error_m,max_position_error_m(float): aggregates overrecords. Empty input → both0.0(documented convention; the empty-set mean has no canonical value, and emitting0.0keeps the JSON schema flat).mean_orientation_error_rad,max_orientation_error_rad(float): same posture.records(tuple[BeliefTraceRecord, ...]): preserved in input order; the report does NOT re-sort.-
analysis_version(int): equalsBELIEF_TRACEABILITY_ANALYSIS_VERSION. -
compute_position_error(truth_pos, belief_pos)— pure, side-effect-free Euclidean norm. -
compute_orientation_error(truth_q_wxyz, belief_q_wxyz)— pure angle-between-rotations in radians. Hamilton[w, x, y, z]input. -
build_traceability_report(*, truth, belief)— single forward pass: truth,belief: each aSequence[VehicleState].- Requires equal length; raises
ValueErrorotherwise. - Requires per-index
stamp_sim_nsequality; raisesValueErrornaming the first mismatched index otherwise. -
Produces one
BeliefTraceRecordper aligned pair, in input order. -
encode_belief_report_to_bytes(report)— JSON serializer using the same encoding posture as ADR-0013 reports:sort_keys=True,indent=2,ensure_ascii=False, trailing newline, UTF-8. -
generate_belief_report(report, output_path)— writes the bytes to disk; pure write, no path invention. -
ghost analyze-belief --truth-mcap PATH --belief-mcap PATH [--output PATH]— CLI subcommand. ReadsVehicleStaterecords from each MCAP viatelemetry.MCAPReplayReader+ decoder catalog (ADR-0013 / T4), builds the report, writes JSON to--outputor stdout. JSON only. No text report. No natural language. No charts.
BELIEF_TRACEABILITY_ANALYSIS_VERSION is 1. Bumping it is a
deliberate breaking change.
Inputs¶
- A pair of
VehicleStatesequences (in-memory) or a pair of MCAP files whose records decode toVehicleState. - Truth and belief must be aligned: same length, same
stamp_sim_nsper index. The report does not interpolate, resample, align by nearest neighbor, or guess.
Outputs¶
- A
BeliefTraceabilityReportdataclass instance. - A JSON document of the report (via
encode_belief_report_to_bytesorgenerate_belief_report).
Limits¶
- The report covers ONLY paired samples. Truth-only or belief-only samples are NOT carried through; alignment must be done by the caller (typically by a runtime that publishes both streams synchronously).
- Covariance diagnostics are restricted to trace and condition number. The full matrix is NOT embedded in the report; consumers who need it should read the source MCAP.
- "Orientation error" is the rotation magnitude, not a signed axis or Euler-angle decomposition.
- Aggregates over empty inputs are emitted as
0.0by convention, not asnullor NaN.total_samples == 0is the unambiguous signal that the aggregates carry no information.
Determinism¶
For identical (truth, belief) input sequences within a fixed
(CPython, numpy):
- The produced
BeliefTraceabilityReportis field-by-field equal. - The encoded report bytes are byte-identical.
- For CLI: identical input MCAPs produce identical output JSON bytes
within the same
(CPython, numpy, mcap library)triple.
The build function:
- Reads no clock.
- Performs no I/O beyond what the reader does (for the CLI path).
- Holds no thread-local state.
- Uses no random.
Exclusions (explicit non-goals)¶
The following are NOT implemented and are NOT extension points sanctioned by this ADR. Any introduction of these would require a new ADR explaining why pure observation was insufficient and what the new artifact's honest framing would be:
- Planners / controllers / path optimization.
- Kalman / Bayesian / particle / smoothing filters.
- SLAM / localization / sensor fusion.
- Anomaly detection / trend detection / forecasting.
- Alerting / paging / notifications.
- Confidence scoring / risk scoring.
- Recommendations / autonomous decisions.
- Machine learning / neural networks.
- Dashboards / GUI / plotting / charts.
- Natural-language summaries.
- Cross-sample causality.
- Estimation-quality scoring (e.g., NEES / NIS, consistency metrics). Adding such metrics is a deliberate new artifact, not an extension of this one.
"Traceability is not estimation, not evaluation"¶
This module reconstructs paired observations. It does NOT:
- correct belief from truth;
- score how good the belief was;
- compute consistency metrics (NEES / NIS) that require statistical framing of "good";
- decide whether a covariance was justified;
- decide whether the system "knew when it did not know" — that judgment is the operator's, reading these records.
If position_error_norm_m is large while covariance_trace is
small, the report does NOT call that "overconfident". It records the
two numbers; the operator forms the hypothesis. The system explicitly
refuses to do so.
The report tells you what was true and what was believed. It does not tell you whether the belief was good.
Consequences¶
Positive.
- Project Ghost can answer the four questions about any captured run where both truth and belief streams are available.
- The narrow observational definition prevents scope creep into estimation-quality / inference territory.
- The artifact composes cleanly with ADR-0015: the noisy-GT estimator produces belief, the truth aggregator produces truth, and this report makes the gap explicit and auditable.
BELIEF_TRACEABILITY_ANALYSIS_VERSIONis a versioned contract; bumping it is a deliberate breaking change.
Negative.
- Operators may be tempted to read "small error + small covariance = good estimator" or "large error + small covariance = overconfident estimator". The ADR's "not evaluation" clause must be cited whenever this assumption is made; we will revisit if and when a separate estimation-quality artifact lands under its own ADR.
- The strict alignment requirement (same length, same stamps) pushes the burden of stream synchronization onto the producer. Justified because alignment policies (nearest neighbor, interpolation, time-warp) are themselves design decisions that would belong in a separate ADR.
Alternatives Considered¶
- Embed full covariance in records — Rejected. Bloats the report and duplicates data already on disk in the source MCAP. Trace + condition number are sufficient for the "what covariance" question in the ADR-0009 §6 obligation.
- Compute NEES / NIS consistency metrics — Rejected. Requires statistical framing of "good" that the observational stance refuses. Belongs in a separate ADR if and when the project decides it needs evaluation, not traceability.
- Alignment by nearest neighbor / interpolation — Rejected. Embeds a policy decision in the report builder. Strict alignment pushes the synchronization to the producer where it belongs.
- Per-channel reports (one per estimator) — Rejected for v1. The current architecture has one belief producer; if multiple land, a new ADR will decide whether to extend this report or emit one per channel.
- Live in-process reporting — Rejected. Violates "offline only" posture established in ADR-0013 / ADR-0014.
Backward compatibility¶
Zero impact on existing artifacts. New module, new public symbols,
new CLI subcommand. RunSummary (ADR-0013) is unchanged.
BehaviorTrace (ADR-0014) is unchanged.