Skip to content

fix(export): own a declaration head's relationships in the order the notation spells them - #1333

Merged
HuiJun merged 28 commits into
developfrom
fix/api-json-owned-relationship-order
Oct 22, 2026
Merged

HuiJun merged 28 commits into
developfrom
fix/api-json-owned-relationship-order

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What and why

The api-json differential (docs/project/api-json-differential.md, the AttributeUsage ownedRelationship gap row) found that for attribute a redefines x = expr; the pilot's ownedRelationship is [Redefinition, FeatureValue] while ours was [FeatureValue, Redefinition]. The cause is wider than that shape: the RDF writer materialized a head's relationship elements (FeatureTyping, Subsetting, Redefinition, ReferenceSubsetting, Subclassification) in a pass after the whole document was encoded, so they were appended to ownedRelationship after the multiplicity's membership, the FeatureValue and every body member, and in a fixed kind order (typing, specializes, subsets, redefines, references) rather than the order written. The API JSON writer serializes ownedRelationship in graph order, so it showed the same.

Now:

  • The writer records each declaration head's clauses as the notation spelled them (by source offset: the specialization clauses and the multiplicity part), materializes the relationship elements in that order, and seats them — with the multiplicity's OwningMembership — where the head stands in ownedRelationship: after the prefix metadata, ahead of the FeatureValue and the body members. ownedSpecialization, ownedTyping, ownedSubsetting, ownedRedefinition and ownedReferenceSubsetting follow the same order. Covers every head form the grammar orders this way: redefines x = v, :>> x : T = v, : T subsets y, : T = v, :>> x default = v, : T [n] :> y / :> y [n] : T, a definition's :> Q ahead of its members, #meta prefixes ahead of the head (internal/translate/export/rdf_out.go, rdf_normative.go, rdf_expr.go; rdf.Graph.ReorderObjects permutes a property's objects in place, tracking the statements through the Term-keyed index feat(export): owned absence and evaluator-derived properties in API JSON #1232 introduced).
  • The reader spells a head back in the order the graph owns its relationship elements, the multiplicity part where its membership stands, so a round trip keeps the spelling (snapshot :>> done :> fuel no longer comes back as subsets fuel redefines done; of p[*] ordered : Fuel no longer comes back as of p : Fuel[*] ordered). A graph stating the collapsed properties alone keeps the fixed clause order (rdf_in.go relationshipWords, spelledHead; end_forms.go payload).
  • Where a head relates one target under two clause kinds that materialize as the same class (feature f subsets x specializes x; owns two Subsettings of x; references and an end form's subsets both as ReferenceSubsetting), the writer states each element's keyword as sysx:headClause and the reader keeps the clauses apart and in order by it under either id form; a graph of another writer that states no such fact keeps the fixed order. A multiplicity no membership places (an older graph's) is written once, with the first typing clause. The extension is documented in docs/reference/rdf-mapping.md.
  • The collapsed head properties (sysml:type, sysml:redefinedFeature, …) keep their kind order, so that part of the Turtle is unchanged.

Specification basis

KerML 1.1 Beta 2 § 8.3.2.1.2 Element: ownedRelationship : Relationship [0..*] {subsets relationship, ordered}. KerML § 8.2.1 (Table 3) defines the notation-to-abstract-syntax mapping: p += Element adds the result of parsing the concrete syntax Element to the abstract syntax list property p, so an ordered list property collects its elements in parse order. The productions are, in order: KerML Feature = … FeatureDeclaration? ValuePart? TypeBody, FeatureSpecializationPart = FeatureSpecialization+ MultiplicityPart? FeatureSpecialization* | MultiplicityPart FeatureSpecialization* with every Typings/Subsettings/Redefinitions/References clause and MultiplicityPart stating ownedRelationship += …, then ValuePart = ownedRelationship += FeatureValue, then the body's members; SysML v2.1 Beta 2 § 8.2 Usage = UsageDeclaration? UsageCompletion, UsageDeclaration = Identification FeatureSpecializationPart?, UsageCompletion = ValuePart? UsageBody, and DefinitionDeclaration = Identification SubclassificationPart? ahead of DefinitionBody. The grammar is also what the pinned pilot (build/pilot-grammars/SysML.xtext) parses with.

Verdict: the order is required by the normative text, not pilot-only. The ordered property plus the mapping rule fix the order to the order the clauses are written in — typings, specializations and the multiplicity as spelled, then the FeatureValue, then the body. The pilot's order is that order; it is differential evidence, not the basis. No compliance row moves (the mapping is export, not validation); the gap row is re-measured below.

In-flight overlap

Base

develop. The branch was first based on PR #1328's feat/apijson-bridge-opensysml-libraries (the referee re-recorded with the OpenSysML Libraries loaded into the pilot bridge is the baseline this change is measured against) via merge commits of its heads, and retargeted to develop once #1328 merged; origin/develop is merged in as merge commits (0a5fa2d94, 5cd37f288, then 4196792df with #1232, #1388 and #1389) and the baseline re-recorded on that tree each time. With #1232 in, rdf.Graph.ReorderObjects works on its Term-keyed subjectIndex: it detaches a subject's index entry the overlay graph shares with its base before touching it, and finds the statements to permute through a per-predicate position cache built on first use and kept current by AddTriple (reset by RewriteTriples), so a head costs its own statements, not a scan of the graph.

api-json referee: before and after

Fresh XDG_CACHE_HOME runs, pilot 2026-08 (0.62.0).

  • First measurement (920 files, on fde7c11c6, before and after the change): 89957 elements matched; properties 618674 → 623436 present-equal (+4762), 50027 → 45265 present-different (−4762), every other bucket and count unchanged, matches lost 0 / gained 0; 101 rows moved, all present-different → present-equal: 67 ownedRelationship, 22 ownedSpecialization, 12 ownedSubsetting. The before run showed the family beyond redefines … = …: a usage's FeatureTyping after its body's FeatureMemberships (part camera : Camera { … }), a PartDefinition's Subclassification after its members, a PortUsage's Redefinition after its members, :>> x : T = v as [FeatureValue, FeatureTyping, Redefinition].
  • On develop (this head, origin/develop 4196792df merged, measured against develop's own committed baseline, re-recorded there by feat(export): owned absence and evaluator-derived properties in API JSON #1232): 924 files; 91721 elements matched (18392 ours-only, 37335 pilot-only, 1729 type-mismatch — unchanged); properties 644960 → 649821 present-equal (+4861), 31668 → 26807 present-different (−4861); ours-missing 1079787, pilot-missing 66545, absent-both 2406377, indeterminate 26031 unchanged; refused/unconvertible/colliding/pilot-failed unchanged. Every moved scorecard row: the same 101 rows (67 ownedRelationship, 22 ownedSpecialization, 12 ownedSubsetting), each one present-different → present-equal by the same count with its other four buckets unchanged; no row moved the other way and no other row moved. Largest: AttributeUsage.ownedRelationship 2332 → 1197, PartUsage.ownedRelationship 1554 → 1004, Feature.ownedRelationship 1802 → 1401, ReferenceUsage.ownedRelationship 852 → 96, PartDefinition.ownedRelationship 215 → 20, RenderingUsage.ownedRelationship 33 → 30. Per root (+present-equal = −present-different): training +325, pilot-examples +1533, pilot-validation +513, kerml-examples +530, spec-examples +248, testdata +58, examples +1329, probes +2, manual-examples +323; file counts and matched elements unchanged in every root. The 99 sites over the first measurement are heads feat(export): own the referent Membership of a FeatureReferenceExpression where the notation places it #1331/feat(identity): fix ids for OpenSysML's own library elements so references link by id #1332 and the develop merges (feat(export): owned absence and evaluator-derived properties in API JSON #1232's derived properties included) brought into the match.
  • Between this branch's previous baseline (taken on develop 5cd37f288) and develop's current one, 3335 further rows move (feat(export): owned absence and evaluator-derived properties in API JSON #1232's derived properties — qualifiedName, owner, memberElement, isLibraryElement, … now stated — with their new matches); those are develop's own (feat(export): owned absence and evaluator-derived properties in API JSON #1232, fix(runtime): send via a state definition's own port through what binds or connects it #1388, fix(runtime): read a step's restated parameter as the redefinition it is #1389, adjudicated in their bodies), already in develop's committed baseline and unchanged by this change. The writer digest moves with the writer.
  • What remains in AttributeUsage.ownedRelationship (1197) is a different shape, checked on the pilot's export of attribute cargoMass : MassValue; (training 33): the pilot owns, after the FeatureTyping, an OwningMembership of a Multiplicity (not implied) on a usage that writes no multiplicity part; ours states none. Recorded in the gap row.
  • Baseline re-recorded with -update on the merged tree, a fresh-cache -check reproduces it, make docs-counts green; gap row 42, the headline figures and the per-root, present-different and pilot-missing tables refreshed from it.

How it was verified

  • TestCorpusRoundTrip with OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 OPENSYSML_REQUIRE_SPEC_EXAMPLES=1: 876 files, 830 stable, 7 whitespace-only, 0 graph-diff, 1 unwritable, 0 unparseable, 38 refused — no movement against the committed expectation. TestCorpusAPIJSONRoundTrip likewise passes with no movement. Both re-run on the merged develop tree.
  • go test ./internal/translate/... ./tests/export/ green on the merged tree. Export goldens re-recorded (36 files): every moved line is an ownedRelationship/ownedSpecialization/ownedTyping/ownedSubsetting/ownedRedefinition order, a relationship-element block moving with it, or a .golden.sysml spelling that now keeps the written clause order. New fixture tests/export/testdata/convert/declaration_head_order.sysml pins every head form above; rdf.Graph.ReorderObjects has a unit test; TestSameTargetSpecializesAndSubsetsKeepTheirSpelledOrder (both orders, IDQualifiedName and IDUUID) and TestUnplacedMultiplicityIsWrittenOnceAcrossSplitTypings pin the two review findings.
  • Behaviour changes pinned by updated expectations: TestAnonymousKeywordsComeBackFromTheTypedFactsAlone, TestLinkedReferencesCarryTheRoundTripWithoutSourceText, TestSelfNamedRedefinitionTargetSurvivesTheGraphAlone, TestPayloadMultiplicityPartBeforeTheTyping (a head comes back in its written clause order instead of the fixed one), TestExpressionBodyParameterIsDeclaredOutsideTheBody (: Gauge [1..upper] owns the FeatureTyping before the multiplicity's membership), TestToolkitAPIGoldenNotation unchanged (the toolkit's JSON states no derived target, so the reader resolves a relationship's end by its specific property). The ontology gate's known-violation list gains the pre-existing isDefault-on-the-usage collapse the new fixture's default = v exercises.
  • Full gate on the merged tree: go build ./..., go vet ./..., gofmt -l . (empty), go test -C tools ./... (the provenance tests included), make lint, make docs-counts, python3 scripts/changelog.py check green; go test ./... green (heavy packages with -timeout 45m, the Makefile's).

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/3e3f2a6554334f64b826404b21c268fa
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/3e3f2a6554334f64b826404b21c268fa?variant=devin
Requested by: @HuiJun

devin-ai-integration Bot and others added 4 commits October 21, 2026 00:36
…notation spells them

ownedRelationship is ordered (KerML 8.3.2.1.2) and the notation adds an
element's owned relationships as it parses each clause (KerML 8.2.1, SysML
8.2), so a usage's typing, specializations and multiplicity are owned in
the order written, ahead of its FeatureValue and its body members; the
writer seated the materialized relationships last. The reader spells a
head back in that order.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…tate, and pin the order in goldens

The reader writes a declaration head's clauses in the order the graph
owns their relationship elements, the multiplicity part where its
membership stands, so a round trip keeps the spelling; a graph stating
the collapsed properties alone keeps the fixed clause order. Goldens
re-recorded for the owned-relationship order, with a fixture for each
head form; rdf.Graph.ReorderObjects permutes a property's objects in
place.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…e ontology gate

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 3 commits October 21, 2026 01:08
…d relationships in notation order

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…braries' into fix/api-json-owned-relationship-order
… the merged tree

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…ace an unplaced multiplicity once

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

… the reader keeps under any id form

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 7 commits October 21, 2026 05:49
Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/api-json-differential-baseline.json
#	docs/project/api-json-differential.md
Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/api-json-differential.md
#	tests/export/testdata/convert/constraints.golden.ttl.sha256
#	tests/export/testdata/convert/end_binding_bodies.golden.ttl.sha256
#	tests/export/testdata/convert/irregular_layout.golden.ttl.sha256
#	tests/export/testdata/convert/result_expressions.golden.ttl.sha256
Co-Authored-By: jason.han <hanhuijun@gmail.com>
… with develop and the bridge follow-ups

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration Bot and others added 2 commits October 22, 2026 00:14
…braries' into fix/api-json-owned-relationship-order

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/api-json-differential-baseline.json
… with the bridge's develop merge

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Base automatically changed from feat/apijson-bridge-opensysml-libraries to develop October 22, 2026 03:23
…-relationship-order

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/api-json-differential-baseline.json
#	docs/project/api-json-differential.md
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 7 commits October 22, 2026 04:18
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…he re-recorded baseline

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…orded baseline

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…-relationship-order

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/api-json-differential-baseline.json
#	docs/project/api-json-differential.md
#	tests/export/testdata/convert/end_binding_bodies.golden.ttl.sha256
…elop

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…-relationship-order

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/api-json-differential-baseline.json
#	docs/project/api-json-differential.md
#	internal/translate/rdf/term.go
…elop with the derived properties

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

@HuiJun
HuiJun merged commit a8597db into develop Oct 22, 2026
28 checks passed
@HuiJun
HuiJun deleted the fix/api-json-owned-relationship-order branch October 22, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant