Kshana — Architecture
Kshana est un seul moteur organisé en couches au-dessus d'un noyau partagé : les paquets de capteurs (horloge, inertiel, transfert de temps, hybride) ; une couche d'astrodynamique / numérique (SGP4/SDP4 analytique (SGP4 : Simplified General Perturbations 4 ; SDP4 : Simplified Deep-space Perturbations 4), un propagateur numérique de Cowell avec son modèle de forces à sept perturbations, la conception de manœuvres, la détermination d'orbite à partir de distances de stations au sol, et l'ajustement du modèle de forces aux éphémérides précises d'agences) ; une couche de temps et de référentiels (échelles de temps IERS (International Earth Rotation and Reference Systems Service), précession–nutation IAU (Union astronomique internationale) 2006/2000A, et réduction CIO (Celestial Intermediate Origin) GCRS↔ITRS (GCRS : Geocentric Celestial Reference System ; ITRS : International Terrestrial Reference System)) ; une couche de fusion (les estimateurs GNSS/INS (GNSS : système mondial de navigation par satellite ; INS : système de navigation inertielle)) ; et les couches d'intégrité, de résilience, de PNT alternatif (PNT : positionnement, navigation et synchronisation temporelle) et lunaire (RAIM/ARAIM/SBAS (RAIM : surveillance autonome de l'intégrité par le récepteur ; ARAIM : surveillance autonome avancée de l'intégrité par le récepteur ; SBAS : système d'augmentation satellitaire), brouillage et détection de leurrage multicouche, recalage sur cartes de gravité/terrain/magnétisme, et PNT cislunaire) ; ainsi que les couches plus récentes LEO-PNT (PNT en orbite terrestre basse), spectre, système solaire et conception de constellations, synchronisation de réseau et campagne, avec les rédacteurs de rapport, d'animation et d'interopérabilité que toute exécution peut utiliser (§1d). Dans toutes ces couches, le moteur ne sait rien du « quantique » face au « classique » : il pilote des modèles d'erreur de capteurs à travers un scénario de coupure GNSS, exécute un estimateur et note le résultat. Un dispositif quantique et un dispositif classique sont donc comparés sur le même scénario, ne différant que par leurs paramètres d'erreur (publiés, cités) et leurs graines de bruit indépendantes.
Ce document rassemble les diagrammes structurels et comportementaux. Le §1 est le noyau des paquets de capteurs ; le §1a cartographie les couches d'astrodynamique, de fusion et de PNT alternatif ajoutées depuis ; les §1b–§1d les couches d'étude, de domaine et de sortie. Pour l'usage, voir le README ; pour ce qui est et n'est pas validé, voir VALIDATION ; pour le tableau de maturité par capacité, voir CAPABILITY.
Abréviations utilisées dans les diagrammes et non développées là où elles apparaissent : ADEV, déviation d'Allan ; BW, largeur de bande ; CAI, interféromètre à atomes froids ; CZML, le Cesium Language ; Δ-DOR, mesure de distance différentielle unidirectionnelle delta ; DO-229E, la norme de récepteur SBAS de la RTCA (Radio Technical Commission for Aeronautics) ; EKF, filtre de Kalman étendu ; FM, modulation de fréquence ; GSE, équipement de soutien au sol ; HPL/VPL, niveau de protection horizontal/vertical ; IMU, centrale inertielle ; IOAG, Interagency Operations Advisory Group ; IQ, échantillons en phase/quadrature ; ISL, liaison inter-satellites ; ITU-T, le secteur de la normalisation des télécommunications de l'Union internationale des télécommunications ; J2–J6, les harmoniques zonales du champ de gravité terrestre de degré 2 à 6 ; KIF, le Kshana Interchange Format ; KML, Keyhole Markup Language ; KPI, indicateur clé de performance ; L1/L5, bandes porteuses GNSS (le L2 des orbites en halo du CR3BP est en revanche le point de Lagrange Terre–Lune 2) ; LEO, orbite terrestre basse ; LOO, leave-one-out (en omettant un à la fois) ; LTC, temps universel coordonné lunaire ; MEO, orbite terrestre moyenne ; MTIE, erreur maximale d'intervalle de temps ; N2, le nombre de diversité de Hill d'ordre 2 ; NED, nord-est-bas ; NTN, réseau non terrestre ; OD, détermination d'orbite ; OPS-SAT, le satellite-laboratoire logiciel en orbite de l'Agence spatiale européenne ; PPP, positionnement précis par point ; PSD, densité spectrale de puissance ; PVT, position, vitesse et temps ; RINEX, Receiver Independent Exchange Format ; RK4 / RK5(4), intégrateurs de Runge–Kutta d'ordre quatre et d'ordre cinq(quatre) emboîté ; SH, harmonique sphérique ; SigMF, Signal Metadata Format ; SIR, rééchantillonnage par importance ; SRIF, filtre d'information en racine carrée ; SRP, pression de radiation solaire ; SSC, coefficient de séparation spectrale ; STK, Systems Tool Kit ; SVG, Scalable Vector Graphics ; TDEV, déviation de temps ; TDM, le Tracking Data Message du CCSDS (Consultative Committee for Space Data Systems) ; TLE, jeu d'éléments à deux lignes ; TPL, Timing Protection Level ; UBX, le protocole binaire de u-blox ; UHF, ultra haute fréquence ; VLBI, interférométrie à très longue base ; VRW / ARW, marche aléatoire de vitesse / d'angle ; WASM, WebAssembly.
1. Structure des modules#
flowchart TD
main["main.rs<br/>CLI (thin wrapper)"]
py["python.rs<br/>PyO3 (feature)"]
wasm["wasm.rs<br/>wasm-bindgen (feature)"]
api["api.rs<br/>run_toml: parse · dispatch by kind · json+svg+summary"]
subgraph shared["Shared core"]
types["types.rs<br/>Seconds · TimeGrid · ModelSpec"]
scenario["scenario.rs<br/>GnssState · GnssTimeline · ClockCfg · Scenario"]
allan["allan.rs<br/>overlapping Allan deviation"]
end
subgraph pack1["Pack 1 · Clock holdover"]
models["models.rs<br/>ErrorModel · ClockModel (incl. flicker FM)"]
estimator["estimator.rs<br/>HoldoverEstimator"]
kalman["kalman.rs<br/>KalmanClock → Integrity bound"]
security["security.rs<br/>clock-aided spoof detection → Security"]
fom["fom.rs<br/>Sample · FoMScores · score · worst_case_holdover"]
report["report.rs<br/>RunResult · hash · to_svg"]
run["run.rs<br/>run / run_clock / run_orbit_clock"]
end
inertial["inertial/<br/>Pack 2 · AccelModel (accel + gyro + bias instability/RW) · run_inertial · quantum_imu CAI + QuantumNavBudget dead-reckoning-over-holdover"]
timetransfer["timetransfer.rs<br/>Pack 3 · TimeTransferLink · run_timetransfer"]
hybrid["hybrid.rs<br/>Pack 4 · run_suite · score_hybrid · run_hybrid (+ integrity/security)"]
fusion["fusion/<br/>joint Kalman PNT estimator · run_fusion"]
orbit["orbit.rs<br/>Propagator (Kepler | SGP4) · Walker / TLE / multi-constellation · visibility · DOP"]
tle["tle.rs<br/>two-line element parsing (line 2 → Kepler, full TLE → SGP4)"]
sgp4mod["sgp4.rs<br/>SGP4 / SDP4 propagator (deep-space + resonance)"]
ensemble["ensemble.rs<br/>Monte Carlo confidence bands"]
sweep["sweep.rs<br/>trade-study parameter sweeps"]
spoof["spoof.rs<br/>active spoofing-attack demonstrator"]
jamming["jamming.rs<br/>link-budget anti-jam C/N₀ (J/S · processing gain · Q)"]
navsignal["navsignal.rs<br/>signal level: BPSK-R / BOC PSD · SSC κ · Gabor BW · DLL jitter · multipath · ranging-code (m-seq/Gold) design-trade"]
holdover["holdover.rs<br/>GNSS-denied clock holdover: van-Loan coast → holdover-to-threshold · quantum-clock classes"]
verification["verification.rs<br/>machine-checked requirement→module→test→oracle→status matrix"]
main --> api
py --> api
wasm --> api
api --> run
api --> inertial
api --> timetransfer
api --> hybrid
api --> ensemble
api --> sweep
api --> fusion
api --> spoof
spoof --> security
api --> jamming
api --> holdover
api --> verification
navsignal -. derives anti-jam Q (κ → Q=1/(R_c·κ)) .-> jamming
jamming -. uses PSD / SSC .-> navsignal
holdover -. coast-variance vs van-Loan recursion .-> kalman
ensemble --> run
sweep --> run
orbit --> tle
tle --> sgp4mod
orbit -. SGP4 propagator .-> sgp4mod
fusion -. composes .-> models
fusion -. composes .-> inertial
fusion --> kalman
run --> models
run --> estimator
run --> kalman
run --> security
run --> fom
run --> report
run --> orbit
models --> types
scenario --> types
inertial --> scenario
timetransfer --> types
orbit --> scenario
hybrid -. composes .-> models
hybrid -. composes .-> estimator
hybrid -. composes .-> inertial
hybrid -. composes .-> timetransfer
pack1 --> shared
inertial --> allan
La CLI (interface en ligne de commande) et les deux liaisons passent par un seul point d'entrée api::run_toml, de sorte qu'elles ne divergent jamais. Les paquets réutilisent le noyau partagé (types, scenario, allan) ; le paquet 4 (hybrid) compose les modèles et estimateurs des paquets 1–3 au lieu de les réimplémenter ; orbit dérive une chronologie GNSS à partir de la géométrie, qui alimente ensuite l'exécution du paquet 1. Le module navsignal se situe au niveau du signal, entre le bilan de liaison et le domaine des mesures : il dérive le coefficient de séparation spectrale κ à partir des spectres de puissance réels du signal et du brouilleur, à partir duquel jamming calcule désormais son Q = 1/(R_c·κ) antibrouillage au lieu de prendre une constante représentative (recoupé en CI (intégration continue)) ; il porte aussi l'arbitrage de conception des codes de télémétrie (séquence m/Gold).
Trois autres modules forment l'ossature de résilience en l'absence de GNSS. holdover répond directement à la question opérationnelle — combien de temps cette horloge peut-elle dériver librement avant que son erreur de synchronisation temporelle ne dépasse le budget ? — en exposant la forme close de l'erreur de dérive de van Loan comme une inversion holdover-vers-seuil, recoupée avec la récurrence de covariance clock_state en plusieurs pas. Son jumeau inertiel vit dans inertial::quantum_imu (QuantumNavBudget), qui compose la dérive de bruit blanc de l'interféromètre à atomes froids avec l'erreur de biais et de facteur d'échelle en un budget de dérive de position sur le holdover (le terme de biais étant recoupé avec l'intégrateur indépendant AccelModel). verification produit toute la matrice exigence → module → test → référence indépendante → état du moteur, avec des invariants testés unitairement qui interdisent une étiquette validé sans référence indépendante externe — de sorte que l'affirmation d'assurance ne peut pas diverger du code.
1a. Couches d'astrodynamique, de fusion et de PNT alternatif#
Au-delà du noyau des paquets de capteurs, trois sous-systèmes partagent le même noyau partagé et alimentent orbit (ou en sont alimentés). La couche d'astrodynamique / numérique ajoute un propagateur de Cowell non analytique à côté du chemin analytique SGP4/SDP4 ; la couche de fusion porte les estimateurs GNSS/INS ; et la couche de PNT alternatif est le recalage sur cartes de gravité en l'absence de GPS (GPS : Global Positioning System). Ce sont des capacités de bibliothèque/scénario (voir CAPABILITY pour savoir lesquelles sont câblées à un kind de scénario et lesquelles sont accessibles comme API (interface de programmation d'application) Rust).
flowchart TD
subgraph astro["Astrodynamics & numerical"]
orbit2["orbit · sgp4 · tle · walker<br/>analytic SGP4/SDP4 · Walker design"]
prop["propagator<br/>Cowell driver (accel_at / accel_rv)"]
forces["forces<br/>two-body · J2–J6 · EGM2008 d/o-70 · 3rd-body · SRP · drag · Schwarzschild + Lense–Thirring · tides"]
integ["integrator<br/>RK4 step-doubling · Dormand–Prince RK5(4)"]
ephem["ephem<br/>low-precision Sun & Moon"]
man["maneuver<br/>impulsive/finite burns · Izzo Lambert · porkchop"]
od["orbit_determination<br/>Gauss–Newton batch (batch_ls) · sequential UKF"]
end
subgraph fusion["Fusion (GNSS/INS)"]
ekf["fusion/gnss_ins_ekf · closed_loop · pack<br/>15-state loosely-coupled EKF"]
tc["fusion/tightly_coupled (8-state)<br/>fusion/tightly_coupled17 (17-state, quantum-CAI)"]
ukf["fusion/ukf — sigma-point core"]
coup["fusion/coupled — clock+position cross-covariance"]
end
subgraph alt["Alt-PNT (GPS-denied)"]
grav["gravimeter<br/>cold-atom model + SH anomaly field + mascons"]
pf["particle_filter — SIR"]
mm["mapmatch — field-match likelihood"]
end
prop --> forces
prop --> integ
forces --> ephem
man --> integ
od --> forces
od --> integ
od --> ukf
tc --> ukf
tc -. coasts on .-> forces
grav --> pf
mm --> pf
grav --> mm
ekf -. drives .-> strap["inertial/ strapdown (quaternion · NED · IMU errors)"]
tc -. drives .-> strap
grav -. CAI floor .-> strap
Les termes de force du propagateur numérique sont désactivés par défaut, de sorte que les activer ne perturbe jamais les références publiées. L'UKF (filtre de Kalman sans parfum) à 17 états, étroitement couplé, traverse une coupure GNSS sur la marche aléatoire de vitesse dérivée de l'accéléromètre quantique à atomes froids ; la détermination d'orbite réutilise les mêmes forces/integrator pour propager un état candidat sur l'arc de poursuite.
Le même noyau partagé porte aussi quatre autres sous-systèmes non dessinés plus haut (leurs paquets apparaissent dans la répartition du §4) : une couche de temps et de référentiels (timescales, jd2, precession, nutation, cio, frames, eop) réduisant TEME↔GCRS↔ITRS (TEME : true equator, mean equinox) et alimentant le paquet ephemeris/trace au sol ; une couche d'intégrité (raim, sbas) pour RAIM/ARAIM/SBAS ; une couche de résilience (jamming, spoof, spoof_detect, spoof_monitors, detection) pour le brouillage et la détection de leurrage multicouche, avec une couche de signal de navigation (navsignal) au niveau du signal — densités spectrales de puissance BPSK-R (BPSK : modulation par déplacement de phase binaire) / sine-BOC (BOC : porteuse à décalage binaire), coefficient de séparation spectrale κ qui dérive désormais le Q antibrouillage de jamming, largeur de bande de télémétrie RMS (Gabor), gigue de poursuite de code DLL (boucle à verrouillage de délai) cohérente early–late, et enveloppe d'erreur de trajets multiples (analyse de performance du signal, pas conception de charge utile RF (RF : radiofréquence) / de matériel d'antenne — rôle d'un partenaire de charge utile) ; et une couche lunaire / cislunaire (lunar, lunar_frame, lunar_od, cr3bp) pour la dynamique CR3BP (problème restreint circulaire à trois corps) — y compris la matrice de transition d'état 6×6 et un correcteur différentiel à tir simple (cr3bp_jacobian, propagate_state_stm, differential_correct_halo) qui produit des orbites en halo/NRHO (NRHO : near-rectilinear halo orbit) véritablement périodiques, reproduisant la NRHO australe L2 9:2 publiée (l'orbite Gateway) avec une période ≈ 6,57 j / un périlune ≈ 3 250 km (publié ≈ 6,56 j / ≈ 3 370 km) ; la transformation sélénocentrique MCI/MCMF (MCI : inertiel centré sur la Lune ; MCMF : centré sur la Lune et fixe par rapport à elle) d'une orbite corrigée et la continuation de famille restent des suites à donner, et la NRHO est une solution CR3BP (circulaire, sans Soleil), non validée par rapport à une véritable éphéméride LANS/Gateway (LANS : Lunar Augmented Navigation Service) — et l'intégrité LunaNet. L'ajustement du modèle de forces aux éphémérides d'agences (precise_od, lunar_od, tides, gravity_sh) et l'ensemble complet des champs de PNT alternatif (igrf, altpnt/terrain, gravimeter) complètent la liste des modules ; voir CAPABILITY pour la maturité par module.
1b. Études de résilience, évaluation par apprentissage automatique et artefacts reproductibles#
Cinq autres groupes de modules forment la couche d'études ouvertes et reproductibles derrière les articles de recherche du projet. Contrairement aux kind de scénario, on y accède par des générateurs cargo run --example / --bin qui écrivent des artefacts déterministes à l'octet près (graines fixes, version du moteur et empreinte de configuration consignées). Tous sont MODELLED (étalonnage synthétique ou sur jeu de données public) et portent des étiquettes de validation honnêtes — aucun n'est une certification.
flowchart TD
gen["study generators<br/>examples/ · bin/ (cargo run) → byte-deterministic artifacts"]
subgraph timing["Timing integrity"]
tplm["tpl<br/>conditional Timing Protection Level: k-σ floor · van-Loan coast · CUSUM"]
end
subgraph resilm["Resilience scoring & decision-instability"]
arch["resilience/arch · score<br/>architecture model + RPCF-aligned scoring"]
stats["resilience/stats<br/>Dirichlet · Kendall-τ · flip-rate · bootstrap"]
div["resilience/diversity · timeline<br/>Hill-N2 common-mode collapse · KPIs"]
study["resilience/study · report · panel<br/>instability study + integrity-hashed assurance report"]
end
subgraph aiml["RF-impairment optimism-gap (Machine learning)"]
ie["impairment_eval<br/>labelled synthetic corpus · ROC/AUC harness"]
is["impairment_study<br/>13-detector panel · scaling laws · LOO predictor"]
ml["impairment_ml<br/>logistic-regression + one-hidden-layer MLP"]
es["eval_stats<br/>bootstrap CI · DeLong · Spearman · ridge"]
sdrm["sdr · realdata<br/>IQ/IF → E/P/L taps → SQM + ingest adapters"]
end
subgraph trade["Quantum-vs-classical"]
cross["crossover<br/>resilience crossover map under parameter uncertainty"]
qt["quantum_trade<br/>measured-ADEV holdover benefit vs classical"]
end
gen --> timing
gen --> resilm
gen --> aiml
gen --> trade
tplm -. cross-checked vs .-> recursion["holdover · clock_state · allan"]
is --> ie
is --> ml
is --> es
ie -. real recordings via .-> sdrm
cross -. composes .-> packs2["clock · inertial packs"]
tpl— le Timing Protection Level conditionnel : une borne limitée par le holdover sur l'erreur de temps non détectée sous leurrage, composant un plancher de moniteur à k sigma, la variance de dérive de van Loan sur la latence de détection, et un temps jusqu'à l'alarme CUSUM (somme cumulée) ; étalonné sur un vrai leurrage enregistré (JammerTest 2024). Il n'existe aucune borne inconditionnelle finie — le TPL est conditionnel à ce qu'un contrôle croisé indépendant détecte l'attaque.resilience/— un moteur de notation de la résilience PNT aligné sur des cadres (catégories RPCF (Resilient PNT Conformance Framework) du DHS (Department of Homeland Security)) plus une étude d'instabilité de décision : un simplexe de pondération de Dirichlet, l'instabilité de rang de Kendall-τ, le taux de basculement du vainqueur top-1, et l'effondrement de la diversité en mode commun (Hill-N2), avec un rapport d'assurance à empreinte d'intégrité et 35 tests de référence dérivés à la main. Voir RESILIENCE-CROSSWALK. Architectures synthétiques alignées sur RPCF v2.0 — une auto-évaluation, et non une certification.impairment_eval/impairment_study/impairment_ml/eval_stats— l'étude de l'écart d'optimisme des dégradations RF : un corpus synthétique étiqueté et un banc ROC/AUC (ROC : caractéristique de fonctionnement du récepteur ; AUC : aire sous la courbe) indépendant du détecteur, un panel de 13 détecteurs (énergie/AGC/SQM/parité (AGC : commande automatique de gain ; SQM : surveillance de la qualité du signal) plus des détecteurs à régression logistique et à MLP (MLP : perceptron multicouche) à une couche cachée, avec graine), des lois d'échelle dans la distribution et hors distribution avec une hypothèse nulle par permutation, et un prédicteur leave-one-out de la dégradation hors distribution. Les métriques d'évaluation sont validées bit pour bit par rapport à scikit-learn.sdr/realdata— un frontal de récepteur défini par logiciel (IQ/IF brut (IQ : en phase/quadrature ; IF : fréquence intermédiaire) → prises early/prompt/late du corrélateur → SQM) et des adaptateurs d'ingestion (RINEX, u-blox UBX, GnssLogger, JammerTest, Yunnan, SatGrid) qui permettent de faire tourner les mêmes détecteurs sur des enregistrements fournis localement ; aucun jeu de données n'est versionné dans le dépôt.crossover/quantum_trade— la carte de croisement de résilience quantique contre classique sous incertitude des paramètres, et l'arbitrage du bénéfice de holdover sur ADEV mesurée ; les noyaux numériques de l'arbitrage sont validés par rapport à scipy.
1c. Couches d'intégrité, de GNSS, d'espace lointain, lunaires et de mission#
Les domaines restants se branchent sur la même répartition api et réutilisent le noyau partagé, les référentiels et la géométrie. Tout ce qui figure ici est MODELLED, sauf si une ligne de la matrice verification cite une référence indépendante externe (noyau RAIM par rapport à SciPy, SBAS par rapport au fork RTKLIB (une bibliothèque libre de positionnement cinématique en temps réel), noyau DOP (dilution de précision) de gnss_lib_py, l'évaluation OPS-SAT, le noyau de niveau de protection ARAIM lunaire, la cohérence d'éphéméride lunaire entre fournisseurs) — le reste de la suite lunaire et le démonstrateur quantique sont modélisés, illustratifs, de source publique, et ne portent aucune affirmation de TRL (niveau de maturité technologique) / d'héritage / d'aval d'une agence.
flowchart TD
api["api.rs — run_toml / run_scenario · 92 kinds"]
subgraph gnss["Integrity & GNSS measurement"]
raim["raim — RAIM/ARAIM · HPL/VPL (kernel vs SciPy)"]
sbas["sbas — DO-229E PL · L1/L5 (vs RTKLIB fork)"]
meas["gnss_sim · ionex · pvt · glonass — pseudorange · iono maps · single-point PVT"]
imp["integrity_impact · frugal — miss→integrity · cost-per-coverage"]
end
subgraph deep["Deep-space & Mars"]
core2["body · ephem_provider · clock_state — multi-body · ephemeris seam · onboard clock"]
radio["radiometric · ccsds_tdm — light-time · Doppler/range · Δ-DOR · TDM"]
mars["deepspace_od · mars_frame · mars_atmos · mars_pnt · linkbudget · gse_sim — SRIF OD · Mars frame · relay-PNT · GSE sim"]
end
subgraph lunar["Lunar PNT suite (evidence tier per piece in the matrix · illustrative public-source)"]
cis["cr3bp · lunar · lunar_frame · lunar_od — CR3BP · halo/NRHO · cislunar ARAIM · MCI↔MCMF"]
suite["lunar_time · lunar_vlbi · lunar_combination · lunar_frame_realise — LTC time · VLBI · joint OD+clock · frame realisation"]
suite2["lunar_service · lunar_dpnt · lunar_interop — Moonlight service-volume · differential PNT · LunaNet/IOAG interop"]
end
subgraph quantum["Quantum-Enabled PNT demonstrator (MODELLED)"]
qd["quantum_devices · quantum_faults · quantum_nav_od — device error models · fault catalogue · GNSS-free OD"]
qt["qtrade · timetransfer_chain · representativeness — unified trade · time-transfer chain · gaps-to-flight ledger"]
end
subgraph mission["Mission analysis, environment & interop"]
ma["launch · reentry · eo_payload · attitude_budget · passes · linkbudget — first-order budgets"]
env["space_weather — Jacchia-71 density"]
iop["rinex · rinex_obs · sp3 · oem · omm · ccsds_tdm · space_packet · interchange · permalink — interop formats + KIF"]
ver["verification — requirement→module→test→oracle→status matrix"]
end
api --> gnss & deep & lunar & quantum & mission
raim -. reuses .-> meas
suite -. reuses validated DOP / SBAS kernels .-> gnss
qt -. rides validated kernels (scipy / sklearn) .-> quantum
1d. PNT LEO, spectre, système solaire, synchronisation de réseau, campagnes et sorties#
Les couches les plus récentes suivent le même schéma : chacune est un ou plusieurs kind de scénario répartis depuis api, bâtis sur le noyau partagé, et MODELLED sauf si une ligne de la matrice nomme une référence indépendante externe.
flowchart TD
api["api.rs — run_toml / run_scenario"]
subgraph leo["LEO PNT (low Earth orbit positioning, navigation and timing)"]
lsig["leo_signal — signal design: acquisition, code tracking, GNSS compatibility, band trade"]
lpass["leo_pass · leo_link (geometry · antenna · itu · iono · energy · spoof · presets) — pass and per-band link budget"]
lnav["leo_navmsg (elements · fit · codec · sisre · services · text) — broadcast ephemeris and clock message"]
lfus["leo_fusion (doppler · joint_pvt · ppp · ntn · timing · polar · presets) — fused MEO+LEO PVT · PPP · 5G NTN"]
lchain["leo_pnt_chain — one system end to end, each stage handing its output to the next"]
end
subgraph rf["Spectrum"]
spec["spectrum · navsignal — L-band (and UHF/S/C) PSD waterfall under a jammer timeline"]
sig["sigmf — SigMF recording read/write · Welch PSD of complex IQ"]
end
subgraph sky["Solar system & constellations"]
ss["solar_system · body · ephem · ephem_provider — bodies, constants, light time at one epoch"]
bp["body_pnt — positioning around any body with a local constellation"]
cd["constellation · walker — Walker delta/star, multi-shell, published GNSS slots, coverage/DOP maps"]
end
subgraph net["Network timing"]
tel["telecom_timing — time error, MTIE, TDEV vs ITU-T masks"]
slot["slot_timing — seconds until a free-running clock leaves a slot guard"]
end
subgraph comp["Composition & outputs"]
camp["campaign · study · suite — chained phases, sweeps, Monte Carlo, shared conditions"]
rep["advanced_report — report.html + report.json with a reproducibility record"]
anim["animation — SVG / HTML / frame animations"]
iop["interop (czml · kml · geojson · stk) · sigmf — exchange files for --export"]
end
api --> leo & rf & sky & net & comp
lchain -. composes .-> lsig & lpass & lnav & lfus
lsig -. SSC chain .-> spec
lfus -. GNSS presets .-> cd
bp -. reuses .-> ss
camp -. runs other kinds through .-> api
Le détail par type figure dans LEO-PNT, LEO-SIGNAL, LEO-PASS, LEO-NAVMSG, LEO-PNT-FUSION, SPECTRUM, CONSTELLATION-DESIGN, TELECOM-TIMING, SLOT-TIMING, CAMPAIGNS, REPORTS, ANIMATION et INTEROP.
1e. Couches de confiance du récepteur, de preuves, de télémétrie, de formation et de compliance (0,35)#
Ces couches lisent le journal propre d'un récepteur ou écrivent des fichiers pour d'autres outils ; aucune ne synthétise ni n'émet de signal radiofréquence. Elles se situent à côté de api plutôt que sous les types de scénario, hormis receiver-trust lui-même, qui est un type de scénario. Tout est MODELLED sauf si une ligne de la matrice nomme une référence indépendante externe, et les sorties maritimes sont à titre consultatif, pas un équipement de navigation homologué.
| Module | Ce qu'il fait | Documenté dans |
|---|---|---|
receiver_trust |
note un journal de récepteur époque par époque avec les moniteurs de confiance ; une plateforme navire ajoute les moniteurs maritimes et le score de 0 à 100 avec les motifs | RECEIVER-TRUST, MARITIME-TRUST |
receiver_trust::live |
la même notation sur un flux au fur et à mesure de son arrivée (stdin, un fichier en cours d'ajout, TCP ou UDP), la phrase $PKSHT et la passerelle facultative ; LiveEngine en est la totalité, sans entrée ni sortie |
MARITIME-TRUST |
evidence |
un paquet signé, à chaîne d'empreintes, d'une fenêtre de journal, de sa configuration et du résultat du moteur, vérifiable hors ligne ; jeton d'horodatage RFC 3161 facultatif | EVIDENCE-PACKS |
telemetry |
le flux de confiance sous forme de métriques Prometheus, d'événements syslog (CEF ou LEEF) et, en option, d'OpenTelemetry (fonctionnalité otlp, désactivée par défaut) |
TRUST-TELEMETRY |
nmea_synth |
NMEA 0183 synthétique pour un pont, avec des événements scénarisés de brouillage, de décrochage progressif (drag-off), de leurrage temporel et de rejeu, et un journal d'instructeur, pour la formation des équipages (texte seul) | NMEA-TRAINING |
interference_map |
cartes agrégées de navigation dégradée issues de rapports d'aéronefs et de navires sous licence ouverte, et exposition d'un itinéraire à ces dégradations ; fichiers locaux uniquement | INTERFERENCE-MAP |
osnma |
un vérificateur côté récepteur pour Galileo OSNMA : il lit les pages I/NAV (pages brutes, CSV vectoriel, flux UBX), contrôle la clé signée, la chaîne TESLA et les MAC, et indique pour chaque satellite si les données de navigation sont authentifiées ; osnma::cli::verify_report est le chemin unique que partagent la ligne de commande, Python et WebAssembly ; état d'authentification uniquement, pas un service d'intégrité |
OSNMA |
compliance |
associe les sorties de Kshana à des cadres publics de résilience et renseigne cette correspondance à partir de documents de résultats ; run_from_text et assess_texts prennent des textes, pas des chemins |
compliance/ |
interop::testbench |
le mouvement du véhicule et les événements d'un scénario sous forme de CSV de mouvement, de NMEA, de texte de points de passage et de fichiers d'événements pour un simulateur GNSS de laboratoire ; aucun signal n'est écrit | TEST-BENCH |
Les intégrations pour bateaux (le plugin Signal K, le mode passerelle d'OpenCPN et la version de référence) appellent l'outil en ligne de commande ; voir MARINE-INTEGRATIONS.
2. Chaîne de traitement du moteur (par exécution)#
Chaque exécution fait avancer un modèle de capteur unique sur la grille temporelle : il est asservi tant que le GNSS est nominal et laissé en marche libre (holdover / navigation à l'estime) pendant la coupure.
flowchart TD
A["for each time step t"] --> B{"i > 0 ?"}
B -- yes --> C["model.step(dt, rng)<br/>evolve noise state"]
B -- no --> D
C --> D{"GNSS state at t ?"}
D -- "Nominal" --> E["discipline to truth<br/>(estimator sync / dead-reckoning reset)<br/>error = 0"]
D -- "Denied/Degraded" --> F["estimator predicts;<br/>error = truth − prediction"]
E --> G["record Sample(t, error, gnss)"]
F --> G
G --> A
A -. after loop .-> H["score(series, spec)<br/>→ figures of merit"]
H --> I["assemble Result<br/>(specs · series · FoM · scenario hash)"]
Un scénario exécute cette chaîne deux fois, une fois pour le capteur quantique et une fois pour le capteur classique, avec des graines indépendantes (classical_seed = seed + 0x9e3779b97f4a7c15) afin que les deux réalisations du bruit ne soient pas corrélées.
3. L'interface du modèle d'erreur (le point d'extension)#
Chaque capteur met en œuvre la même idée : un objet à état dont step() fait avancer l'erreur stochastique interne et dont l'état cumulé est lu à chaque pas. Les horloges exposent la phase cumulée ; les accéléromètres exposent la position doublement intégrée ; les liaisons exposent la gigue par mesure.
classDiagram
class ErrorModel {
<<trait>>
+step(dt, rng)
+spec() ModelSpec
}
class ClockModel {
+y0, q_wf, q_rw, drift, flicker
+phase() s
+det_freq() +drift_rate()
}
class AccelModel {
+bias, q_va, gyro_bias, q_arw
+pos() m +theta() rad
+reset()
}
class TimeTransferLink {
+sigma_j
+sample(rng) s
}
ErrorModel <|.. ClockModel
ClockModel : white FM + random-walk FM + flicker FM + aging
AccelModel : accel bias + VRW + gyro bias + ARW (gravity-tilt)
TimeTransferLink : white timing jitter
ModelSpec { id, kind, provenance, params } est transmis dans le résultat afin que chaque valeur de la sortie soit traçable jusqu'à la source publiée nommée dans provenance.
En plus de l'estimateur analytique HoldoverEstimator, le paquet d'horloge exécute un filtre de Kalman à deux états (phase, fréquence) (KalmanClock) dont le bruit de processus correspond au modèle de vérité. En roue libre pendant une coupure, la variance de son erreur de phase croît exactement jusqu'à q_wf·T + q_rw·T³/3, la relation analytique du holdover, et sa borne à 1 σ en ligne sert à renseigner la mesure de mérite d'intégrité (fraction des échantillons de la coupure dont l'erreur reste dans la borne à k σ).
4. Répartition (CLI et liaisons)#
api::run_toml(src) est le point d'entrée unique : il examine le kind de premier niveau, désérialise le scénario correspondant, exécute le paquet et renvoie { json, svg, summary, csv } (csv n'est présent que pour les types qui publient un tableau de reproductibilité). La CLI les écrit dans des fichiers, ainsi que le rapport (report.html, report.json) et toute sortie --export / --animate ; les liaisons Python et WebAssembly les renvoient à l'hôte. Une seule répartition, aucune dérive.
flowchart TD
F["api::run_toml(src) · run_scenario(src)"] --> K{"ScenarioKind::classify<br/>typed · exhaustive · 92 kinds<br/>(absent kind → clock; unknown kind → InvalidInput)"}
K --> G1["Timing<br/>clock · timetransfer · quantum-time-transfer · telecom-timing · slot-timing"]
K --> G2["Inertial & fusion<br/>inertial · hybrid · hybrid-ukf · fusion · gnss-ins · quantum-gnss-free-nav · ins-trn-coast · hybrid-optical-rf"]
K --> G3["Orbit, geometry & positioning<br/>orbit · ephemeris · constellation-design · gnss-sim · pvt"]
K --> G4["Integrity<br/>integrity · lunar-integrity · araim-reference-check"]
K --> G5["Resilience & spectrum<br/>jamming · spoof · spoof-detect · quantum-anomaly-detect · tracking-loop · spectrum · conflict-resilience"]
K --> G6["Alt-PNT (GPS-denied)<br/>gravity-map · terrain-nav · terrain-slam · combined-altpnt"]
K --> G7["LEO PNT<br/>leo-signal · leo-pass · leo-navmsg · leo-pvt · leo-ppp · ntn-positioning · leo-pnt-chain"]
K --> G8["Lunar / cislunar suite (MODELLED)<br/>lunar-time-offset · lunar-time-budget · lunar-vlbi · lunar-vlbi-fim · lunar-joint-od-clock<br/>lunar-frame-realisation · lunar-frame-campaign · lunar-llr-datum · realtime-frame-eop<br/>moonlight-service-volume · lunar-differential-pnt · lunar-interop-export · lunar-beacon<br/>lunar-jamming · lunar-attack-surface · earth-gnss-lunar · cislunar-observability · cislunar-arc-recovery"]
K --> G9["Deep space & solar system<br/>mars-pnt · solar-system · body-pnt"]
K --> G10["Machine learning & trade<br/>impairment-eval · quantum-trade"]
K --> G11["Mission analysis & environment<br/>launch-window · reentry · eo-coverage · attitude-budget · aperture-duty-cycle<br/>passes · link-budget · space-packet · space-weather"]
K --> G12["Trade studies, campaigns & interop<br/>sweep · sweep-nd · campaign · oem-interop"]
G1 & G2 & G3 & G4 & G5 & G6 & G7 & G8 & G9 & G10 & G11 & G12 --> W["RunOutput { json, svg, summary, csv }<br/>+ SHA-256 scenario_hash"]
La répartition se fait sur une énumération typée ScenarioKind, appariée de façon exhaustive (voir la sous-section suivante) ; ajouter un paquet est donc une modification vérifiée à la compilation. Un kind absent retombe sur le paquet clock, de sorte que les scénarios existants à type unique se désérialisent sans changement (serde ignore le champ kind de chaque structure de scénario). Un kind non reconnu est refusé avec KshanaError::InvalidInput, qui nomme le type intégré le plus proche lorsque le nom ressemble à une faute de frappe.
Répartition typée et API structurée#
La répartition se fait sur une énumération typée ScenarioKind, et non sur une correspondance de chaîne brute : ScenarioKind::classify(src) résout le champ kind en une variante, et le répartiteur l'apparie de façon exhaustive ; ajouter un paquet est une modification vérifiée à la compilation, et non une faute de frappe dans une chaîne. Deux surfaces typées existent à côté de run_toml, qui renvoie une chaîne (conservée pour la CLI et les liaisons existantes) :
run_scenario(src) -> Result<RunOutput, KshanaError>: l'entrée typée, avec une taxonomie d'erreurs structurée (InvalidInput,NonConvergence,Unsupported,IoError). Chaque erreur porte unkind_tag()stable, afin qu'un appelant puisse se brancher sur la catégorie de l'échec au lieu d'analyser le message. Les liaisons l'exposent sous le nomerror_kind(toml).list_scenario_kinds() -> Vec<ScenarioMeta>(etlist_scenario_kinds_json(), exposé dans les liaisons sous le nomlist_kinds()) : introspection programmatique, avec pour chaque type son nom, sa description et ses champs obligatoires ou facultatifs, pour l'interface utilisateur et l'autocomplétion des notebooks.
Étendre Kshana avec un paquet externe#
Un paquet tiers met en œuvre deux petits traits stables au sens de semver issus de api :
use kshana::api::{ExternalPack, KshanaError, RunOutput, Scenario, ScenarioMeta};
struct MyPack {
threshold: f64, // the deserialized scenario fields
}
impl Scenario for MyPack {
fn run(&self) -> Result<RunOutput, KshanaError> {
// Run the model, then build the unified output envelope.
Ok(RunOutput {
json: format!("{{\"threshold\": {}}}", self.threshold),
svg: String::from("<svg xmlns=\"http://www.w3.org/2000/svg\"/>"),
summary: format!("my-pack | threshold {}", self.threshold),
csv: None,
})
}
}
impl ExternalPack for MyPack {
fn kind_name(&self) -> &'static str {
"my-pack"
}
fn meta(&self) -> ScenarioMeta {
ScenarioMeta {
name: "my-pack",
description: "An out-of-tree example pack.",
required_fields: &["threshold"],
optional_fields: &[],
}
}
}
ExternalPack possède aussi une méthode register_into(&mut PackRegistry) par défaut, sans effet sauf si un paquet choisit le point d'accès de répartition par registre dans src/registry.rs. Le paquet intégré jamming est branché via Scenario comme exemple détaillé ; les paquets hors arborescence suivent le même contrat sans dupliquer le cœur (à l'image du point d'extension ErrorModel du §3, qu'utilise la surcouche privée de résilience).
Kshana possède donc deux points d'extension stables : ajouter un capteur en implémentant ErrorModel (§3), ou ajouter un type de scénario entier en implémentant Scenario + ExternalPack, tous deux stables au sens de semver, aucun n'exigeant de dupliquer le cœur :
classDiagram
class ErrorModel {
<<trait — sensor seam>>
+step(dt, rng)
+spec() ModelSpec
}
class Scenario {
<<trait — pack seam>>
+run() RunOutput
}
class ExternalPack {
<<trait : Scenario>>
+kind_name() str
+meta() ScenarioMeta
}
ErrorModel <|.. ClockModel
ErrorModel <|.. AccelModel
ErrorModel <|.. TimeTransferLink
Scenario <|.. ExternalPack
Scenario <|.. JammingPack : worked example
ExternalPack <|.. ThirdPartyPack : out-of-tree, no core fork
5. La pièce maîtresse hybride#
Le paquet hybride exécute une suite (une horloge et un capteur inertiel) et exige que la synchronisation temporelle et la position restent dans la spécification ; pnt_holdover est la durée jusqu'à ce que l'une des deux soit dépassée. Facultativement, une liaison optique inter-satellite resynchronise l'horloge pendant la coupure : aide temporelle uniquement ; la position n'est pas resynchronisée, car un transfert de temps donne le temps, pas la position. C'est ce qui isole le capteur inertiel comme facteur limitant.
flowchart LR
subgraph suite["PNT suite (per technology)"]
clk["clock → timing error"]
acc["inertial sensor → position error"]
isl["optical ISL<br/>re-sync clock at interval"] -. aids .-> clk
end
clk --> J["both within spec ?"]
acc --> J
J --> P["pnt_holdover = first breach<br/>(timing OR position)"]
6. Disponibilité GNSS dérivée de la géométrie#
orbit.rs est une couche de géométrie déterministe et sans dépendance. Un Propagator est soit l'Orbit képlérien analytique (à deux corps, avec J2 séculaire en option), soit un propagateur Sgp4 complet construit à partir d'un jeu complet d'éléments à deux lignes ; un générateur Walker-delta produit des constellations synthétiques, et la visibilité en ligne directe = occultation par la Terre + masque d'élévation. Le nombre de satellites visibles correspond à un état GNSS (≥4 = nominal, 1–3 = dégradé, 0 = refusé), et build_timeline le transforme en la chronologie de disponibilité qui pilote l'exécution standard de holdover d'horloge. La disponibilité est donc dérivée de la géométrie plutôt que rédigée à la main, alors que l'exécution, l'estimateur et la notation restent inchangés.
Une constellation fournie sous forme de TLE complets est propagée avec le modèle SGP4/SDP4 de sgp4.rs (vérifié par rapport aux vecteurs AIAA (American Institute of Aeronautics and Astronautics) 2006-6753) ; les éléments à seule ligne 2 conservent le chemin analytique à deux corps. Les deux peuvent être mélangés dans un même bloc de constellation.
flowchart LR
U["user orbit"] --> V
C["Walker constellation"] --> V["visible_count(t)<br/>occultation + mask"]
V --> S["gnss_state: ≥4 / 1–3 / 0"]
S --> T["build_timeline → GnssTimeline"]
T --> R["run_orbit_clock → clock holdover"]
7. Liaisons#
Le cœur se compile sans changement vers le natif, vers une extension Python et vers WebAssembly. Les modules Python (python.rs, PyO3 abi3) et WebAssembly (wasm.rs, wasm-bindgen) sont des dépendances facultatives, activées par fonctionnalité (--features python / --features wasm) : la compilation par défaut et la suite de tests ne les compilent jamais, tandis que la porte d'audit des dépendances (cargo deny --all-features) inspecte bien leurs arbres de dépendances, car ces crates sont livrés dans la roue et dans le paquet npm. Les deux appellent api::run_toml, de sorte que toutes les surfaces renvoient des résultats identiques. Le module WebAssembly alimente le bac à sable du navigateur dans web/ et exporte treize fonctions : run, run_all (une exécution du moteur qui renvoie ensemble le résultat, le graphique, le résumé et le tableau), chart_svg, summary, table_csv, list_kinds, error_kind, version, le codec d'URL (URL : adresse web) partageable encode_permalink / decode_permalink, et les trois exporteurs export_sp3 / export_omm / export_oem qui alimentent le menu d'export du bac à sable. Deux autres portes d'entrée atteignent la même api : le serveur MCP (Model Context Protocol) (mcp/kshana-mcp, un crate rmcp exclu de l'espace de travail qui expose quinze outils : run_scenario, list_scenario_kinds, validate_scenario, list_example_scenarios, get_example_scenario, report_scenario, animate_scenario, list_export_formats, export_interop, import_route, export_sp3, export_omm, export_oem, export_table_csv, assess_receiver_log ; les deux outils d'exemples lisent les scénarios intégrés par la fonctionnalité bundled-scenarios de la bibliothèque, désactivée par défaut, que seul ce serveur active) et le plugin d'IDE (environnement de développement intégré) JetBrains (ide/jetbrains, un projet Kotlin qui appelle la CLI kshana par un shell au lieu de lier la bibliothèque).
flowchart LR
cli["CLI · main.rs<br/>native binary"] --> api
py["Python · python.rs (PyO3 abi3)<br/>RunOutput class + run · run_full · run_typed · scenario_kinds · list_kinds · validate_toml · error_kind · version"] --> api
wasm["WebAssembly · wasm.rs (wasm-bindgen)<br/>run · run_all · chart_svg · summary · table_csv · list_kinds · error_kind · version · encode/decode_permalink<br/>export_sp3 · export_omm · export_oem"] --> api
mcp["MCP server · mcp/kshana-mcp (rmcp)<br/>tools: run_scenario · list_scenario_kinds · validate_scenario<br/>list_example_scenarios · get_example_scenario<br/>report_scenario · animate_scenario<br/>list_export_formats · export_interop · import_route<br/>export_sp3 · export_omm · export_oem · export_table_csv"] --> api
ide["JetBrains plugin · ide/jetbrains (Kotlin)"] -- spawns process --> cli
api["api::run_toml / run_scenario / list_scenario_kinds"] --> out["identical { json, svg, summary } on every surface"]
Activation par fonctionnalité : Python et WASM sont --features python / --features wasm ; la table de scénarios intégrés est --features bundled-scenarios (le seul usage qu'en fait le serveur MCP) ; le serveur MCP et les contrôles croisés xval/* sont des crates EXCLUS de l'espace de travail ; le plugin d'IDE ne lie rien : il exécute la CLI.
8. Déterminisme et reproductibilité#
- Tout l'aléa passe par un unique
ChaCha8Rnginitialisé par graine à chaque exécution ; l'ordre des pas est fixe, de sorte que(scenario, seed, engine version) → identical bits. - Le résultat porte un
scenario_hashSHA-256 (SHA : Secure Hash Algorithm) ;scripts/check-reproducible.shexécute deux fois un scénario de référence et vérifie que les sorties sont identiques octet pour octet. - Le même moteur se compile vers le natif, vers une extension Python et vers
wasm32-unknown-unknownpour des exécutions dans le navigateur qui produisent les mêmes nombres.
9. Structure différée / future#
Les couches d'astrodynamique, de fusion et d'alt-PNT du §1a (le propagateur SGP4/SDP4 complet, le propagateur numérique de Cowell avec son modèle de forces à sept perturbations et ses deux intégrateurs adaptatifs, la conception de manœuvres et de trajectoires, la détermination d'orbite, les estimateurs GNSS/INS à 15, 8 et 17 états, le filtre couplé horloge + position et l'appariement de cartes de gravité) ont toutes été livrées, de même que la mesure de mérite de sécurité avec un démonstrateur de leurrage actif, la géométrie réelle TLE / multi-constellation, les bandes Monte-Carlo, les balayages d'études de compromis, le rapport HTML (HyperText Markup Language) imprimable avec son jumeau lisible par machine, et les workflows de publication, de roues et de pages.
Plusieurs capacités autrefois listées ici comme travaux futurs ont depuis été livrées et sont traitées plus haut ou dans VALIDATION : la nutation complète IAU 2000A et la réduction GCRS↔ITRS / ITRF (International Terrestrial Reference Frame) fondée sur le CIO, sans équinoxe (vérifiée bit à bit par rapport à SOFA/ERFA (SOFA : Standards of Fundamental Astronomy ; ERFA : Essential Routines for Fundamental Astronomy)), le géopotentiel EGM2008 (Earth Gravitational Model 2008) jusqu'au degré/ordre 70, le terme d'entraînement des référentiels de Lense–Thirring, les marées solides, océaniques et atmosphériques, une validation croisée d'éphéméride de niveau DE (DE440/ANISE (DE440 : Development Ephemeris 440 ; ANISE : Attitude, Navigation, Instrument, Spacecraft, Ephemeris, une boîte à outils de géométrie planétaire en Rust pur)), la validation croisée externe avec Orekit 12,2 du propagateur numérique de Cowell (0,08 m sur 24 h) et de la détermination d'orbite par lots et séquentielle, et le navigateur à 17 états à couplage serré présenté comme le type de scénario hybrid-ukf.
Les suites restantes sont suivies dans CHANGELOG [Unreleased] et la feuille de route par capacité dans CAPABILITY : une valeur de référence indépendante externe pour l'évaluateur EGM (Earth Gravitational Model) de haut degré exactement sur l'axe polaire et une comparaison proche du pôle propre à pyshtools (l'évaluateur jusqu'au degré 2190 et son chargeur .gfc à l'exécution sont livrés, MODELLED ; voir xval/high-degree-gravity/), un pilote météorologique spatial variable dans le temps pour le niveau de densité de traînée NRLMSISE-00 (NRLMSISE : Naval Research Laboratory Mass Spectrometer and Incoherent Scatter Radar Extended atmosphere model) en option (la densité de traînée par défaut est le modèle exponentiel statique, et la densité Jacchia-71 du type space-weather est caractérisée par rapport à NRLMSISE-00 mais n'est pas remplacée par elle), un contrôle externe de l'ombre à assombrissement centre-bord solaire en option, le couplage serré GNSS/INS sur la phase de la porteuse, et une carte de gravité EGM2008/EIGEN (EIGEN : European Improved Gravity model of the Earth by New techniques) réelle pour l'appariement alt-PNT.
Un dépôt de surcouche privé contient la profondeur de résilience soumise à restriction d'export ; il se branche via la même interface ErrorModel (et le contrat ExternalPack du §4) sans modifier le moteur public.