Problem
spar today has no mechanical check that an OSATE-produced AADL model parses in spar, or that a spar-produced model parses in OSATE / Ocarina. The current "interop story" is purely internal — we hand-write fixtures, parse them, run analyses, assert on golden output. As we grow we will silently drift into a spar-specific dialect of AADL, and we won't notice until an external user can't load their model.
We need an interop matrix. Treating it as a continuous CI artefact ("the matrix is the plug-fest") rather than a physical event is the right shape — DICOM Connectathon-style multi-vendor week is overkill for year one.
Reference tools
- OSATE (SEI/CMU, EPL, quarterly cadence) — the reference. Eclipse-based; the established CI invocation is a headless Tycho plugin, used in practice by RAMSES and ESA TASTE.
- Ocarina (OpenAADL/ocarina, ESA mirror) — the independent second opinion. Different codebase from OSATE, still releasing.
- *Not_ RAMSES — it reuses OSATE's front-end so it's not actually a second parser.
Exchange format
.aadl text catches lexer/parser/grammar drift. Cheap.
- AAXL2 XMI (AS5506 Annex C — the only standardised interchange format) catches semantic drift after instantiation. spar would grow
spar emit-aaxl2 and spar import-aaxl2.
There is no public AS5506 conformance test suite to lift. Corpus comes from osate/examples + osate/alisa-examples (the Aircraft Tier1/2/3 reference models) + a small set of hand-typed snippets from the AS5506 annexes.
Pattern to copy
CycloneDX ↔ SPDX interop: shared corpus, cross-tool CI matrix, with an explicit upfront allow-list of fields that don't survive the round-trip. For spar, the Spar_* property sets (Spar_TSN, Spar_Network, Spar_Identity, Spar_Migration, Spar_Power) are exactly that allow-list — tolerated as opaque on OSATE's side, asserted-equal on spar's side.
Proposed design
Tiered CI matrix
| Tier |
What |
When |
Cost |
| A |
osate/examples corpus parses cleanly in spar |
every PR |
seconds, pure Rust |
| B |
OSATE in Docker round-trips text → aaxl2 → spar and spar → aaxl2 → OSATE |
nightly |
minutes |
| C |
Add Ocarina as third opinion on Tier-A corpus |
quarterly |
one CI day |
Round-trip tests (cumulative)
- T1
parse-text(spar) — corpus parses without error
- T2
text → spar HIR → text — AST-equivalent (canonicalised: sorted modifiers, normalised whitespace, expanded inherited properties)
- T3
text → spar instance → aaxl2 → reparse(spar) — instance-equivalent
- T4 cross-tool: OSATE → aaxl2 → spar parses
- T5 cross-tool: spar → aaxl2 → OSATE re-instantiates
What "pass" means
Not byte-identical. Compare on the canonical instance: for each instance object, equal category, classifier, set of feature instances, set of connection instances, and property association bag with declared-vs-inherited collapsed. Implement once in spar as instance_eq() and emit a structural diff on failure.
Disagreement handling
When T4/T5 diverges between spar and OSATE:
- Minimise to ≤30-line fixture
- Check AS5506C clause + Annex C XMI schema — spec is arbiter
- File mirrored issues on
osate/osate2 and spar with the clause cite
- Record the agreed interpretation under
test-data/interop/adjudicated/<id>/ with a RULING.md
- If ambiguous, defer to OSATE and open an AS-2C clarification
Quick win (proposed as a first PR, ~half a day's work)
Smallest signal-bearing increment, no Docker / no OSATE-in-CI yet:
git submodule add https://github.com/osate/examples test-data/interop/osate-examples (EPL — record in LICENSES/)
- New
tests/osate_corpus.rs that walks every .aadl file and asserts spar parse returns Ok, with a SKIP.toml allow-list for known-broken upstream files (upstream's own README flags some)
- Print a single number per CI run: N parsed / M total, broken into core / EMV2 / BA buckets
That number, tracked over time, is the cheapest possible canary against silent dialect drift. No commitment to the heavier Tier-B work; proves the corpus + license + submodule mechanics first.
Out of scope
- Physical multi-vendor plug-fest event (year one — the CI matrix IS the plug-fest)
- AGREE / Resolute / Cheddar interop (annex-only or narrow scope; not parser-level)
- AADL V3 (AS-2C is still drafting; we track but don't implement)
References
Problem
spar today has no mechanical check that an OSATE-produced AADL model parses in spar, or that a spar-produced model parses in OSATE / Ocarina. The current "interop story" is purely internal — we hand-write fixtures, parse them, run analyses, assert on golden output. As we grow we will silently drift into a spar-specific dialect of AADL, and we won't notice until an external user can't load their model.
We need an interop matrix. Treating it as a continuous CI artefact ("the matrix is the plug-fest") rather than a physical event is the right shape — DICOM Connectathon-style multi-vendor week is overkill for year one.
Reference tools
Exchange format
.aadltext catches lexer/parser/grammar drift. Cheap.spar emit-aaxl2andspar import-aaxl2.There is no public AS5506 conformance test suite to lift. Corpus comes from
osate/examples+osate/alisa-examples(the Aircraft Tier1/2/3 reference models) + a small set of hand-typed snippets from the AS5506 annexes.Pattern to copy
CycloneDX ↔ SPDX interop: shared corpus, cross-tool CI matrix, with an explicit upfront allow-list of fields that don't survive the round-trip. For spar, the
Spar_*property sets (Spar_TSN,Spar_Network,Spar_Identity,Spar_Migration,Spar_Power) are exactly that allow-list — tolerated as opaque on OSATE's side, asserted-equal on spar's side.Proposed design
Tiered CI matrix
osate/examplescorpus parses cleanly in spartext → aaxl2 → sparandspar → aaxl2 → OSATERound-trip tests (cumulative)
parse-text(spar)— corpus parses without errortext → spar HIR → text— AST-equivalent (canonicalised: sorted modifiers, normalised whitespace, expanded inherited properties)text → spar instance → aaxl2 → reparse(spar)— instance-equivalentWhat "pass" means
Not byte-identical. Compare on the canonical instance: for each instance object, equal
category,classifier, set of feature instances, set of connection instances, and property association bag withdeclared-vs-inheritedcollapsed. Implement once in spar asinstance_eq()and emit a structural diff on failure.Disagreement handling
When T4/T5 diverges between spar and OSATE:
osate/osate2and spar with the clause citetest-data/interop/adjudicated/<id>/with aRULING.mdQuick win (proposed as a first PR, ~half a day's work)
Smallest signal-bearing increment, no Docker / no OSATE-in-CI yet:
git submodule add https://github.com/osate/examples test-data/interop/osate-examples(EPL — record inLICENSES/)tests/osate_corpus.rsthat walks every.aadlfile and assertsspar parsereturnsOk, with aSKIP.tomlallow-list for known-broken upstream files (upstream's own README flags some)That number, tracked over time, is the cheapest possible canary against silent dialect drift. No commitment to the heavier Tier-B work; proves the corpus + license + submodule mechanics first.
Out of scope
References
feedback_rigor_and_honesty,pulseengine-claude:pulseengine-feature-loop