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_string marker tag written under the attribute that uses it. That is why the summary says six entities, not seven.

  • ev-low carries no key but ev-high carries "key": 1. Fields are written only when they differ from the SysideReqIF library 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 4: author in your own vocabulary

From here on the partner’s vocabulary never has to be typed again. Write a new requirement the way your ontology reads, specializing your own SafetyRequirement and redefining your own attributes with your own enum literal. The only ReqIF-aware line is the @reqif block naming the exchange it belongs to:

requirement def WindowTiltDetection :> SafetyRequirement {
    @reqif {
        :>> reqif::source = "partner-b";
    }
    attribute :>> summary = "The window must stop on obstruction";
    attribute :>> criticality = Criticality::high;
}

Then mint its ReqIF identity:

syside reqif lock --source partner-b
INFO: Locking Vehicle::WindowTiltDetection...

lock fills in the identifier and last_change fields on the annotation and leaves everything else alone. Both are minted fresh on every run, so the UUID you get will not be the one shown below.

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

syside reqif init

Once per workspace, before anything else.

author your ontology

Any time before apply. The elements a mapping binds to must exist.

syside reqif link scaffold

Whenever either side may have changed, and whenever you are unsure. Always safe: it only reads.

edit the binding fields

Only if the scaffold reports unbound or wrongly-bound rows. Its summary line says so without opening the file.

syside reqif link apply

After editing, and after adding any elements the mapping asked for.

syside reqif export --source S

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:

  • apply is 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 link entirely is fine when the partner’s ReqIF was imported rather than mapped: import writes 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.