Result schema — reading a Kshana *.result.json
Every run writes a versioned, self-describing JSON (JavaScript Object Notation) artifact next to the scenario.
This page is the hand-written reader's guide to the interchange envelope and to the
clock and orbit result shapes: each field's unit, where it is computed in the
source, and how to read it. The canonical type is RunResult in
src/report.rs; the figures of merit are FoMScores in
src/fom.rs.
It is not the complete field reference. The engine dispatches over far more kinds than these two, and each emits its own pack. The complete reference — unit, provenance class and definition for every numeric field of every built-in kind — is generated, not hand-written: see Units and provenance, per field, for every kind below.
Interchange envelope (KIF, the Kshana Interchange Format) — recognising and version-checking an artifact#
Kshana artifacts can be wrapped in a neutral, self-describing interchange
envelope so a third-party tool can recognise the file and decide whether it
can read it before it commits to a payload schema. The canonical type is
Envelope in src/interchange.rs.
| Key | Type | Meaning |
|---|---|---|
format |
string | Always "kshana-interchange" (FORMAT_TAG) — how a foreign tool recognises a Kshana artifact. |
schema_version |
string | The MAJOR.MINOR schema version (SCHEMA_VERSION, the single source of truth for the whole crate). |
kind |
string | Artifact kind, e.g. "scenario", "run-result", "trade-study" — lets a consumer route without parsing the body. |
engine_version |
string | Crate version (Cargo.toml) that produced the payload. |
payload |
object | The wrapped artifact as canonical JSON (a Scenario, a RunResult, …). |
Compatibility contract. Versions are MAJOR.MINOR. New fields are added with
#[serde(default)] (the additive discipline), so a reader can parse any
artifact of the same major and an equal-or-older minor (Compatible). A
strictly newer minor (ForwardIncompatible) may carry fields this reader
cannot honour, and a different major (MajorIncompatible) is a structural
break — both are refused by Envelope::parse, which validates the format tag and
version on the boundary rather than silently mis-parsing foreign input.
Determinism. The envelope carries no timestamp — Kshana's reproducibility
promise (scenario + seed + engine_version reproduces a run) requires it.
Provenance lives in engine_version and the payload's own scenario_hash.
A typed read-back (Envelope::payload_as::<T>()) works for any payload that
implements Deserialize (e.g. Scenario). Result artifacts are serialize-only
by design and are read as the raw payload JSON value.
Top level (clock and orbit packs)#
| Key | Type / unit | Meaning | Source |
|---|---|---|---|
schema_version |
string | Result schema version (independent of engine version). | report.rs |
engine_version |
string | Crate version (Cargo.toml) that produced the file. |
report.rs |
scenario_hash |
hex string | SHA-256 (SHA: Secure Hash Algorithm) over the canonical scenario JSON — the run is reproducible from scenario + seed + engine_version. |
report.rs:hash_scenario |
seed |
u64 | RNG (random-number generator) seed for the quantum run (classical uses seed + 0x9e3779b97f4a7c15). |
run.rs |
threshold_ns |
nanoseconds | The timing spec: an error within ±threshold_ns is "in spec". |
scenario |
quantum |
object (ClockRun) |
The quantum sensor's run (see below). | report.rs |
classical |
object (ClockRun) |
The classical sensor's run, for comparison. | report.rs |
units |
object | Per-field unit/provenance map for this document, keyed by field path. See §Units and provenance below. | field_schema.rs:units_block |
figure_tiers |
object | The verification tier of each reported figure of merit, and always the document's last key. figures lists one entry per figure present: path (e.g. quantum.fom.timing_p95_ns), tier (VALIDATED = checked against an independent external oracle, MODELLED = first-principles, internally tested), requirement (the verification-matrix row the tier is read from) and applicable. applicable is false, with a reason, for security in every scenario with no attack configured: the bound stays in fom.security, but it does not answer a question the scenario asks. untiered lists any figure no matrix row owns (the hybrid and fusion position figures), instead of giving it a tier. Emitted by the clock (single run and Monte Carlo ensemble), orbit, hybrid and fusion kinds. Carries no numbers. |
fom_label.rs:figure_tiers |
geometry |
object, optional | Orbit-pack geometry summary: samples_total, samples_with_fix, sigma_uere_m (m, the modelled per-satellite range-error budget), best_pdop / median_pdop (dimensionless) and best_position_sigma_m / median_position_sigma_m (m, PDOP (position dilution of precision) × sigma_uere_m). Orbit pack only. |
orbit.rs |
eci_track |
array of [x, y, z] km, optional |
Propagated Earth-centred-inertial track of the user spacecraft, one entry per sampled time. Orbit pack only; omitted otherwise. Output-only — not hashed. | report.rs |
meta |
object (StudyMeta), optional |
Additive report metadata (study title, generation stamp, author, disclaimer). Omitted when absent, so a meta-less run is byte-identical to legacy output. Output-only — not hashed. | report.rs |
ClockRun (each of quantum / classical)#
| Key | Type / unit | Meaning |
|---|---|---|
spec.id |
string | Sensor identifier (e.g. optical-sr-lattice). |
spec.kind |
string | Model kind (e.g. clock). |
spec.provenance |
string | Citation/source for the sensor's parameters. |
spec.params |
object | The raw model parameters used. |
series |
array of {t, error_ns, gnss} |
Per-step time series: t seconds, error_ns the timing error in nanoseconds, gnss one of nominal/degraded/denied. |
fom |
object (FoMScores) |
The scored figures of merit (below). |
adev_curve |
array of {tau_s, adev, n_samples, noise, edf, ci_lo, ci_hi} |
Overlapping Allan deviation: tau_s the averaging time (s), adev the dimensionless fractional-frequency stability σ_y(τ), n_samples the overlap count behind that point, noise the power-law type identified from the MDEV (modified Allan deviation) slope (e.g. WhiteFm), edf the noise-type-specific effective degrees of freedom, and ci_lo/ci_hi the χ²-based 95% confidence band on adev at that τ. Computed in allan.rs:overlapping_adev_curve. |
filter_health |
object, optional | Kalman filter-consistency assessment (NIS/NEES (NIS: normalised innovation squared; NEES: normalised estimation error squared) against their χ² bands), computed in filter_health.rs; omitted for runs that do not assess it. |
FoMScores (the fom object)#
| Field | Unit | Meaning | Source | Caveat |
|---|---|---|---|---|
timing_rms_ns |
ns | RMS (root mean square) clock-phase (timing) error over the outage. | fom.rs:score |
a timing metric, not position |
timing_p95_ns |
ns | 95th-percentile timing error over the outage. | fom.rs:score |
|
holdover_s |
s | Worst-case (shortest) in-spec coast across outage segments. | fom.rs:worst_case_holdover |
grid-bounded — a lower bound at the time-step resolution |
resilience_slope_ns_per_s |
ns/s | Least-squares growth rate of |error| during the outage. | fom.rs:score |
|
availability |
fraction [0,1] | Fraction of the whole run with an in-spec solution. | fom.rs:score |
|
integrity |
fraction [0,1] or null | Filter self-consistency: fraction of outage samples whose true error stays inside the Kalman k-σ bound. | run.rs |
NOT HPL/VPL/RAIM (HPL: horizontal protection level; VPL: vertical protection level; RAIM: receiver autonomous integrity monitoring) integrity — see INTEGRITY.md |
security |
fraction [0,1] or null | Analytic spoof-detectability bound from clock stability. | run.rs + security.rs |
meaningful only with a configured attack, so the clock and orbit packs (which configure none) mark it applicable: false in figure_tiers and print it as n/a (no attack) in the summary; not a multi-satellite RAIM detector |
What "good" looks like#
- A quieter clock (lower
adev_curve) gives a longerholdover_sand a highersecuritydetectability bound — that is the quantum-vs-classical contrast. availabilitynear 1.0 means the system stayed in spec almost the whole run; during a long outage it drops for the noisier clock.integritynear 1.0 means the filter's own uncertainty bound was honest about its error — a self-consistency statement, not a safety-of-life guarantee.
Other scenario kinds#
Every other kind emits its own result shape. A few of the common ones:
- inertial: position-domain figures of merit in metres (
pos_rms_m,pos_p95_m) — single-axis (1-DOF (DOF: degree of freedom)), single-seed (seeCAPABILITY.md). - hybrid / fusion:
timing_spec_nsandposition_spec_mat the top level, and per side the timing and position figures of one sensor suite (position_p95_m,position_holdover_s, andpnt_holdover_s, the time until either spec breaches), with afigure_tiersblock like the clock pack's in which the position figures are listed asuntiered. - spoof: per-step spoof offset vs the clock's detection bound, plus whether the
spoof reached the spec before detection (
src/spoof.rs).
For the precise field set of any kind, read its entry in
field-units-schema.json (below), the kind's section of
SCENARIOS.md, or the corresponding *Result struct in src/.
Other files a run writes#
Beside <scenario>.result.json the CLI (command-line interface) writes
<scenario>.chart.svg, a printable <scenario>.report.html and the same report as
<scenario>.report.json, whose reproducibility block records the engine version, the
SHA-256 of the scenario file, the seed, the platform and the exact command (see
REPORTS.md and REPRODUCIBILITY.md). Kinds that
publish a reproducibility table also write <scenario>.table.csv (CSV: comma-separated
values). --export writes exchange files (see INTEROP.md) and
--animate an animation (see ANIMATION.md).
Units and provenance, per field, for every kind#
This page is hand-written and covers the clock and orbit packs. The machine-readable counterpart covers every built-in kind:
- In the result itself. Each scenario's result document carries a top-level
unitsobject mapping an emitted field's path to{"unit", "provenance", "note"}. The path grammar (an array contributes one[]-suffixed segment shared by its rows;*is a single-segment wildcard for data-keyed objects) and the closed provenance vocabulary are defined insrc/field_schema.rs. - As one file.
field-units-schema.jsonis the harvested union: for every kind, every numeric field it emits at a stated source, with its unit, provenance class, evidence tier and definition — and, named rather than omitted, every field that does not have one yet. Itscoverageblock carries the counts.
Both are enforced by tests/field_units_global.rs, which runs every registered kind and
fails if an emitted numeric field lacks a unit and a provenance class. Kinds not yet
covered are named individually in that file's UNCOVERED_KINDS with the reason each is
open, and the count is pinned so the list can only shrink.
Regenerate the JSON after any change to a report's shape or to a units block:
cargo test --test field_units_global zzz_emit_field_units_schema -- --ignored