Skip to content

[textual export] MetadataUsage feature values are lost during export #2509

Description

@kkkk1258999

Summary
When a MetadataUsage carries feature values, the textual export writes only the value-less prefix form #Definition. Every feature value carried by the usage is silently lost.
The typing survives the round trip; the data does not. There is no warning and no validation error — the export simply produces a smaller model than the one it was given.

Version
Reproduced on SysON 2026.7.3 (Maven artifacts, syson-application)
The code path is unchanged on main (line numbers below are from main)
Steps to reproduce
In any project, insert this text (insertTextualSysMLv2, or type it in the textual editor):
metadata def RoundTripEvidence {
attribute executionId : String;
attribute toolId : String;
}
part roundTripTarget {
@RoundTripEvidence {
executionId = "EXEC-ROUNDTRIP";
toolId = "probe-tool";
}
}
Confirm the import worked. The model JSON contains the MetadataDefinition and both LiteralString values.

Export the document as text (GET /api/editingcontexts/{editingContextId}/documents/{documentId} with Accept: text/html, which is what SysMLv2DocumentExporter handles).

Actual
metadata def RoundTripEvidence {
attribute executionId : String;
attribute toolId : String;
}
#RoundTripEvidence part roundTripTarget;
Both executionId and toolId are gone.

Expected
The exported text still carries the values, so that re-importing it yields the same model — for example:

part roundTripTarget {
@RoundTripEvidence {
executionId = "EXEC-ROUNDTRIP";
toolId = "probe-tool";
}
}
Where
SysMLElementSerializer.java on main:

// 2847
private void appendExtensionKeyword(Appender builder, Type type) {
for (var rel : type.getOwnedRelationship()) {
if (rel instanceof OwningMembership owningMember) {
owningMember.getOwnedRelatedElement().stream()
.filter(MetadataUsage.class::isInstance)
.map(MetadataUsage.class::cast)
.forEach(metadataUsage -> this.appendPrefixMetadataMember(builder, metadataUsage, type));
}
}
}

// 2858
private void appendPrefixMetadataMember(Appender builder, MetadataUsage metadataUsage, Type type) {
Metaclass def = metadataUsage.getMetadataDefinition();
if (def != null) {
builder.appendSpaceIfNeeded().append("#").append(this.getDeresolvableName(def, type));
this.childrenMembershipToSkip.add(metadataUsage.getOwningMembership());
}
}
Two things combine here:

appendPrefixMetadataMember is the only rendering a MetadataUsage ever gets. There is no caseMetadataUsage, and the metadata keyword is never emitted anywhere in the serializer.
The usage's own membership is added to childrenMembershipToSkip, so its owned features are not serialized either.
The regular children traversal also excludes metadata usages explicitly (lines 699 and 942):

.filter(this.relationPredicates.isMembership().and(this.relationPredicates.isMetadataUsage().negate()).or(this.relationPredicates.isImport()))
So there is currently no path through the serializer that can write the values.

Suggested direction
The #Definition prefix form has no place to put values, so the fix is not a change inside appendPrefixMetadataMember alone: a MetadataUsage that carries feature values needs to be rendered in a form that can hold a body— the @Definition { ... } form the input above used, for instance. Keeping the prefix form for usages that carry no values would preserve today's output for the common case.

We have not attempted a patch; the choice of concrete syntax is better made by the maintainers.

Why this matters
Metadata is the standard extension mechanism, so it is where downstream tools put information that has to travel with the model — traceability links, tool bindings, analysis provenance. In our case a metadata usage records which external execution a result was adopted from, and the export drops exactly that.

The failure is silent, which is what makes it costly: the model looks fine, the export succeeds, and the loss is discovered later — after the exported file has become someone's input.

Related
#2419 / #2420 — the metadata of a connector end declared inline is dropped by the export. Same class of problem, for a different construct. This report is about the feature values of a metadata usage generally.
Out of scope
The # prefix form itself, which is correct for usages that carry no values.
Metadata on connector ends (already handled by #2420).
Import; the import side reads the values correctly.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions