How this toolkit's interchange JSON relates to the normative sources — the OMG pilot implementation (its Xtext grammars vendored in spec-refs/, the published 20250201 JSON schemas and XMI metamodel) — and what to expect when exchanging payloads with other SysML v2 implementations. Representation choices below were adjudicated against the pilot implementation as the normative reference; where other tools serialize differently, this toolkit follows the pilot.
Full-form output validates against the published 20250201 JSON schemas, with one documented departure: the value of a numeric literal that no JSON number denotes exactly is emitted as the literal's own text, where the schema declares LiteralInteger.value an integer or null and LiteralRational.value a number or null (see Numeric literal values below for why, and crates/sysmlv2-parser/tests/full_json.rs, where the schema check's expectations live, for the exception itself). Compact output contains owned properties and deliberately omits derived properties required by those schemas; it is checked separately against the XMI property inventory. The full form computes every derived property the derivation layer answers (API.md §7) and, by default, emits the inheritance/import closures as type-correct empty values and the inheritance-aware properties over the owned side — the API's "passthrough" level — so payload size stays proportional to the model. Under the closure policy (EmissionPolicy::closures, sysmlv2 convert --to full-json keeps the passthrough level) the four closure names — inheritedMembership, inheritedFeature, importedMembership, featuringType — are written and the inheritance-aware properties carry their specification values over the inherited and imported memberships, with or without the implied library heritage. Measured on the standard corpus (251 files, library loaded): the closure form over the written heritage is 1.25× the passthrough payload, and 2.31× with the implied heritage (every part listing the members of Parts::Part and its bases). Both forms are schema-valid and this toolkit reads either; a model whose heritage is deeper than the resolver's depth budget (24 levels) is refused under the closure policy rather than written with a truncated closure.
- Operator-expression operands are wrapped: each operand becomes the
FeatureValueof an ownedinparameter Feature under a private ParameterMembership (pilotOperandEList/TypeUtil.addOwnedParameterTo; the 20250201 metamodel removed the oldoperandcontainment). Explicit argument lists (f(a, b)) wrap the same way with default visibility (KerML.xtextArgumentMember). - Effective names:
Membership.memberNameand full-formnamederive from an unnamed feature's naming feature — the first redefined feature (attribute :>> uid = 4;is member-nameduid), a referenced feature, or a chain's last link (pilotElement::name → effectiveName()→Feature::namingFeature). Binary connector/succession ends take their computed positional names (source/target,earlierOccurrence/laterOccurrence), payloadspayload, return parametersresult, subjectssubj, objectivesobj. - Qualified names follow the
escapedNamederivation literally (KerML 8.3.2.1): only a name without the form of a basic name is quoted, so a reserved word used as a name stays bare —ControlFunctions::if,ViewDefinition::view— which is the spelling the normative library ids (below) hash and the pilot implementation's XMI carries.qualifiedNameis therefore a model property, not guaranteed to parse as a reference; textual emission quotes such names ('part'::'view'). isComposite/isReference: SysML usages are composite by default, except the inherently referential metaclasses (attribute, reference, enumeration, binding/succession-as-usage, event, exhibit, include, perform — pilot*Implconstructors),ref/directed/enddeclarations, usages with no featuring type, and non-subport ports.isReferencederives as the negation; therefkeyword round-trips exactly.- Membership-implied directions: subjects/actors/stakeholders are
in, return parametersout(pilotParameterMembershipAdapter). - Metadata usages are AnnotatingElements owned via OwningMembership even in type bodies (unfeatured, referential); a bare about-less
@M;member canonicalizes to the#Mprefix shape. - Comment/doc bodies are normalized like the pilot's
ElementUtil.processCommentBody, iterated to a fixpoint (the round-trip gate requires idempotence); requirementtextderives from documentation bodies. - Classification/cast/extent expressions: type references are owned parameter Features with a
FeatureTyping; casts use a ReturnParameterMembership; the implicit subject spells as a self-reference; themeta/@@left side as a MetadataAccessExpression. - Connector ends are ReferenceUsages (pilot
ConnectorEndrule), succession ends included; interface ends are PortUsages (pilot InterfaceEnd/DefaultInterfaceEnd). Flow ends follow the pilot'sFlowEndrule (prefix ReferenceSubsetting when spelled, plus an owned ReferenceUsage whose FlowRedefinition targets the last step). - Multiplicity bounds: the pilot's
MultiplicityBoundsrule owns the bound literals directly under the MultiplicityRange (no operator wrapping). - Numeric literal values carry a JSON number whenever one denotes the written literal exactly, and the literal's own text otherwise. A JSON number is a 64-bit integer or a double, so it holds neither an integer past 64 bits, nor more significant digits than a double's precision, nor an exponent outside its range; evaluation reads literals exactly, so emitting a rounded number there would make a model's JSON form and its textual form disagree.
LiteralInteger.valueis thus12but"170141183460469231731687303715884105727", andLiteralRational.valueis3.5or0.1but"1.234567890123456789012345678901"and"1e400". Readers accept either spelling for both metaclasses; a string value is parsed as the literal text it is. The published schema declares both properties numeric, so the text spelling is the one departure from it noted above. - Variations (and enum definitions) are implicitly abstract; exposes force
isImportAll; chain/index/collect/select expressions carry their fixedoperator; multi-step chain expressions flatten to a single FeatureChainExpression + OwnedFeatureChain member. - Every SysML port definition owns its implicit
~PConjugatedPortDefinition (OwningMembership, declaredName~P) with the PortConjugation pointing back at the original. - Satisfy
bytargets bind per the pilot'sSatisfactionFeatureValue; named invocation/constructor arguments own the pilot'sParameterRedefinitionof the callee's parameter; positional arguments take the callee'sinparameter names (in-document callees); binding connector ends take theLinks::SelfLinkpositional namesthisThing/sameThing. - Implied relationships are emitted when resolving against
--lib, following the pilot's anti-redundancy rule.
Top-level standard-library packages get uuid5(NameSpace_URL, prefix + escapedName); every named — including effectively named (KerML 8.2.3.5) — element under fully-named ancestry gets uuid5(topPackageUuid, qualifiedName); the owning membership of such an element gets …qualifiedName + "/owningMembership"; alias Memberships get the alias's qualified name; each document root Namespace is uuid5(top, ""). The norm's positional ids for unnamed elements are 1-based ownedRelationship indices that count an implementation's implied-relationship closure, so they are not portable across implementations; such elements are never name-referenceable and keep deterministic path-based ids here (cargo run --example libids prints the table).
The complete derived-property census — every isDerived name with its class (structural, inheritance-aware, closure) and whether the emitter derives it — is generated into spec-refs/derived-properties.md by tools/derived_census.py; the bullets below are the hand-maintained notes that the census does not express.
- Implied end-Redefinition elements for binary connector ends are not emitted (the positional names are derived, the relationships are not).
mayTimeVary/SysMLisVariableare set for variation/enum cases recognized structurally; complete derivation through library conformance remains limited.- The compact owner-side
conjugatedPortDefinitionproperty remains an explicit XMI-audit omission. The implicit~Pelement and full-form property are emitted; this is a property-coverage gap, not absence of the conjugated element.
Errors are reserved for what is provably wrong (syntax, illegal body context, a metadata feature typed by a resolved non-metaclass, provably violated multiplicity…). Unresolved references are warnings — the resolver covers 99.9%+ of the reference corpus, and conforming models must not fail on the long tail or on genuinely incomplete load paths. Pipelines that must refuse incomplete models (e.g. gating a Flexo commit) opt in with check --strict, which treats any finding as failure. Partial models are not second-class: in full-form output unresolved references become deterministic dangling @ids plus schema-valid TextualRepresentation recovery annotations by default, so converting the payload back to text restores the exact source references. Recovery is independent of the optional Flexo envelope.
The binary form (CBOR.md) is a byte-level re-encoding of the compact interchange element array — schema-equivalent by construction, since decoding reproduces the exact compact JSON Value before any consumer sees it. It adds no conformance surface of its own: everything above about representation rules, library element IDs, and partial models applies unchanged. The payload header carries the generated-table version; a decoder refuses a version it does not carry rather than silently mis-indexing, and the reserved id-elision flag bit is likewise refused by decoders that predate it.
User-element @ids are graph-derived (see IDS.md): chained UUIDv5 from the document root through ownership, with name-based segments for membership-owned named elements — chained past the membership's ordinal, so a named member and its membership derive from the owner and the member's name alone — and positional segments otherwise. Consequence for interchange consumers: every non-root id of a compact payload is recomputable from structure + names (ids::derive_ids), and re-emitting a model from text is id-stable under edits that do not move or rename the element's own ancestry — including inserting members before it. Payloads that carry explicit ids lift verbatim. Library element ids are unchanged (normative KerML 9.1 where named).
Sibling anonymous redefinitions such as ref :>> items = a; and ref :>> items = b; resolve their target against the inherited feature, including an inherited override. Their inferred local names cannot resolve each other's redefinedFeature. Each occurrence remains a distinct feature; compact JSON and navigation reference sites point at the same intended base target. Declared local targets remain available, and missing or genuinely ambiguous inherited targets still produce findings.