Linking an existing ontology
New in v0.11.0
syside reqif import generates a workspace from a ReqIF document. Linking is the
other situation: the SysML v2 side already has an ontology of its own, and a partner’s
.reqif describes the same concepts under the partner’s identifiers and names.
syside reqif link records which of your elements is which of the partner’s ReqIF
entities, by writing @reqif annotations onto your own elements. Once they carry the
annotations, syside reqif export --source <partner> exports your ontology as that
partner’s exchange, and your element names never have to change.
Walkthrough
Every command and every output below comes from the two files in this archive, so the
walkthrough can be followed by running it. Unzip it, cd into it, and start at step 1.
Download the walkthrough model (ZIP)
The two inputs
Partner B sends a partner.reqif describing their requirement schema. Only the
type-level part matters for linking, so this is the whole of it, trimmed of the header:
<REQ-IF-CONTENT>
<DATATYPES>
<DATATYPE-DEFINITION-STRING IDENTIFIER="dt-string" LONG-NAME="Text"
MAX-LENGTH="512" LAST-CHANGE="..."/>
<DATATYPE-DEFINITION-ENUMERATION IDENTIFIER="dt-severity" LONG-NAME="SeverityLevels"
LAST-CHANGE="...">
<SPECIFIED-VALUES>
<ENUM-VALUE IDENTIFIER="ev-low" LONG-NAME="Low" LAST-CHANGE="...">
<PROPERTIES><EMBEDDED-VALUE KEY="0" OTHER-CONTENT=""/></PROPERTIES>
</ENUM-VALUE>
<ENUM-VALUE IDENTIFIER="ev-high" LONG-NAME="High" LAST-CHANGE="...">
<PROPERTIES><EMBEDDED-VALUE KEY="1" OTHER-CONTENT=""/></PROPERTIES>
</ENUM-VALUE>
</SPECIFIED-VALUES>
</DATATYPE-DEFINITION-ENUMERATION>
</DATATYPES>
<SPEC-TYPES>
<SPEC-OBJECT-TYPE IDENTIFIER="sot-requirement" LONG-NAME="Requirement" LAST-CHANGE="...">
<SPEC-ATTRIBUTES>
<ATTRIBUTE-DEFINITION-STRING IDENTIFIER="ad-title" LONG-NAME="Title" LAST-CHANGE="...">
<TYPE><DATATYPE-DEFINITION-STRING-REF>dt-string</DATATYPE-DEFINITION-STRING-REF></TYPE>
</ATTRIBUTE-DEFINITION-STRING>
<ATTRIBUTE-DEFINITION-ENUMERATION IDENTIFIER="ad-severity" LONG-NAME="Severity"
LAST-CHANGE="...">
<TYPE><DATATYPE-DEFINITION-ENUMERATION-REF>dt-severity</DATATYPE-DEFINITION-ENUMERATION-REF></TYPE>
</ATTRIBUTE-DEFINITION-ENUMERATION>
</SPEC-ATTRIBUTES>
</SPEC-OBJECT-TYPE>
</SPEC-TYPES>
</REQ-IF-CONTENT>
Your workspace already has an ontology, written the way your organization names things. It has nothing to do with ReqIF yet:
package Vehicle {
private import SysideReqIF::*;
enum def Criticality {
enum low;
enum high;
}
abstract requirement def SafetyRequirement {
attribute summary : ScalarValues::String;
attribute criticality : Criticality;
}
}
The same six concepts appear on both sides under different names: Requirement is
SafetyRequirement, Title is summary, Severity is criticality,
SeverityLevels is Criticality, and the two enum values are low and high.
Linking is how you say so.
Note
Differing names are not a requirement, only a way to make the mapping visible in this
example. Linking is about identity, not renaming, and the names matching already is
a common case: the partner’s Status enum and your Status enum are the same
concept, and you want the two treated as one.
Reaching for import there is what goes wrong. import keys off @reqif
annotations, so an element that carries none is invisible to it: it cannot tell that
your Status means the same thing, and writes a second Status into
ReqIF_Import_Enums beside the one you already have. No error is raised, because the
two live in different packages. You are left with two disconnected copies of one
concept, and only the imported copy survives a round-trip.
link binds the partner’s identity onto the element you already have, and writes
nothing else. When a name already matches, apply leaves long_name off the
annotation entirely, since there is no partner-side name that differs from yours to
record.
Step 1: scaffold
Run syside reqif init first, since the archive deliberately ships no
SysideReqIF.sysml (that way you get the copy matching your installed Syside). Then:
syside reqif link scaffold partner.reqif --source partner-b --output mapping.json
INFO: Wrote mapping scaffold to mapping.json
INFO: 0 bound, 6 unbound, 0 drifted field(s); edit bindings and run 'syside reqif link apply'
The scaffold transcribes every type-level entity in the .reqif into a row with a
binding field, and pre-fills each binding from the annotations the workspace
already carries for this source. Nothing is annotated yet, so all six come back null:
{
"schema_version": 1,
"source": "partner-b",
"reqif_file": "partner.reqif",
"reqif_digest": "sha256:5e1233e93a4438db1341b837c266afbf107a6f8ae274f7a46d79aebbfa5bcd08",
"concepts": [
{
"kind": "spec_object_type",
"identifier": "sot-requirement",
"long_name": "Requirement",
"binding": null,
"attributes": [
{
"identifier": "ad-title",
"long_name": "Title",
"datatype": {
"kind": "string",
"identifier": "dt-string",
"long_name": "Text",
"max_length": 512
},
"binding": null
},
{
"identifier": "ad-severity",
"long_name": "Severity",
"datatype": {
"kind": "enum",
"identifier": "dt-severity",
"long_name": "SeverityLevels",
"binding": null,
"values": [
{ "identifier": "ev-low", "long_name": "Low", "binding": null },
{ "identifier": "ev-high", "long_name": "High", "key": 1, "binding": null }
]
},
"binding": null
}
]
}
]
}
Two details visible here recur throughout:
The string datatype row has no
binding. A scalar datatype has no element of its own in SysML v2; its identity rides on the@reqif_stringmarker tag written under the attribute that uses it. That is why the summary says six entities, not seven.ev-lowcarries nokeybutev-highcarries"key": 1. Fields are written only when they differ from theSysideReqIFlibrary default, and an absent field means “leave it at the default”.
Step 2: fill in the bindings
Replace each null with the qualified name of the element it is:
sot-requirement -> "Vehicle::SafetyRequirement"
ad-title -> "Vehicle::SafetyRequirement::summary"
ad-severity -> "Vehicle::SafetyRequirement::criticality"
dt-severity -> "Vehicle::Criticality"
ev-low -> "Vehicle::Criticality::low"
ev-high -> "Vehicle::Criticality::high"
Step 3: apply
syside reqif link apply mapping.json
INFO: Applied mapping for source 'partner-b': 7 annotation(s) written, 0 repaired,
0 moved away, 1 file(s) rewritten.
Seven annotations for six bound entities: the extra one is the @reqif_string marker
carrying the scalar datatype. Vehicle.sysml now reads (trimmed to one attribute):
abstract #reqif_spec_object_type requirement def SafetyRequirement {
attribute summary : ScalarValues::String {
@reqif {
:>> reqif::identifier = "ad-title";
:>> reqif::last_change = "2026-08-18T15:34:40+03:00";
:>> reqif::source = "partner-b";
:>> reqif::long_name = "Title";
}
@reqif_string {
:>> reqif::identifier = "dt-string";
:>> reqif::last_change = "2026-08-18T15:34:40+03:00";
:>> reqif::source = "partner-b";
:>> reqif_string::max_length = 512;
:>> reqif::long_name = "Text";
}
}
attribute criticality : Criticality { ... }
@reqif {
:>> reqif::identifier = "sot-requirement";
:>> reqif::last_change = "2026-08-18T15:34:40+03:00";
:>> reqif::source = "partner-b";
:>> reqif::long_name = "Requirement";
}
}
This is the whole idea: your names stayed yours. The partner’s name rides in
long_name and the partner’s identity in identifier, both stamped with
source = "partner-b". apply also added the #reqif_spec_object_type prefix,
because that is what makes the def a spec object type on the ReqIF side.
Step 5: export as the partner’s exchange
syside reqif export out.reqif --source partner-b --title "Vehicle safety requirements"
out.reqif carries partner B’s identifiers and names throughout, with no trace of
yours:
IDENTIFIER="sot-requirement" LONG-NAME="Requirement"
IDENTIFIER="ad-title" LONG-NAME="Title"
IDENTIFIER="ad-severity" LONG-NAME="Severity"
IDENTIFIER="dt-severity" LONG-NAME="SeverityLevels"
IDENTIFIER="ev-low" LONG-NAME="Low"
IDENTIFIER="ev-high" LONG-NAME="High"
The requirement authored in step 4 is translated on the way out. It was written against
summary and Criticality::high, and it leaves as ad-title and ev-high, under the
spec object type the def specializes:
<SPEC-OBJECT IDENTIFIER="d3daf1b4-6735-4335-956b-46aaea93330c"
LAST-CHANGE="2026-08-18T15:46:08+03:00" LONG-NAME="WindowTiltDetection">
<VALUES>
<ATTRIBUTE-VALUE-STRING THE-VALUE="The window must stop on obstruction">
<DEFINITION>
<ATTRIBUTE-DEFINITION-STRING-REF>ad-title</ATTRIBUTE-DEFINITION-STRING-REF>
</DEFINITION>
</ATTRIBUTE-VALUE-STRING>
<ATTRIBUTE-VALUE-ENUMERATION>
<VALUES>
<ENUM-VALUE-REF>ev-high</ENUM-VALUE-REF>
</VALUES>
<DEFINITION>
<ATTRIBUTE-DEFINITION-ENUMERATION-REF>ad-severity</ATTRIBUTE-DEFINITION-ENUMERATION-REF>
</DEFINITION>
</ATTRIBUTE-VALUE-ENUMERATION>
</VALUES>
<TYPE>
<SPEC-OBJECT-TYPE-REF>sot-requirement</SPEC-OBJECT-TYPE-REF>
</TYPE>
</SPEC-OBJECT>
The translation is the annotations doing their job: every element the requirement touches already carries partner B’s identity, so nothing about the requirement itself has to.
A second partner is a second --source: the same elements can carry partner C’s
identities alongside partner B’s, and each export writes only its own. See
Serving several exchange partners .
Step 6: re-run freely
Scaffolding again now reproduces the mapping from the annotations, and applying an unchanged mapping does nothing at all:
INFO: all 6 entities bound and up to date; nothing to apply
INFO: Mapping already applied; workspace unchanged.
The no-op is byte-identical: not even a file’s modification time is touched.
The loop
The walkthrough above is one pass through a loop that can be entered at any point:
init -> author ontology -> { scaffold -> edit bindings -> apply } -> export --source S
Step |
When to run it |
|---|---|
|
Once per workspace, before anything else. |
author your ontology |
Any time before |
|
Whenever either side may have changed, and whenever you are unsure. Always safe: it only reads. |
edit the |
Only if the scaffold reports unbound or wrongly-bound rows. Its summary line says so without opening the file. |
|
After editing, and after adding any elements the mapping asked for. |
|
Any time. Reads the annotations, never the JSON. |
The annotations are the stored state. The JSON mapping is a working copy of one
reconciliation, never state of its own: it can be regenerated at any time, no other
command reads it, and nothing depends on keeping it. --source is required on
scaffold; without --output the scaffold goes to stdout.
Two further properties are worth knowing:
applyis all-or-nothing. Every problem is collected and reported together, and nothing is written unless the whole mapping is clean and the resulting workspace still satisfies the ReqIF identity preconditions.Skipping
linkentirely is fine when the partner’s ReqIF was imported rather than mapped:importwrites the annotations itself.
The mapping document
A binding is one of three things: a qualified name, null (unmapped, which apply
skips), or the string "!unbind" (remove this mapping from whatever element holds it).
Deleting a row is not an unmap, since apply acts only on the rows present in the
document.
Datatype and enum value rows also carry the ReqIF facts that live on marker tags:
max_length for strings, min / max for integers and reals, accuracy for reals,
and key / other_content for enum values. These are always taken from the .reqif
file rather than from a pre-existing workspace annotation, because the partner’s
exchange is the authority on its own datatypes. A workspace tag that disagrees is
repaired by apply, and that is what the summary line’s drifted count reports.
Staleness
link scaffold records a digest of the .reqif bytes it read in the mapping’s
reqif_digest field, and link apply refuses a mapping whose file no longer hashes to
it:
ERROR: the mapping was scaffolded from an older/different version of 'partner.reqif';
re-run `syside reqif link scaffold`.
A re-export may have added, dropped, renamed, or re-typed entities, so rows scaffolded
from the old bytes can no longer be trusted to describe the same things. Re-running
scaffold is the fix; deleting the digest is not.
The .reqif file is located relative to the mapping document’s own directory and
nowhere else, since the two normally travel together. If it is not reachable (the
document was moved away from it, or came from another machine), apply proceeds and
says so. It can proceed because apply never reads the .reqif: scaffold
transcribed every ReqIF fact into the rows, so the document is self-contained and the
file is only ever used to corroborate it.
Diagnostics
A binding that does not resolve is reported one of two ways, never both.
When an existing sibling name is a near match, the fix belongs in the JSON:
ERROR: the mapping cannot be applied; 1 problem found:
- concepts[0] (spec_object_type 'sot-requirement').attributes[0] ('ad-title'):
binding 'Vehicle::SafetyRequirement::summry' does not resolve to any element in
the workspace; did you mean 'Vehicle::SafetyRequirement::summary'?
When nothing in the workspace resembles it, the error ends with a paste-ready SysML v2 skeleton of the elements the mapping expects, nested per the qualified names and declared as the importer requires:
ERROR: the mapping cannot be applied; 1 problem found:
- concepts[0] (spec_object_type 'sot-requirement'): binding
'Chassis::BrakeRequirement' does not resolve to any element in the workspace
The mapping expects these elements in your ontology; add them (or fix the names)
and re-run apply:
package Chassis {
private import SysideReqIF::*;
abstract requirement def BrakeRequirement;
}
Pasting that and re-running the same link apply succeeds. Members missing from an
element you already have are listed separately, under that element’s qualified name,
since redeclaring the owner would shadow the real one.
Either way the error also notes that the ontology may simply have moved on since the
mapping was scaffolded, in which case re-running link scaffold is the fix.