Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Chapter 19b — External Vocabulary Map

The DSL ships with its own nouns and its own verbs. Most readers already carry one of four other vocabularies. This chapter is the phrasebook.

A DSL does not land in an empty room. By the time a reader reaches this series, they are already carrying a vocabulary — Jira tickets, SysML diagrams, ISO 29148 strata, Gherkin scenarios — and they are reading this package through the grammar of whichever vocabulary they know. The question that arrives in every code review, every lunch, every conference corridor, is the same one: how does your thing map onto the thing I already do?

This chapter answers that question four times, once for each of the four target vocabularies. The aim is not to claim superiority. It is to be honest about which mappings are lossless, which are lossy, and which break. Three of the four vocabularies carry something the DSL does, in a shape close enough that the translation is local and mechanical. The fourth — BDD — is structurally different in a way that is worth naming, because most readers who try to map AC to Scenario will produce a bad fit and not realise why.

A preview of the verdict, drawn as a translation matrix before the prose explains each cell:

Diagram
The four-way translation matrix. Green cells are lossless; amber cells are lossy (the target vocabulary carries the concept but loses a property the DSL holds); red cells are absent (the target vocabulary has no carrier at all).

Read the matrix column by column. SysML is the green column — five of six constructs map without loss. ISO 29148 is mostly green but breaks on the test binding (red). Jira is entirely amber — every concept has a carrier but every carrier loses a structural property. BDD has two green, one amber, and three red cells, concentrated on the relations.

The rest of the chapter walks each column and then closes with a focused section on the three red cells — the places where the mapping does not merely lose a property but fails outright.

Vocabulary 1 — Jira: Epic, Story, Sub-task

Jira is the vocabulary most readers carry. The mapping is tempting because every concept has a surface carrier, but the carrier is in each case a ticket type, not a type in the type-theoretic sense. The full mapping, drawn out:

  • REQ → Epic. An Epic is Jira's longest-lived ticket type, and by Jira convention it is the one that holds a business motivation. REQ-DOG-FOODthe DSL must be tested with itself; zero describe/it — would be an Epic. The Epic has a description field for the narrative. It has labels for categorisation. It has a status workflow for lifecycle. What it does not have is a parent field that means this Epic refines that Epic. Jira's hierarchy goes Epic → Story → Sub-task; it does not go Epic → Epic. The refinement graph — REQ-A refines REQ-B refines REQ-C — has to be carried outside the type system, usually in a labels-and-descriptions convention that the DSL would catch at compile time and Jira will never catch at all.

  • FEAT → Story. A Story is Jira's unit of deliverable work. FEATURE-TRACE-EXPLORER-TUI — the interactive TTY browser over the traceability graph — would be a Story under the REQ-DISCOVERABLE-TRACEABILITY Epic. The abstract methods on the Feature class (traceExplorerBuildsGraph, traceExplorerHandlesArrowKeyNavigation, etc.) would become the bullet list in the Story description, or, if the team has discipline, individual Sub-tasks. The mapping is lossy on two axes. First, a Feature class satisfies multiple Requirements via @Satisfies(ReqA, ReqB, ReqC) — the decorator takes a class list. A Jira Story links to a single Epic via the Epic Link field. The many-to-many relation collapses to many-to-one the moment it enters Jira. Second, the abstract methods on the class are type-checked — rename one, and the @Verifies<T>('...') chain breaks at compile time. The bullet list in a Jira description is a string. Rename an AC in code and the bullet list in Jira says something that no longer exists.

  • AC → Sub-task (or bullet, or Xray test). This is where Jira offers three incompatible carriers and each team picks one. Sub-tasks are the structural match — a child of the Story, with its own status workflow, assignable, estimatable. The problem is that Sub-tasks are heavy; teams who create one Sub-task per AC quickly drown in ticket count. The alternative, a bullet list in the Story description, is light but loses all structure. The third option, an Xray test case linked to the Story, is structurally cleanest but requires the plugin and its licence tax. Whichever option a team picks, the mapping has the same flaw: the AC name is a string in Jira, and nothing validates that string against the codebase.

  • @Refines → label or issue link. Jira has no first-class refinement relation between same-type tickets. The closest carriers are issue links (free-form relations like blocks, relates to, is part of) and labels. Both are strings. Both require a convention. Both break silently on rename.

  • @Satisfies → Epic Link or issue link. The decorator that the DSL uses for many-to-many Feature-to-Requirement mapping collapses to Jira's single-Epic-per-Story constraint. Teams who need many-to-many reach for labels (label:REQ-DOG-FOOD, label:REQ-DISCOVERABLE-TRACEABILITY, label:REQ-PARALLEL-DELIVERABLE) or for the links field. Same observation as @Refines — strings, convention, silent rot.

  • @Verifies → Xray link, or test-name convention. Out of the box, Jira has no relation between a ticket and a test file. The standard workaround is a test-name convention (describe('STORY-123', ...)) that greps find and nothing validates. Xray adds a real relation, but it is a separate registry with its own lifecycle, and it charges per seat.

The Jira column of the matrix is amber across the board. Every concept has a carrier. Every carrier loses a property the DSL holds. The pattern is consistent: types become strings, relations become conventions, validation becomes team discipline. A team that runs on Jira and ports to the DSL loses nothing, because the DSL carries every relation Jira carries and more. A team that runs on the DSL and ports to Jira loses — at minimum — the typed refinement graph, the typed many-to-many satisfaction, the compile-time AC name checks, and the automatic test binding. Jira is not wrong; it is a different kind of thing. It is a workflow tool. The DSL is a specification tool. They are adjacent, not interchangeable.

One honest note on Jira. The vocabulary we are mapping onto is the out-of-the-box Jira vocabulary. Jira with Xray plus Structure plus a custom-field set plus a scripted post-function hook can close some of the gaps — at the cost of five plugins, three admin roles, and a compliance model that lives half in the database and half in Groovy. The DSL's pitch is not Jira is broken; it is the same semantics, in the type system, with no plugins, no DB, and no Groovy. That pitch is honest only if the mapping is honest, which is why the matrix is amber and not red.

A second honest note on Jira. The single feature Jira has that the DSL does not is workflow. Jira ticket states — To Do, In Progress, In Review, Done — are first-class, with configurable transitions, role-gated approvals, and a time-in-state metric that feeds velocity and burndown charts. The DSL carries no workflow state; a Requirement class is either in the codebase or it is not, and its status is implicit in whether the satisfying Features are fully covered by passing tests. For a team that runs sprint planning and needs stakeholder-visible Kanban boards, the workflow features of Jira are not optional — they are the point of the tool. The pragmatic split: Jira owns the when (planning, states, transitions), the DSL owns the what (the typed specification). A Requirement class can carry a one-line comment pointing at the Jira ticket key that represents the workflow side of the same facts. If the Jira key is renamed, the comment is stale but nothing breaks; if the Requirement class is renamed, the typed graph updates and the Jira comment becomes a stale pointer in the other direction. Neither side is authoritative about the other; they are separate registries coordinating via convention. A team that tries to make one authoritative about the other creates either a rigid workflow (the DSL dictating ticket states, which breaks sprint flexibility) or a rigid specification (Jira dictating refinement, which breaks the type checks). The split is the less clever answer, and the one that survives contact with a real engineering organisation.

A third honest note on Jira. The mapping we have drawn is the standard Jira mapping (Epic/Story/Sub-task). A non-trivial share of Jira installations run a custom scheme — Initiative/Epic/Story/Sub-task, or Theme/Initiative/Epic/Story, or worse. The DSL's refinement graph has no fixed depth, so it maps naturally onto whichever ladder a team has chosen; but the mapping is still amber, because the ladder's rungs are still ticket types, not relations. The Initiative-Epic relation in a four-rung Jira scheme is still a hierarchy link (parent ticket points at child ticket), not a typed refinement, and it still breaks on rename.

A note on Azure DevOps and Linear

Jira is the reference point because it has the largest installed base, but the mapping applies with minor adjustments to the other project-management tools a reader might already carry. Azure DevOps has Epic / Feature / User Story / Task / Bug — a four-rung ladder instead of Jira's three — but the structural properties are the same: tickets are untyped, cross-references are strings, the refinement graph is a parent-child link that breaks on ticket-type changes. Linear has Project / Issue / Sub-issue — a shallower hierarchy — with the same string-based cross-reference mechanism. Both tools' amber mapping is identical in shape to Jira's; the labels on the cells change, the failure modes do not.

Teams on Azure DevOps often use the area path and iteration path fields to carry structure the ticket type does not. These fields are strings with a tree-structured syntax (Product\Component\Subcomponent), and they break on rename exactly the way Jira's Epic Link breaks. The DSL's typed decorators replace both fields; a team migrating from Azure DevOps drops the area-path convention and relies on @Refines for the structural relation.

Linear's approach is minimal — the Project field is the single hierarchical carrier, and everything below it is flat. Teams with multi-level requirements running on Linear typically use tags (Linear's labels) to carry refinement, which is the same convention-driven-string approach as Jira labels with the same drift properties. Migrating from Linear to the DSL is the easiest of the three because Linear's schema is small and the specification-side structure is minimal; the engineering cost is in adding the refinement graph, not translating it.

Vocabulary 2 — SysML: «satisfy», «deriveReqt», «verify»

SysML is the vocabulary the DSL was designed with in mind, even if the design was not explicitly modelled on the standard. The structural match is cleanest of the four. Three decorators map to three stereotypes without loss:

  • @Satisfies → «satisfy». In SysML, «satisfy» is the relation from a Block (a design element) to a Requirement block (a specification element). The arrow runs from the Block to the Requirement: this block satisfies this requirement. The DSL decorator has exactly this directionality — it is written on the Feature class, and its arguments are Requirement classes. @Satisfies(ReqDiscoverableTraceabilityRequirement, ReqDogFoodRequirement) reads as «satisfy» directed at two Requirement blocks simultaneously. The many-to-many nature is preserved — SysML «satisfy» is a relation, not a reference, and multiple «satisfy» arrows on a single block are valid.

  • @Refines → «deriveReqt». SysML's refinement relation between Requirement blocks is «deriveReqt»: this requirement is derived from that requirement. The stratum shift (stakeholder → system → software, in 29148 language) is carried by chains of «deriveReqt» in SysML and by chains of @Refines in the DSL. The DSL ships a DAG check (Refines must form a DAG, not a cycle); SysML models with «deriveReqt» cycles are similarly ill-formed, though not every SysML tool enforces the check.

  • @Verifies → «verify». SysML's «verify» relation runs from a Test Case (a behaviour element) to a Requirement block (or to an AC on one). The DSL decorator runs from a test class to a Feature-and-AC pair; the AC is the atomic unit being verified, and the decorator's string argument is the name of the abstract method that holds the AC. The mapping is not exactly «verify» onto the Requirement — it is «verify» onto the AC of the Requirement, which SysML also supports via the Requirement block's text and satisfiedBy / verifiedBy sub-attributes.

Two things do not map. The first is Style — the plurality of rhetorics (default, industrial, lean, agile, kanban) that each Requirement class picks. SysML has no equivalent concept. A SysML Requirement has a text attribute that is free-form prose; it does not carry a style selector. A port of the DSL to SysML would collapse the five styles into the single text attribute and lose the ability to re-derive the prose from the facts. This is a loss of rhetorical choice, not of structural information — a team that does not care about rhetorical plurality will not notice. A team whose stakeholders include a safety auditor who wants industrial SIL phrasing and a PO who wants lean phrasing will notice immediately.

The second is the registry. The DSL ships with a runtime registry — every @Satisfies call registers the Feature-Requirement edge in a module-scoped map, so a single npx requirements trace pass can walk every edge in the codebase without parsing AST. SysML has no equivalent convention; the registry is implicit in the model file that holds the diagrams, and whatever tool renders the diagrams owns the traversal. This is not a structural mismatch — it is an execution-context mismatch. SysML is modelled before code; the DSL is modelled in code.

The verdict on SysML is clean: a reader who already runs SysML can port to the DSL in an afternoon, and the DSL adds typed AC methods and the Style plurality on top of the mapping SysML already gives them. A reader who ports the DSL out to SysML loses Style and gains diagrams. For most teams this is a worthwhile trade in both directions, depending on which artefact the audience wants to read.

A remark on why SysML is the cleanest mapping. The DSL's three decorators were not chosen by reading SysML first; the convergence is the other way around. The DSL's three verbs — satisfies, refines, verifies — are the minimal set needed to carry the REQ → FEAT → AC → Test chain without collapse. SysML arrived at the same minimal set because the same structural problem has the same structural answer. «satisfy» is the relation from a design to a specification; «deriveReqt» is the relation between specifications at different strata; «verify» is the relation from a test to an AC. These three are the joints of requirements engineering, and any vocabulary that closes the chain has to name all three. SysML names them; the DSL names them; Jira's standard vocabulary names only one (the Epic-Story hierarchy, which conflates «deriveReqt» and «satisfy»); Gherkin names none. The cleanness of the SysML mapping is not an accident — it is the convergent minimum of two careful efforts to close the same loop.

A remark on tool chains. SysML is rarely edited by hand; the usual surface is a modelling tool (Cameo Systems Modeler, MagicDraw, Enterprise Architect, Papyrus) that writes XMI files which may or may not version-control cleanly. The DSL's surface is .ts files that version-control exactly like any other source. A team running SysML has a licence discussion and a tool-admin discussion; a team running the DSL has neither. This is not a structural difference between the vocabularies — it is a difference in where the vocabulary lives. Both can be printed, both can be reviewed, both can be exported. The DSL's location in the code repository is a practical win for small teams; SysML's location in a dedicated tool is a practical win for large organisations with formal model-based engineering processes. Neither is wrong; they target different gradients of organisational size.

An aside on SysML 2.0

The discussion above targets SysML 1.x, which is the version most current practitioners use. SysML 2.0, published by the OMG in 2023, introduces a textual syntax (not just the diagrammatic notation) that looks structurally close to a programming language. In SysML 2.0 a requirement is declared as:

requirement def discoverable_traceability {
  doc /* a user browsing the graph must be able to discover
         relations without prior knowledge of the vocabulary */
  subject user : User;
  require assume user.isExploring == true;
}

This is closer to the DSL's surface than SysML 1.x's XMI was. The textual syntax carries the same «satisfy», «deriveReqt», «verify» relations, and the file is version-controllable as plain text. A team that adopts SysML 2.0 and the DSL simultaneously has two structurally similar text-based specification languages; the choice between them comes down to toolchain (the DSL runs on tsc, SysML 2.0 runs on the SysML 2.0 reference implementation) and audience (the DSL's compile-time checks are instant in a TypeScript IDE; SysML 2.0's checks require the dedicated tool).

A future direction the DSL could take is a SysML 2.0 output format, rendering each Requirement class as a SysML 2.0 requirement def block and each @Satisfies / @Refines / @Verifies decorator as the corresponding SysML 2.0 relation. This would give the DSL a bi-directional bridge to the SysML ecosystem with no loss on the core relations. Style plurality would remain a DSL-only concept.

Vocabulary 3 — ISO/IEC/IEEE 29148: the three strata

ISO/IEC/IEEE 29148:2018 — Systems and software engineering — Life cycle processes — Requirements engineering — is the international standard most enterprise programmes either claim to follow or silently fail to follow. It partitions requirements into three strata, and the DSL's @Refines graph aligns with those strata as follows:

  • Stakeholder Requirements Specification (StRS) — the top stratum. These are business requirements phrased in stakeholder language. The traceability graph must be discoverable by a user with no prior knowledge of the vocabulary. That is a stakeholder requirement. In the DSL, it is a Requirement class at the root of a @Refines chain — no parent.

  • System Requirements Specification (SyRS) — the middle stratum. These are architectural requirements derived from the StRS. The traceability graph must expose a keyboard-navigable TTY interface with arrow-key drill-down. That is a system requirement. In the DSL, it is a Requirement class with an @Refines(StRSParent) decorator.

  • Software Requirements Specification (SRS) — the bottom stratum. These are implementation requirements derived from the SyRS. The TTY interface must exit cleanly on Ctrl-C and refuse to start when stdout is not a TTY. That is a software requirement. In the DSL, it is either a Requirement class with an @Refines(SyRSParent) decorator or — more commonly — an AC on a Feature that satisfies the SyRS requirement.

Two things are worth naming explicitly. The first is that DefaultStyle is 29148-aligned by construction. The modal verbs baked into the default rhetoric — shall, should, may, will, must — are pulled directly from ISO 29148 §5.2.4, which defines the requirement-level modal verbs and their binding strength. The other styles in the DSL deviate deliberately: IndustrialSilStyle adds SIL-level annotations, LeanStyle drops the modals in favour of user-story phrasing, EarsStyle imposes the five EARS templates, and so on. A reader who opens a Requirement class with style = DefaultStyle and compares its render() output to an ISO 29148 §5.2.4 requirement entry will find them structurally indistinguishable — that is the intent.

The second is that ISO 29148 has no test binding. The standard defines requirements, their attributes (id, text, rationale, priority, stakeholder, source, fit criterion), and the relations between them (derives, allocates, satisfies). It does not define how a requirement binds to a test artefact. The fit-criterion attribute (§5.2.7) is the closest thing — a verifiable statement of what it would take to accept the requirement as met — but the standard stops at the statement. How the statement binds to a test class, a test method, or a test run is explicitly out of scope, deferred to the verification and validation processes defined in the companion standards (ISO/IEC/IEEE 15288 and 12207).

This is the gap @Verifies closes. The DSL binds a test class to an AC method on a Feature class that satisfies a Requirement class, and the whole chain is type-checked. ISO 29148 does not prescribe this binding because the standard is written to be tool-agnostic; any tool is permitted to close the gap however it likes. The DSL closes it with a TypeScript decorator and compile-time key-of checks. A Jira-plus-Xray team closes it with a plugin database. A SysML team closes it with «verify» arrows to a Test Case diagram. The shapes differ; the gap is the same gap.

The verdict on 29148 is green-with-one-red. Three strata map lossless onto @Refines chains. Modal verbs map lossless onto DefaultStyle. Fit criteria map onto AC method names. The test binding is absent by design — a hole the standard leaves for tools to fill.

A note on the other attributes §5.2 defines. The standard lists a longer attribute set than modal verb and fit criterion: identifier, stakeholder, source, rationale, priority, risk, dependency, verification method, acceptance criterion, assumptions, conflicts. The DSL carries most of these, not all. The id field is first-class. The title field carries the stakeholder-facing name. The style-rendered prose carries the rationale and the modal. The priority enum on the Feature carries the priority for the satisfying work (but not on the Requirement itself — a gap the series notes in chapter 04). The @Refines graph carries the dependency relation when it is a true refinement; when the dependency is lateral (Requirement A cannot be implemented before Requirement B is live) there is no direct carrier and the standard's dependency attribute has no DSL equivalent. The verification method attribute — whether a requirement is verified by test, inspection, analysis, or demonstration — is partly carried by @Verifies (which picks the test-based method by construction) and left open for the other three. A team doing safety certification will need to supplement the DSL with manual inspection records; @Verifies does not pretend to cover non-test verification.

A note on the 29148 document shape. The standard prescribes a document layout — cover page, scope, references, definitions, stakeholder list, the requirements themselves, traceability tables, appendices. The DSL's requirements render command produces the requirements section and the traceability table; the surrounding sections are not generated. A team that needs a full 29148-compliant SRS document runs the DSL's render pass and pastes the output into a document template. This is a pragmatic split — the DSL owns the structured part, the template owns the unstructured framing. A future DSL version may ship a 29148 document template, but the absence is not a structural gap; it is a scope choice.

A walk-through of the three strata, using the running example, to make the mapping concrete:

Stratum 1 — StRS. The stakeholder requirement is a user browsing the traceability graph must be able to discover relations without prior knowledge of the vocabulary. In the DSL, this is ReqDiscoverableTraceabilityRequirement — a root Requirement class with no @Refines decorator. Its DefaultStyle-rendered body reads The system shall present the traceability graph in a form that a user without prior vocabulary can explore and navigate. The modal verb shall is 29148-aligned. The entry would sit in the StRS document at position StRS-01.

Stratum 2 — SyRS. A system requirement derived from StRS-01 is the traceability graph shall expose a keyboard-navigable TTY interface with arrow-key drill-down. In the DSL, this is a Requirement class with @Refines(ReqDiscoverableTraceabilityRequirement). Its DefaultStyle body reads The system shall expose an interactive TTY interface over the traceability graph, supporting arrow-key navigation, drill-down, and help-overlay display. The entry sits in the SyRS document at SyRS-TRACE-EXPLORER-01, with a trace back to StRS-01 via the @Refines chain.

Stratum 3 — SRS. A software requirement derived from SyRS-TRACE-EXPLORER-01 is the TTY interface shall exit cleanly on Ctrl-C and refuse to start when stdout is not a TTY. In the DSL, this is either a further Requirement class at the SRS stratum or — more commonly — an AC method on the FeatureTraceExplorerTuiFeature class: traceExplorerExitsCleanlyOnCtrlC() and traceExplorerRefusesToStartOnNonTty(). The choice between SRS-as-Requirement-class and SRS-as-AC-method is the judgement call the chapter mentioned earlier — broad enough to be its own predicate with its own refinement lineage, or narrow enough to be tested as a method on a Feature. Method-sized statements go on Features; policy-sized statements go on Requirements.

The three-stratum walk-through illustrates that the DSL's @Refines chain is not merely a tree — it carries the stratum shift that 29148 makes explicit. A team that wants to enforce the stratum separation can add a convention: root Requirements are StRS, middle layer is SyRS, leaf Requirements (or ACs) are SRS. The DSL does not enforce the convention, but it does not forbid it; a linter could be written to check it if a team needs the enforcement.

Vocabulary 4 — BDD: Feature, Scenario, Given-When-Then

BDD — Behaviour-Driven Development — is the vocabulary most readers try to map first, because the word Feature is shared and the word Scenario sounds like an AC. Both intuitions are partly wrong, and the partly-wrong is worth unpacking because it is the most common misreading of this series.

Start with the surface mapping:

  • Feature file header → REQ. A .feature file header — Feature: Discoverable traceability graph followed by a short narrative — is the closest analog of a Requirement class. Both carry a name, a narrative, and a scope boundary. The mapping is lossy in one direction: a Gherkin Feature narrative is prose; a Requirement class carries a typed Style selector, a refinement chain, a satisfaction chain. A port of a .feature file to a Requirement class keeps the narrative and adds structure. A port in the other direction loses structure.

  • Feature: block → FEAT class. Confusingly, Gherkin's Feature: keyword and the DSL's Feature class overlap in name but not exactly in role. A Gherkin Feature: block is a container for Scenarios; it does not declare an interface or a method surface. A DSL Feature class declares abstract methods (the ACs) that any concrete implementation or test class must refine. The structural match is partial: both carry a unit of deliverable work, but the DSL carries a type, and Gherkin carries a container.

  • Scenario → AC? This is the mapping to refuse. A BDD Scenario is an imperative trace: Given the graph contains three Requirements, when the user presses arrow-down, then the cursor moves to the next Requirement. An AC, in the DSL, is a predicate: the user can navigate the graph with arrow keys. The scenario instantiates the predicate — it gives one example of a trace that satisfies the predicate — but it does not replace it. A predicate holds over many traces; a trace is a single point. A Feature class with one AC can legitimately have ten Scenarios testing that AC with different inputs. The relation is one-to-many, not one-to-one.

This matters because readers who try to make AC a Scenario produce one of two bad outcomes. The first is under-specification: they pick one trace per AC and freeze it, and the AC's predicate silently narrows to only the case we wrote a scenario for. The second is over-specification: they split one AC into N Scenarios and lose the name that anchored the predicate, so the decorator surface (@Verifies<T>('traceExplorerHandlesArrowKeyNavigation')) has no key to point at. Neither outcome is fatal; both are drift.

The right mapping: a Gherkin Scenario is a test of an AC, not the AC itself. In the DSL, that is a test class with @Verifies<T>('...') and a body that happens to be structured as Given-When-Then. The DSL does not prescribe the test body's shape — it prescribes only the decorator. Teams who like Gherkin can write Gherkin inside the test body; teams who prefer plain assertions can write plain assertions. The key-of check on the decorator string is what carries the binding.

  • Given-When-Then step → Then assertion. The mapping is mostly mechanical. A Given step sets up state; a When step triggers the behaviour; a Then step asserts the post-condition. In the DSL test class, the Then assertion is the lone call that maps to expect(...).toBe(...) or the equivalent. Gherkin's three-part narration adds readability; the DSL does not require the narration but does not forbid it either.

Two things do not map. The first is the refinement graph. A .feature file is flat — it is a single Feature with a list of Scenarios, and there is no Gherkin vocabulary for this Feature derives from that Feature. The DSL's @Refines chain has no BDD equivalent. The second is the satisfaction relation. A Gherkin Scenario does not declare which Requirement it satisfies; it declares which Feature it tests, and the Feature is the Gherkin Feature, which is the closest analog of a Requirement, which makes the relation collapsed and implicit. The DSL's @Satisfies(ReqA, ReqB) has no BDD equivalent — a Scenario cannot say this trace contributes to meeting Requirement A and Requirement B simultaneously.

The verdict on BDD is the mixed one. Two green cells (Feature-file header, Gherkin step), one amber (Feature keyword), three red (Scenario-as-AC is the wrong mapping, and @Refines and @Satisfies have no Gherkin carrier at all). A team on BDD porting to the DSL keeps their Scenarios as test bodies and adds the refinement and satisfaction graphs on top. A team on the DSL porting to BDD loses both graphs and keeps only the flat Feature-Scenario relation.

A longer remark on the Scenario-as-AC mismatch, because it is the single most common misreading of the series and because the mismatch is philosophically substantive. BDD was designed — by Dan North in 2006, building on Kent Beck and Eric Evans — to push testing closer to the stakeholder, by writing test narrations in a language the stakeholder could read. The Given-When-Then structure is a narration shape borrowed from Cockburn's use-case template and from classical dramatic structure (setup, action, consequence). Its grammatical unit is the trace — one particular run through the system, with one particular set of inputs and one particular expected output. The power of the trace shape is that it is concrete; non-technical stakeholders can read a trace and say yes, that is what I want or no, that is not what I want.

An AC in the DSL is a different grammatical unit. It is a predicate — a property the system must hold, phrased as a named abstract method. traceExplorerHandlesArrowKeyNavigation() is the name of a property; the method itself returns an ACResult, which is a binary pass/fail over a single executed check. A single predicate holds over infinitely many traces. The predicate the user can navigate the graph with arrow keys is satisfied by the trace arrow-down from the first node selects the second node, and also by arrow-up from the third node selects the second node, and also by arrow-left on a branch point collapses the subtree, and so on. A BDD suite that claims to test the predicate writes one, or three, or ten Scenarios — sampling the predicate at particular points. The AC's name anchors the predicate; the Scenarios instantiate the predicate.

This is not a bug in BDD. It is a deliberate design choice — the grammatical unit of a specification written for a stakeholder is the trace, because traces are what stakeholders can evaluate. But it is not the right grammatical unit for a specification written by engineers for a type system. Type systems check predicates (the presence of a method with a given name and signature), not traces (the outcome of a run with given inputs). The DSL's grammatical unit is therefore the predicate, and the BDD Scenario demotes to the role of a test of the predicate, implemented inside the body of the @Verifies-decorated test class. Teams who accept this demotion keep both tools happy — Gherkin narration where the stakeholder can read, typed predicates where the compiler can check. Teams who resist the demotion produce suites where AC names drift from Scenario names, where refactors silently miss Scenarios, and where the count of passing Scenarios no longer maps onto the count of satisfied predicates. The resistance is the common failure mode.

A short digression on Cucumber's step-definition files. The Gherkin .feature file is the stakeholder-facing surface; the step-definition file (.rb, .py, .ts) is the engineer-facing binding that maps each Given/When/Then sentence to a runnable function. The step-definition file is closer to the DSL's register than the .feature file is — it has types, it has imports, it has a compilable shape. A team that writes step definitions in TypeScript can import the DSL's Requirement and Feature classes into the step-definition file and use them as the anchoring surface, keeping the .feature file as a narration layer and the step definitions as the typed binding. This is the most idiomatic hybrid of the two vocabularies; it is also the least discussed one in either community.

A note on the mapping's direction

Before the failure-point discussion, one directional clarification. When this chapter says X maps to Y, the phrasing is neutral — it states an equivalence, not a direction. In practice every mapping has a direction: the DSL's concept is the source, the external vocabulary's concept is the target, and the mapping is a render pass from the former to the latter. The reverse direction — importing external artefacts into DSL classes — is possible but is a one-shot operation, not a sustained sync, because the external vocabulary is more permissive than the DSL and the import cannot always produce a well-typed output without manual fixup.

Two consequences. First, the mapping preserves properties that the target vocabulary can carry and drops properties that the target vocabulary cannot carry. The DSL's typed refinement graph, when rendered to SysML, becomes a «deriveReqt» graph that SysML can carry — lossless. The same graph rendered to Jira becomes a label convention — lossy on types. The same graph rendered to Gherkin becomes nothing at all — absent. The render pass is always a projection, never a loss-free translation except in the SysML case, and even there the SysML model cannot carry the typed executable binding the DSL has.

Second, the mapping cannot be reversed in the general case. A SysML model with no DSL-style typed code, a Jira instance with only label conventions, a Gherkin suite with no refinement relations — each of these can be imported into the DSL, but the import must supply the missing structure. For SysML the missing structure is the code-level class hierarchy and the AC method names; for Jira it is the refinement graph inferred from label conventions; for Gherkin it is the entire refinement-and-satisfaction graph invented from scratch. Import is a migration step, not a sync mechanism. Once the DSL is authoritative, the flow is outward only.

Where it breaks — three failure points

The matrix's three red cells are not minor. Each one marks a place where the mapping is not merely lossy but fails outright — the target vocabulary has no carrier, and a team using that vocabulary has to invent a convention outside the tool to bridge the gap. The three failures are independent, and together they explain why the DSL is its own thing rather than a sub-set of any of the four targets.

Diagram
The three places the mapping breaks hard. Each failure is independent — none of the four target vocabularies suffers all three. The DSL addresses all three by construction.

Failure 1 — Jira has no types

Every Jira field that holds a reference to another Jira artefact is a string or a string-keyed foreign reference. The Epic Link field holds an Epic's key (PROJ-42), not a typed reference. The labels field holds free-form strings. The issue links field holds a typed relation name (blocks, relates to) plus a string-keyed foreign reference. Rename the Epic from PROJ-42 to PROJ-99 (a rare operation, but possible via project renames, cross-project moves, or Jira instance migrations) and every test file that contains describe('PROJ-42 ...', ...) or every Jira label epic:PROJ-42 or every ticket description that mentions PROJ-42 points at something that no longer exists.

The Jira UI will not warn you. The scanner will not warn you. The compliance process is manual inspection or a plugin-maintained registry. The DSL's answer — all references are class references, and the TypeScript compiler checks every one — is the answer Jira structurally cannot give. Making Jira give it would require Jira's data model to be typed, and Jira's data model is deliberately untyped for the same reason a spreadsheet is untyped: flexibility at the cost of validation. It is not a bug in Jira; it is a design choice whose cost is exactly this failure mode.

The failure mode compounds across the Jira lifecycle. A ticket that is split (one Story becomes two Stories when the original scope is too large) leaves every external reference pointing at the original key; the original is usually closed as superseded, with a link to the two successors buried in a comment. A ticket that is merged is the mirror case — two Stories become one, and references to the subsumed key break. A ticket that is archived (long-closed tickets moved out of the default search scope to keep the instance performant) is still addressable by key but invisible to most queries, which breaks dashboards that filter on the ticket label. A ticket whose project is renamed sees every key re-prefix (OLDPROJ-42 becomes NEWPROJ-42); Jira maintains the redirect for the UI but external strings (test names, commit messages, chat mentions) do not rewrite. A ticket whose instance is migrated (Jira Server to Jira Cloud, or cross-instance consolidation) may change keys entirely. Each of these five lifecycle events is routine in a multi-year Jira instance. Each silently invalidates every external string-based reference. The DSL's typed references see zero of these events because the references are classes in the same codebase and move with the codebase.

A concrete example from a team that ran this failure mode for eighteen months before catching it: the team had 2,400 test files with describe('PROJ-XXX ...', ...) strings referencing Jira tickets. The Jira project was split in half during an organisational re-alignment; half the tickets kept the PROJ- prefix, half moved to NEWPROJ-. The redirect worked in the Jira UI, but the describe strings did not rewrite. Over the next year, roughly 800 of the 2,400 test files accumulated broken references — the describe string pointed at a key that no longer existed in either project. The team discovered this when an auditor asked for a traceability report. The remediation was a two-engineer-week grep-and-rewrite exercise, plus a retrospective that concluded never use Jira keys in test names. The team moved to semantic names — describe('authentication redirects unauthenticated users ...') — and a separate (unchecked) mapping document linking semantic names to Jira keys.

The DSL's answer to the same failure mode: all test names are method names on classes, the method names are keys of the Feature class's abstract method set, and the typed binding is checked by the compiler. A ticket rename, a project split, a cross-instance migration — none of these affect the DSL because the DSL does not know about them. The specification is independent of the workflow tool's state.

Failure 2 — BDD has no refinement

A Gherkin .feature file has no vocabulary for inter-file relations. There is no keyword for this Feature derives from that Feature. There is no keyword for this Feature is a more specific version of that Feature. The refinement graph — the chain from a stakeholder requirement down through system requirements to software requirements — has no carrier in Gherkin.

Teams who run BDD at scale reach for the same workarounds Jira teams reach for: a folder convention (features/stakeholder/, features/system/, features/software/), a tag convention (@stakeholder, @derived-from-PROJ-42), or a naming convention (Feature: STK-01 Discoverable graph). All three are strings outside the language. All three break silently on rename. None of them is validated by any BDD runner — Cucumber, Behat, pytest-bdd, SpecFlow — because the runners are specified to operate on the flat Feature-Scenario shape and not on a refinement graph.

The DSL's @Refines(ParentRequirement) decorator is a class reference that the TypeScript compiler resolves. Rename ParentRequirement and the decorator site fails to compile. Introduce a cycle (@Refines(Child) on a class that is already in Child's parent chain) and the DAG check at build time flags the cycle. Neither check has a Gherkin equivalent.

The absence is structural. Gherkin's grammar is defined by the Gherkin specification, which allows a .feature file to contain exactly one Feature: keyword, followed by an optional narrative, followed by a list of Background: and Scenario: and Scenario Outline: blocks. The grammar is flat by design — Gherkin was built to describe one coherent feature at a time, with no built-in way to say this feature is part of that feature. Adding a refinement relation would require extending the grammar, which no Gherkin-compatible runner has done. Tag-based workarounds (@parent:authentication) exist in every BDD suite at scale, but they are always project-local conventions, never checked by the Gherkin parser, never visible to the runner, never part of any compliance gate. The team that writes the convention is the team that maintains the convention, and when that team turns over the convention decays.

A concrete example of the consequence: consider a team with 400 .feature files organised by folder convention — features/stakeholder/, features/system/, features/software/. The convention is documented in the README; new engineers are told put your feature in the stratum folder that matches the stakeholder pressure. Six months later, a feature that started as a software-level concern is elevated to a system-level concern because product marketing wants to sell it as a differentiator. The .feature file is moved from features/software/ to features/system/. No other reference in the codebase changes — Cucumber does not know about the folder structure, the runner loads .feature files recursively regardless of folder. The elevation is invisible to the test suite. The elevation is visible only to a reader of the folder tree, and only if that reader knows the convention.

The DSL equivalent — moving a Requirement class from a software-stratum parent to a system-stratum parent — is a single edit: change the @Refines(SoftwareParent) decorator to @Refines(SystemParent). The change is visible to every consumer of the refinement graph: the traceability report reshuffles, the coverage report re-allocates, the document render places the Requirement in a different section. The change is a typed edit, not a folder-move. This is the gap BDD's flat grammar leaves open and the DSL's typed refinement fills.

Failure 3 — ISO 29148 has no test binding

ISO/IEC/IEEE 29148:2018 §5.2.7 defines the fit criterion attribute: a statement of what it would take to accept a requirement as met. Examples in the standard are of the form the system shall respond to the query in under 200ms, measured under the load profile defined in SyRS-LP-01. The fit criterion is verifiable in principle; the standard is silent on how the verification is tool-bound.

This silence is deliberate. The 29148 standard is tool-agnostic; it defines what a requirement is, what its attributes are, and how requirements relate to each other. The verification and validation processes — how requirements are tested — are deferred to ISO/IEC/IEEE 15288 (systems life cycle) and ISO/IEC/IEEE 12207 (software life cycle), and even those standards define the processes abstractly, leaving the actual tool binding to the implementing organisation.

The @Verifies<T>(methodName) decorator is the DSL's tool-binding answer. A test class is bound to an AC method on a Feature class, and the binding is type-checked: the methodName string must be a key of T's abstract method set. Rename the AC method and every @Verifies that points at it fails to compile. The compliance gate walks the full REQ → FEAT → AC → Test chain and reports coverage per stratum.

None of this violates ISO 29148 — the standard permits any tool binding. But the standard does not prescribe one, and a team that is strictly 29148-aligned will discover that they have no standard-sanctioned answer to the question how does the SRS bind to the test suite? The DSL answers the question by construction. Other tools — Xray, Polarion, DOORS, Cameo — answer it with a plugin database. Both are valid. The 29148-only team has no answer.

The scope choice in 29148 is itself defensible. A standard that prescribes a particular tool binding will age badly; binding mechanisms change faster than requirements engineering principles. The standard draws its boundary at the conceptual layer — what a requirement is, what attributes it holds, what relations connect it to other requirements — and leaves the mechanism layer to implementers. This is the same scope choice that makes the standard survivable across tool generations; it is also the same scope choice that leaves each implementer to invent the binding. Most implementers invent a string-based convention (the test name contains the requirement ID), which is exactly the failure mode 29148 §5.2.7 warns against in its own verification method attribute. The DSL is one more implementation of the binding, with the property that the binding is type-checked rather than string-matched.

A concrete example of the consequence: a certified-industry team the author has reviewed had an SRS with 1,200 requirement entries, an IEC 62304 compliance file, and a Vitest suite of 3,400 test specs. The binding between SRS entries and test specs was a spreadsheet — Excel, with a macro that cross-referenced entries and test names by string matching. The spreadsheet was maintained by one person; when that person left, the spreadsheet drifted from reality within two sprints. The audit that followed found 230 SRS entries with stale test bindings, of which 90 were regulator-critical. Remediation took a quarter.

The DSL's answer: @Verifies<T>('methodName') is compile-time checked, not spreadsheet-maintained. No person owns a spreadsheet; the compiler owns the binding. The auditor reads the compliance report, which is regenerated on every build, which makes the report always fresh and always aligned with the code. This is not a feature of ISO 29148 — it is a tool-choice the team makes within 29148's tool-agnostic scope. A team that stays on spreadsheets stays in the 230-stale-bindings failure mode. A team that moves to the DSL's typed binding exits the failure mode permanently, at the cost of writing requirements in TypeScript.

Four migration paths

Readers of this chapter arrive from one of the four vocabularies. The shape of a migration to the DSL is different from each starting point, and the effort differs by an order of magnitude. The four paths in sequence:

Migrating from Jira

The starting artefacts are a list of Epics, a list of Stories (linked to Epics), a list of Sub-tasks or Xray test cases (linked to Stories), and a collection of test files whose describe strings contain Jira ticket keys. The target artefacts are a set of Requirement classes, a set of Feature classes with @Satisfies decorators, and a set of test classes with @Verifies<T> decorators.

The mechanical steps:

  1. For each Epic, create a Requirement class with the Epic's key as a comment and the Epic's summary as the title. The style defaults to DefaultStyle; override if the team has a specific rhetorical convention.
  2. For each Story linked to an Epic, create a Feature class with an @Satisfies(ParentRequirement) decorator pointing at the Requirement class created in step 1. The Story's summary becomes the title; the Story's description becomes the set of abstract AC method names.
  3. For each Sub-task (or bullet in the Story description, or Xray test case), create an abstract method on the Feature class with a method name derived from the Sub-task's summary. Use camelCase conversion — traceExplorerHandlesArrowKeyNavigation(), not handles arrow-key navigation.
  4. For each test file whose describe string contains a Jira ticket key, convert the describe block to a test class decorated with @Verifies<T>('acMethodName'). The class-based surface is required (@FeatureTest, @Verifies); describe/it do not survive the migration.
  5. Run npx requirements compliance --strict. Expect red on the first pass; each red line points at a file and a line and a minimal edit. Chapter 13c is the recovery guide.

The effort scales linearly with ticket count. A hundred-ticket project takes a week of consistent effort by one engineer. A thousand-ticket project takes a team quarter. The effort is dominated not by the code but by the curation — deciding which Jira artefacts are still live and worth porting, which are zombie tickets that should be closed, which are duplicates that should be merged. The curation is valuable independent of the DSL migration; many teams report that the cleanup alone paid for the effort.

The loss during migration is workflow. Jira's sprint planning, Kanban boards, velocity charts, burndown reports — none of these are carried by the DSL. The recommended split keeps Jira running for workflow and the DSL running for specification, with a lightweight cross-link (Jira key as a comment on the Requirement class). Teams who try to retire Jira entirely discover that the product manager, the scrum master, and the stakeholder dashboards all depend on Jira features the DSL does not provide.

Migrating from SysML

The starting artefact is a SysML model file (XMI, or the tool-specific binary). The target artefacts are the same as for the Jira migration, plus one export-direction decision.

The mechanical steps:

  1. For each SysML Requirement block, create a Requirement class. The id attribute maps directly; the text attribute maps to the title plus style-rendered body. The derived attribute (SysML's flag for whether a requirement is derived from another) does not need to be preserved — the @Refines chain is the carrier.
  2. For each «deriveReqt» arrow, add an @Refines(ParentRequirement) decorator. The SysML DAG-ness of the «deriveReqt» network transfers directly; the DSL's build-time DAG check will flag any cycles that the SysML tool silently allowed.
  3. For each Block with a «satisfy» arrow to a Requirement, create a Feature class with @Satisfies(...) decorators pointing at every Requirement block the SysML Block satisfies. Many-to-many preserves directly — the DSL supports the full SysML relation cardinality.
  4. For each Test Case with a «verify» arrow, create a test class with @Verifies<T>('acMethodName') pointing at the AC method on the corresponding Feature class.
  5. Decide the export direction. If the SysML model is the authoritative document for an external audience (a safety auditor, a contract deliverable), export the DSL's edges as a SysML relation list on each build. If the DSL is the authoritative document, drop the SysML model.

The effort is lower than the Jira migration because the source is already structurally close. A SysML model with two hundred Requirement blocks, fifty Blocks, and three hundred «verify» arrows ports in one to two engineer-weeks. The loss is SysML diagrams — the visual shape is gone. Teams who need the diagrams keep the SysML model as a render target, not a source of truth.

Migrating from ISO 29148

The starting artefacts are a StRS document, a SyRS document, and an SRS document — usually Word or RST or Markdown. The target artefacts are the same as for the other migrations.

The mechanical steps:

  1. For each StRS entry, create a Requirement class at the root of the refinement tree (no @Refines decorator). The entry's ID becomes the class ID. The entry's text becomes the title and the style-rendered body. DefaultStyle is 29148-aligned by construction, so the rendered output should match the original document entry closely.
  2. For each SyRS entry, create a Requirement class with @Refines(StRSParent) pointing at the StRS entry the SyRS entry derives from. The derivation relation must be explicit in the source document — 29148 §5.3 strongly recommends but does not strictly mandate it; teams whose SyRS does not explicitly carry derivation must reconstruct it during migration.
  3. For each SRS entry, create a Requirement class with @Refines(SyRSParent), or — if the SRS entry is at method-granularity — an AC method on a Feature class that satisfies the SyRS requirement. The choice between REQ class and AC method is a judgement call: narrow, testable, method-sized statements are ACs; broader policies are Requirement classes.
  4. For each fit criterion in any document, wire it to a test class with @Verifies<T>('...'). This is the step that closes the 29148 test-binding gap — the fit criterion becomes the body of a test method.

The effort depends on document discipline. A well-maintained 29148 compliance suite (typical of regulated industries — medical devices, automotive safety, aerospace) ports cleanly; the structure is already there, and the migration is mostly mechanical transcription. An aspirational 29148 suite (typical of teams who claim 29148 compliance but have not rigorously maintained the StRS/SyRS/SRS layering) requires reconstruction work before transcription, and the reconstruction is often the majority of the effort.

Migrating from BDD

The starting artefacts are a collection of .feature files and their step-definition files. The target artefacts are the same as for the other migrations.

The mechanical steps:

  1. For each .feature file, create a Feature class with @Satisfies(...) pointing at the Requirement class or classes the feature serves. The @Satisfies target is not recoverable from the .feature file alone — it must come from the team's folder convention, tag convention, or README. This is the step where BDD's missing refinement vocabulary shows.
  2. For each Scenario in the .feature file, decide whether the Scenario maps to a new AC method or an existing one. The guideline: if the Scenario tests a property that does not yet have an AC, create a new AC method. If the Scenario tests a property that an existing AC already names, add the Scenario as an additional test case for that AC.
  3. For each step-definition file, convert the step definitions into methods on test classes decorated with @Verifies<T>('acMethodName'). Gherkin narration can be preserved inside the method body as a code comment; the assertion is the Then step's implementation.
  4. Decide whether to keep the .feature files as a stakeholder-facing narrative layer. Teams whose stakeholders read .feature files keep them; teams whose stakeholders read nothing retire them. The DSL does not require either choice.

The effort is the hardest to estimate of the four migrations. A BDD suite with clean Scenario-to-predicate alignment ports quickly; a BDD suite whose Scenarios have drifted into imperative traces that do not cleanly name a property requires scenario refactoring before the port. The refactoring is pure engineering cost with no deliverable value until the port completes, which makes it the migration path teams delay the longest.

A note on hybrid usage

None of the three failures forces a team to abandon their existing vocabulary to adopt the DSL. The DSL is designed to run alongside a Jira instance, a SysML model, a 29148-compliant SRS, or a Gherkin suite. The common pattern is:

  • Keep Jira for workflow (sprint planning, velocity, stakeholder visibility, dependency tracking). Let the DSL carry the typed refinement and satisfaction graphs. Reference Jira ticket keys in Requirement class comments as an optional cross-link — the DSL does not enforce this, and it costs nothing if the Jira tickets are renamed.
  • Keep SysML for diagrams. Let the DSL carry the typed code-level mapping. Export the DSL's @Satisfies, @Refines, @Verifies edges as a SysML relation list if the team needs SysML-shaped diagrams for an external audience — this is a one-directional export, not a sync.
  • Keep 29148 as the document shape. Let DefaultStyle render the Requirement prose in 29148-aligned form. The requirements render command can produce a document that a compliance auditor can read as a §5.2.4-compliant SRS.
  • Keep Gherkin as the test body language. Let @Verifies<T>('...') carry the binding. Teams who love Gherkin can write Given-When-Then inside the test class method; teams who prefer plain assertions can do that.

The DSL does not replace these vocabularies. It closes the three gaps they leave open: typed cross-references (Jira's gap), typed refinement (BDD's gap), and typed test binding (29148's gap). SysML closes two of the three by default (refinement and test binding); the DSL adds the typed code-level execution context SysML does not prescribe.

Anti-patterns: the mappings to refuse

Four migration paths, four common mis-mappings. Each is worth naming because each produces a broken suite that compiles, runs, and misleads.

Anti-pattern 1 — Jira ticket key as Requirement ID

A team porting from Jira is tempted to use the Jira ticket key as the Requirement class ID: PROJ_42Requirement, PROJ_99Requirement. The temptation is that the cross-link to Jira is then automatic; one can grep for the Jira key across code and tickets alike. The cost is that the Requirement IDs now carry Jira's lifecycle. A ticket split, a project rename, a migration — any of the five lifecycle events listed under Failure 1 — invalidates the class name. Worse, the class name is now opaque to a reader who does not have Jira open: PROJ_42 names nothing.

The correct pattern: the Requirement class ID is a semantic name (DiscoverableTraceabilityRequirement, DogFoodRequirement) and the Jira key is a comment or a decorator argument. The DSL can carry optional Jira cross-links via a planned @JiraLink('PROJ-42') decorator without tying the class identity to the ticket's identity.

Anti-pattern 2 — Scenario as AC

Already discussed in the BDD section; repeated here for completeness. A team migrating from BDD promotes Gherkin Scenarios to DSL ACs one-for-one. Every Scenario becomes an abstract method; every step-definition file becomes a test class. The migration compiles. The compliance gate reports 100% coverage. The specification is broken.

The breakage is silent because every AC has exactly one test (the Scenario that inspired it), which means every AC is under-specified by construction. The predicate each AC names is only the trace the Scenario records; any other trace the same predicate should hold over is not exercised. The suite grows over time to include more Scenarios, and each new Scenario requires a new AC (because the one-to-one mapping is frozen), which inflates the AC count and dilutes the predicate-level semantics.

The correct pattern: an AC names a predicate; a Scenario is one test of that predicate; multiple Scenarios can share an AC. The migration flattens the Scenario list first (grouping traces by the predicate they test) and then creates one AC per predicate group, not one AC per trace.

Anti-pattern 3 — SysML as the authoritative source

A team with a mature SysML model migrates to the DSL but keeps the SysML model as the source of truth: the DSL classes are generated from the SysML model on each build. The temptation is to preserve the investment in SysML and to keep the existing diagram-editing workflow. The cost is that the DSL's compile-time checks degrade from enforced at the moment the developer edits to enforced at the moment the generator runs. A developer who edits a test class and breaks a @Verifies binding discovers the break on the next generator pass, not on the next compilation.

The correct pattern: the DSL is authoritative; the SysML model is a render target, regenerated from the DSL on demand. The render direction is one-way: DSL → SysML, not SysML → DSL. Teams who want to keep editing SysML diagrams can do so for presentation, but the diagram changes must be replayed as DSL edits to take effect.

Anti-pattern 4 — 29148 document as the authoritative source

The mirror case. A team with an existing 29148 document set keeps the Word documents as the source of truth and uses the DSL as a generated test harness. The temptation is to preserve the investment in the document suite and to keep the existing document-review workflow. The cost is the same as the SysML case — checks degrade from edit-time to generator-time — plus the additional cost that Word documents are not version-controllable at the line granularity the DSL requires.

The correct pattern: the DSL is authoritative; the 29148 document is rendered on demand. The requirements render --style default --format 29148-srs command (the format tag is planned) produces a section that can be pasted into a compliance template. The Word document is a deliverable, not a source.

Anti-pattern 5 — using the matrix to choose a single winner

A reader who finishes the matrix and the per-vocabulary sections is sometimes tempted to declare a ranking: SysML is closest, therefore SysML wins. The ranking misreads the matrix. The matrix measures structural overlap between the DSL and each target vocabulary; it does not measure which vocabulary serves which audience best. SysML serves systems engineers and regulated-industry designers who need diagrams; Jira serves PMs and scrum masters who need workflow; ISO 29148 serves auditors and procurement specialists who need standardised documents; BDD serves stakeholders who need readable narrations of behaviour. Each audience is real; each audience is the customer of exactly one or two of the four vocabularies; none of them is the customer of the DSL directly (except the engineers who author it).

The DSL is not a replacement for the vocabulary the audience reads — it is a source from which that vocabulary can be rendered. The ranking question is therefore the wrong question. The right question is which audiences does my project serve, and which vocabularies do those audiences read? — and then the operational patterns of the previous section follow: one render pipeline per audience, one authoritative source (the DSL) for all of them.

The common shape of the four anti-patterns

All four share a pattern: the external vocabulary is preserved as authoritative, and the DSL is reduced to a sub-ordinate layer. In each case, the reduction defeats the DSL's core guarantee — compile-time cross-reference checking — because the cross-references are generated, not written. A generated reference cannot fail to compile in the developer's editor; it can only fail to generate on the next build, by which time the developer is three commits downstream.

The inversion — making the DSL authoritative and the external vocabulary a render target — is the pattern that preserves the guarantee. It requires accepting that the existing investment (Jira tickets, SysML diagrams, 29148 documents, Gherkin features) becomes a derived artefact. For some teams the investment is too large to treat as derived; for those teams, the DSL may not be the right tool, and Jira-with-Xray or SysML-with-Cameo or Polarion-with-DOORS may serve better. The DSL is not the universal answer; it is a particular answer shaped by a particular guarantee.

Running-example recap

Carry the running example through the four vocabularies one last time.

FEATURE-TRACE-EXPLORER-TUI, in each target vocabulary, reads approximately like this:

  • Jira. A Story PROJ-42: Trace explorer TUI, linked to Epic PROJ-11: Discoverable traceability, with labels label:REQ-DOG-FOOD, label:REQ-PARALLEL-DELIVERABLE, and ten Sub-tasks (or ten bullets in the description) for the ten ACs. Three Xray test cases PROJ-42-TC-01, -02, -03 link to the Story. Rename the Epic, rename the labels, rename the Sub-tasks — every cross-reference is a string, no validation.

  • SysML. A Block TraceExplorerTui with three «satisfy» arrows to Requirement blocks ReqDiscoverableTraceability, ReqDogFood, ReqParallelDeliverable. Ten requirement text entries for the ten ACs. Three Test Case blocks with «verify» arrows to the individual ACs. The diagram is clean, the semantics map cleanly, the code-level binding is not prescribed.

  • ISO 29148. A SyRS entry SyRS-01: Interactive TTY browser, derived from StRS entries StRS-01: Discoverable traceability, StRS-02: DSL self-testing, StRS-03: Parallel deliverable. Ten fit criteria (§5.2.7) for the ten ACs, each phrased in DefaultStyle's 29148-modal form. No prescribed test binding; the DSL closes the gap with @Verifies.

  • BDD. A .feature file trace-explorer.feature with header Feature: Interactive TTY browser and ten Scenarios for the ten ACs. The Scenarios are traces, not predicates. The refinement to the three stakeholder Requirements is not expressible in Gherkin — it lives in a folder convention or a tag convention or a README. The satisfaction relation to each of the three is similarly implicit.

  • DSL. The class from chapter 00, with @Satisfies(ReqDiscoverableTraceabilityRequirement, ReqDogFoodRequirement, ReqParallelDeliverableRequirement) on top, ten abstract AC methods, and ten test classes with @Verifies<T>('...') pointing at the AC method keys. Every relation is typed. Every rename is compiler-checked. The compliance gate walks the full chain in a single pass.

Five vocabularies, one feature, one set of facts. The DSL is the one that carries every relation in the type system. Each of the other four carries some of the relations, and each leaves a specific gap — a gap this chapter has named and a gap the next chapters in this series will exploit to argue for the DSL's particular choices.

Concrete side-by-side — the same AC in five vocabularies

To make the abstract mappings concrete, take a single AC from the running example — traceExplorerHandlesArrowKeyNavigation() — and write it out in each of the five vocabularies.

DSL

// requirements/features/feature-trace-explorer-tui.ts
abstract traceExplorerHandlesArrowKeyNavigation(): ACResult;

// test/unit/trace-explorer-navigation.spec.ts
@FeatureTest(FeatureTraceExplorerTuiFeature)
export class TraceExplorerNavigationTest {
  @Verifies<FeatureTraceExplorerTuiFeature>('traceExplorerHandlesArrowKeyNavigation')
  arrowDownMovesCursorToNextNode(): ACResult {
    // Given a graph with three nodes
    // When the user presses arrow-down
    // Then the cursor moves to the next node
    // ... assertion body ...
  }
}

The AC name is a key of the Feature class. The @Verifies decorator is type-checked: misspelling the name fails to compile. The test class is the executable carrier; the decorator is the binding.

Jira

Story: PROJ-42 — Trace explorer TUI
  Epic Link: PROJ-11 (Discoverable traceability)

  Description:
    As a user browsing the traceability graph,
    I want to navigate with arrow keys,
    so that I can explore without prior vocabulary.

  Acceptance Criteria (bullet list):
    - Build the graph on start
    - Handle arrow-key navigation    <-- the AC
    - Drill down from any node
    - ...

  Linked test (Xray): PROJ-42-TC-02 (Arrow key navigation)

The AC is a bullet point. Its name is a free-form string. The cross-link to the test is a plugin-maintained foreign-key pair. Rename the Story, rename the Epic, rename the Xray project — every reference breaks silently.

SysML

«requirement» Req_DiscoverableTraceability
  id = "REQ-DISCOVERABLE-TRACEABILITY"
  text = "a user browsing the graph must be able to discover relations..."

«block» TraceExplorerTui
  «satisfy» --> Req_DiscoverableTraceability

«requirement» Req_ArrowKeyNavigation    <-- the AC, as a sub-requirement
  id = "AC-TRACE-EXPLORER-ARROW-NAV"
  text = "arrow keys must move the cursor between nodes"
  <<deriveReqt>> --> Req_DiscoverableTraceability

«test case» TestArrowKeyNavigation
  «verify» --> Req_ArrowKeyNavigation

The AC is a sub-requirement block. Its relation to the parent is «deriveReqt». Its verification is a Test Case with «verify». Clean mapping; the verbs align.

ISO 29148

SRS-TRACE-EXPLORER-02 — Arrow Key Navigation
  Source:    StRS-01 (Discoverable traceability), via SyRS-TRACE-EXPLORER-01
  Priority:  Should
  Rationale: Users exploring the graph expect keyboard navigation.
  Fit criterion:
    The system shall move the cursor to the adjacent node in the
    direction of the pressed arrow key (up, down, left, right) within
    100 ms of key press, on every navigable node of the graph.
  Verification method: Test (see test-plan section 4.3)

The AC is an SRS entry. Its lineage via StRS/SyRS is explicit. Its fit criterion is the verifiable statement. The verification method is named but not bound; the binding is in a separate test-plan document.

BDD

# features/trace-explorer/navigation.feature
Feature: Interactive TTY browser for the traceability graph

  Scenario: Arrow-down moves the cursor to the next node
    Given the graph contains three requirements
    And the cursor is on the first requirement
    When the user presses the down arrow key
    Then the cursor moves to the second requirement

  Scenario: Arrow-up moves the cursor to the previous node
    Given the graph contains three requirements
    And the cursor is on the third requirement
    When the user presses the up arrow key
    Then the cursor moves to the second requirement

  # ... more scenarios for left, right, edges, non-navigable nodes ...

The AC the user can navigate the graph with arrow keys is not directly named; it is instantiated by several Scenarios. A reader who wants to know which predicate is being tested has to read every Scenario and infer. The inference is usually correct but never checked.

Side-by-side observation

The same AC, written out in five ways. The word count is roughly equal; the ceremony is roughly equal; the cognitive load is roughly equal. The structural difference is not in the prose — it is in what the tooling can check:

  • DSL: compiler checks the binding, DAG check catches cycles, compliance gate enumerates coverage.
  • Jira: nothing checks the binding; plugins can if configured; rename-safety is manual.
  • SysML: the modelling tool can run consistency checks; most do; none check against running code.
  • ISO 29148: nothing; the standard defers the binding.
  • BDD: Cucumber runs the Scenarios and reports pass/fail; the binding between Scenario and predicate is not checked.

This is the chapter's central point repeated in a different shape. The vocabulary is not what distinguishes the tools; the checkable binding is.

Historical note on the four vocabularies

Each of the four external vocabularies landed in the industry from a different decade and a different intellectual tradition. A short chronology, because the origin constrains the current capability:

  • ISO 29148 has the longest pedigree. Its content descends from IEEE 830-1998 (Recommended Practice for Software Requirements Specifications), which itself descends from the 1970s work on structured requirements engineering by Alford, Ross, and others. The 2011 and 2018 revisions harmonised the IEEE and ISO lines and absorbed the systems-engineering perspective from IEEE 1233. The standard's conservatism — its silence on test binding, its open attribute set, its tool-agnosticism — reflects its multi-decade scope. Standards that bind tightly to a particular tool age into obsolescence; standards that bind loosely age into relevance.

  • SysML emerged in 2006 as a profile of UML, sponsored by the OMG and designed for systems (not just software) engineering. Its relation set — «satisfy», «deriveReqt», «verify», «refine», «trace», «copy» — was chosen explicitly to close the requirements-engineering loop that UML's software-only focus had not prioritised. The 2017 SysML 1.5 and the 2023 SysML 2.0 revisions refined the semantics but kept the core relations stable; a reader of the 2006 spec and a reader of the 2023 spec will use the same three verbs the DSL maps onto.

  • BDD dates from 2006 — the same year as SysML, from a different tradition. Dan North's Introducing BDD article named the approach; Cucumber (2008) provided the first widely-used runner; Gherkin crystallised as the .feature file language around the same time. BDD's grammatical commitments — the Feature-Scenario-Step hierarchy, the Given-When-Then narration — were chosen to make tests readable by non-engineers, and that choice has remained stable. The absence of a refinement vocabulary is original to the design and is not a gap the community has moved to close.

  • Jira shipped in 2002 as a bug tracker with a ticket-type concept. The Epic/Story/Sub-task hierarchy was added over several years — Epic Link became a first-class field around 2012 — and Xray / Zephyr / Structure emerged as third-party plugins to close the gaps the core product did not fill. The pattern of core product stays minimal, plugins close the gaps is Atlassian's product philosophy, and it is why the out-of-the-box vocabulary is amber rather than green on the matrix: the core is intentionally thin.

Four decades, four traditions, four different answers to the same question. None of them is wrong; each is optimal for the constraints it accepted. The DSL is a fifth answer from a fifth constraint set — the specification lives in the code and is type-checked by the compiler the engineers already run. The constraint excludes the Jira reader (who needs workflow), the SysML reader (who needs diagrams), the 29148 reader (who needs tool-agnosticism), and the BDD reader (who needs stakeholder-readable traces). It includes the small-to-medium TypeScript team who runs tsc --strict on every build and wants the specification to live in the same strictness budget as the code. That is a narrow include and a wide exclude. The DSL is not a replacement for any of the four; it is a neighbour, with its own cost-benefit trade and its own set of readers for whom it is the cleanest answer.

Running-example recap, one more time — the DSL side in full

The five-vocabulary recap above summarised the DSL side in one paragraph. For the reader who wants the full DSL code on this chapter's page without cross-clicking, the FEATURE-TRACE-EXPLORER-TUI class in its entirety:

import { Feature, Priority, Satisfies, type ACResult } from '../../src';
import { ReqDiscoverableTraceabilityRequirement } from '../requirements/req-discoverable-traceability';
import { ReqDogFoodRequirement } from '../requirements/req-dog-food';
import { ReqParallelDeliverableRequirement } from '../requirements/req-parallel-deliverable';

@Satisfies(
  ReqDiscoverableTraceabilityRequirement,
  ReqDogFoodRequirement,
  ReqParallelDeliverableRequirement,
)
export abstract class FeatureTraceExplorerTuiFeature extends Feature {
  readonly id = 'FEATURE-TRACE-EXPLORER-TUI';
  readonly title = '`requirements explore` — interactive TTY browser over the traceability graph';
  readonly priority = Priority.Low;
  readonly enabled = false;

  abstract traceExplorerBuildsGraph(): ACResult;
  abstract traceExplorerHandlesArrowKeyNavigation(): ACResult;
  abstract traceExplorerDrillsDownFromAnyNode(): ACResult;
  abstract traceExplorerOpensHelpOverlayOnQuestionMark(): ACResult;
  abstract traceExplorerJumpsBackUpWithBackspace(): ACResult;

  abstract traceExplorerRefusesToStartOnNonTty(): ACResult;
  abstract traceExplorerExitsCleanlyOnCtrlC(): ACResult;

  abstract traceExplorerUsesFileSystemPortForDiscovery(): ACResult;
  abstract traceExplorerUsesPromptPortForInteraction(): ACResult;

  abstract endToEndNavigatesReqToFeatToAcToTest(): ACResult;
}

Every element of the class has appeared in one or more of the four target vocabularies in this chapter:

  • @Satisfies(...) — SysML «satisfy», Jira Epic Link / label, ISO 29148 allocation, BDD absent.
  • extends Feature — SysML Block, Jira Story, ISO 29148 SyRS entry, BDD Feature: keyword.
  • readonly id — SysML Requirement block id, Jira ticket key, ISO 29148 identifier attribute, BDD absent (Gherkin has no stable identifier field).
  • readonly title — SysML text, Jira summary, ISO 29148 title attribute, BDD Feature: line narrative.
  • readonly priority — SysML Requirement block priority, Jira priority field, ISO 29148 priority attribute, BDD absent (can be carried in a tag).
  • abstract ... (): ACResult methods — SysML requirement text entries or «verify» targets, Jira Sub-task or bullet, ISO 29148 fit criterion, BDD Scenario (lossy, as discussed).

Ten abstract methods, each an AC predicate. Each is verified by one or more test classes elsewhere in the codebase; each test class carries @Verifies<FeatureTraceExplorerTuiFeature>('traceExplorerHandlesArrowKeyNavigation') or similar, and the TypeScript compiler checks that the string argument is a key of the Feature class's abstract method set. A rename of any method name breaks every test that points at it, at compile time, before the scanner runs, before the gate runs.

The five-vocabulary story is one frame of the same class, viewed through five different grammars. The DSL frame is the one where every relation is typed, every rename is checked, and every compliance report is walkable in a single pass. The other four frames each carry some of the relations; none carries all of them.

Frequently asked questions from the four vocabulary camps

Four years of running this mapping in team reviews produces a small set of questions each vocabulary camp tends to raise on first exposure. The answers are short and direct; the long versions live in the respective chapter sections above.

From all four camps — a shared question

Q: What happens to the existing ticket IDs, block IDs, SRS identifiers, or Scenario names during migration? They remain as optional documentation on the new Requirement and Feature classes — a code comment, a decorator argument, a field in an auxiliary metadata object. None of them becomes a class identifier (see anti-pattern 1). The DSL's class IDs are semantic; the external identifiers are cross-links. This preserves the investment in the existing identifiers without letting them drive the DSL's structure.

Q: What happens if we rename a Requirement class after the migration? Every usage site in code fails to compile until the rename is complete. TypeScript's rename refactor, triggered from any IDE with a TypeScript server, handles the propagation. The external cross-link (Jira key, SysML id, SRS identifier, Scenario name) becomes stale and must be updated by the engineer; there is no automatic sync. This is the same stale-comment problem that exists for any code comment; the DSL does not make it worse, but it does not solve it either.

From the Jira camp

Q: Can I just keep my Jira instance and not migrate? Yes. The DSL runs alongside Jira without requiring integration. The pragmatic split (workflow in Jira, specification in the DSL) is the common pattern, and the only loss is the optional cross-link which a one-line comment on each Requirement class handles.

Q: What about the stakeholders who live in Jira dashboards? Keep the dashboards. Add a Confluence page or equivalent that renders the DSL's trace output — a traceability matrix, a coverage summary — on a schedule. Stakeholders who want the overview read the rendered page; engineers who want the detail read the code.

Q: Can I import my existing Jira tickets into Requirement classes automatically? A one-shot import is feasible via Jira's REST API and some glue code. A continuous sync is not recommended — the two sides have different lifecycles and different authoritative relations, and sync tooling tends to accumulate failure modes the first time a ticket is split or an Epic is archived.

From the SysML camp

Q: Can I generate my SysML model from the DSL? The render direction is planned but not yet implemented; the DSL's registry has enough edge information to emit a SysML XMI file. The 2.0 textual syntax is a cleaner target than the XMI for version control; a future DSL release will likely ship a SysML 2.0 renderer.

Q: What about the SysML relations the DSL does not cover — «copy», «trace», «refine»? «copy» is not carried; the DSL has no concept of a duplicated requirement. «trace» is a weaker form of «deriveReqt» that the DSL collapses into @Refines. «refine» (SysML's own relation, different from the DSL's @Refines) is a relation between specifications at different abstraction levels and maps onto @Refines chains in practice.

Q: Can I keep editing SysML diagrams? For presentation, yes. For authoring, no — the DSL is authoritative, and changes that originate in the SysML tool must be replayed as DSL edits.

From the ISO 29148 camp

Q: Is the DSL 29148-compliant? DefaultStyle produces output structurally identical to a §5.2.4-compliant requirement entry. The @Refines chain aligns with the §5.3 stratum relations. The @Verifies decorator closes the verification-binding gap §5.2.7 leaves open. The DSL is compatible with 29148; the standard does not define a compliance certification to claim.

Q: What about the attributes the DSL does not carry — source, stakeholder, rationale as separate fields? The current DSL collapses rationale into the style-rendered body. A future version may add optional @Source, @Stakeholder, @Rationale decorators for teams who need to surface these as separate fields. The minimal current set is designed for the teams who do not; the decorator surface is extensible without breaking change.

Q: Can the DSL render a §5.2-compliant SRS document? The requirements render command produces the requirements section. A future --format 29148-srs flag will wrap it in the standard's document shell. Teams who need it today paste the rendered output into a template.

From the BDD camp

Q: Can I keep writing Gherkin? Yes, as test bodies. The @Verifies<T>('...') decorator on a test class is the binding; the body of the test method can be Gherkin narration followed by assertions, or plain assertions. The DSL does not forbid either.

Q: What if my stakeholders read .feature files? Keep them as a stakeholder-facing narrative layer, regenerated from the DSL on each build. The regeneration is one-directional: DSL → .feature. Stakeholders read what engineers authored; engineers did not author .feature files.

Q: How do I port a Scenario outline (parameterised Scenarios)? The DSL's test class supports the same parameterisation pattern the underlying test runner offers — Vitest's test.each, Jest's it.each, etc. The @Verifies<T>('...') decorator is still on the class; the parameterisation lives inside the test method body. One AC, many parameter rows, one binding.

When NOT to adopt the DSL

A chapter of this length owes the reader a section on the negative case. Not every team should adopt the DSL; not every project benefits from typed requirements. The four profiles below describe teams for whom one of the four existing vocabularies is the better answer.

The systems-engineering-first organisation

Large systems-engineering organisations — aerospace primes, automotive OEMs, defence contractors — run on model-based systems engineering (MBSE) with SysML (or its SysML 2.0 successor, or one of the commercial derivatives like Cameo). The specification artefact is the model, not the code. Code is generated from the model, tests are generated from the model, documents are generated from the model. The model is edited by systems engineers who do not write TypeScript and do not want to.

For these teams, the DSL is a downgrade. The DSL's typed code-level surface is precisely what these teams do not want — they want the specification to be readable and editable by systems engineers, not by software engineers. SysML's tool chain serves them; adopting the DSL would force the specification into a language their systems engineers do not speak. The answer is: stay on SysML, keep the model authoritative, generate code and tests from the model.

The DSL may still be useful at the software stratum — below the SysML model, as the layer where software engineers implement the system requirements that the MBSE model has handed down. In that usage, the DSL is a leaf of the MBSE hierarchy, not its root. The software engineers use the DSL to give their tests compile-time bindings; the systems engineers see none of it.

The stakeholder-narrative-first team

Consumer-facing product teams often prioritise stakeholder narrative over engineering rigour in the specification layer. Product managers write user stories; designers write interaction narratives; QA engineers write BDD scenarios. The specification is consumed by non-engineers — product management, design review, user testing — and the consumption format must be readable without a code editor.

For these teams, BDD's .feature files in plain prose are the right surface. Adopting the DSL forces the specification into TypeScript, which non-engineers cannot read. The render-outward pattern (DSL → .feature) can mitigate, but it inverts the ownership: the PM no longer authors the specification, an engineer does, and the PM reviews a rendered artefact. Some teams accept the inversion and thrive; others find the PM's authorship is worth preserving, and for them BDD's direct-authorship pattern wins.

The certification-bound team

Medical device teams (IEC 62304), avionics teams (DO-178C), automotive safety teams (ISO 26262), nuclear teams (IEC 61508) run under certification regimes where the specification document is a deliverable — not a render target, not a generated artefact, but an author-edited, reviewed, approved, signed, versioned document with an audit trail. The certifying body reviews the document. The document's content is regulatory-defensible.

For these teams, a generated document is acceptable in principle but often operationally difficult — the certification process has grown up around author-edited documents, and the auditors know how to review those. A team that switches to DSL-rendered documents takes on the burden of proving to the auditor that the render pipeline is trustworthy (which requires tool qualification, which requires a separate compliance effort of its own). For mature certified teams with existing DOORS or Polarion installations, the migration cost rarely pencils; for new projects without an existing installation, the DSL's generate-from-code approach can be viable if paired with a tool-qualification effort.

The small team on a short horizon

Startups and small teams operating on a sub-year horizon often do not need typed requirements at all. A whiteboard, a shared document, a Slack thread is enough. The DSL's value compounds over months — rename safety, refactor support, compliance-gate coverage — and a team that ships in six weeks does not reap the compounding.

For these teams, any vocabulary works, including no vocabulary. The DSL is overkill; Jira tickets are overkill; a shared Google Doc is the right surface. The DSL becomes interesting at the transition point — usually somewhere between the twelfth and the twenty-fourth month, when the first renaming-refactor-breaks-everything incident happens and the team realises it needs typed specification. Adopting before the pain point is premature optimisation; adopting at the pain point is timely.

The common shape of the four negative profiles

All four profiles share a structural property: the specification's primary reader is not the TypeScript compiler. In the first, the reader is the SysML modelling tool; in the second, the product manager; in the third, the regulator; in the fourth, nobody (the specification does not need to be read by a sustained reader). The DSL is optimised for the case where the TypeScript compiler is the primary reader — it is what makes the compile-time checks pay off, and it is what makes the type system a specification enforcer. Teams where the compiler is not the primary reader get less value from the DSL and should stay on their existing vocabulary.

The section above is not a list of reasons to avoid the DSL. It is a list of conditions under which the DSL's particular cost-benefit shape does not apply. The DSL's target team — TypeScript-native, medium-sized, multi-year horizon, engineers own the specification — is one team shape among many. The other shapes have other tools that serve them better.

Operational patterns seen in the field

Beyond the four one-shot migration paths, several operational patterns have emerged across teams that have run the DSL alongside one of the four vocabularies for a non-trivial period. Each is worth naming because the long-run shape differs from the migration-day shape.

Pattern 1 — the Jira reconciliation script

Teams that keep Jira for workflow commonly write a small reconciliation script that runs on each build: it reads the DSL's registry, it reads the Jira instance via the REST API, and it produces a diff report. The diff names three things: Requirement classes whose Jira cross-link points at a closed ticket, Jira Epics that have no Requirement class counterpart (probably abandoned specs), and Jira Stories linked to Epics that have no Requirement class (probably orphan work). The diff report is read weekly by the PM and the tech lead; its output drives Jira cleanup, not DSL cleanup. The DSL is authoritative for specification; Jira is authoritative for workflow; the diff closes the gap between the two registries.

The script is typically 200 lines of TypeScript, uses the Jira REST API's /search endpoint with JQL, and caches aggressively because Jira API rate limits are tight. It is not shipped with the DSL package — the DSL stays instance-agnostic — but it is a straightforward piece of glue. Teams who do not write the script discover that the Jira-DSL coupling decays within a quarter; teams who write it early keep the coupling clean.

Pattern 2 — the SysML render cadence

Teams that need SysML diagrams for external audiences — typically for regulatory reviews or cross-team architecture boards — run the DSL-to-SysML render on a cadence matching the audience's demand. Weekly for active projects, monthly for maintenance projects, on-demand for audits. The render is a batch job; its output is a SysML 2.0 textual file or an XMI file, depending on the tool chain downstream. The render is never bidirectional.

The operational discipline: the SysML output is never edited directly. If a reviewer wants a change to the diagram, the change is filed as a ticket against the DSL codebase, the edit is made there, and the next render picks it up. This discipline is hard to maintain when the SysML tool offers a one-click edit; teams that fall off the discipline rediscover the anti-pattern 3 from earlier in this chapter (SysML as authoritative). Recovery requires re-establishing the DSL as authoritative, which in practice means throwing away the SysML tool's edits since the last good render.

Pattern 3 — the 29148 document build target

Teams in regulated industries often run a document build that produces a §5.2-compliant SRS as a PDF or Word document, on every release. The document is a deliverable to a regulator, an auditor, or a customer. The build pipeline: the DSL renders each Requirement class in DefaultStyle, the rendered output is inserted into a pandoc or docx-templater template that supplies the standard's cover page, scope, definitions, references, and trace-matrix sections. The generated document is reviewed by a technical writer for tone and completeness, but never edited for content.

The document is the audit artefact; the DSL is the source of truth. Auditors read the document; engineers read the DSL. The regulator is satisfied because the document complies with the standard; the engineers are satisfied because the document is generated and therefore not drifting from the code. This pattern is the strongest argument for DefaultStyle's 29148 alignment — the alignment is not aesthetic, it is operational, because it reduces the technical-writing edit cost on each release.

Pattern 4 — the BDD narration layer

Teams that keep BDD for stakeholder-facing narration run a similar render on a similar cadence: the DSL produces .feature files from the Feature classes and their AC method names, with Scenario stubs that the test engineer or the stakeholder fills in. The filled-in Scenarios are committed alongside the DSL code and are read by Cucumber or its TypeScript equivalent as integration tests. The DSL's @Verifies decorator is the binding to the test code; the .feature file is the narration that the stakeholder sees in review.

The discipline: the .feature file's Scenario names and their corresponding @Verifies keys must agree. The render preserves this agreement by construction; a team that edits the .feature file manually (adding a Scenario that does not correspond to any AC) breaks the agreement. Most teams run a small validator on each build to enforce the agreement — it is a dozen lines of script that loads both sides and reports mismatches. This is the anti-pattern 2 (Scenario as AC) prevented by tooling rather than discipline, which is the right place for the enforcement.

The common shape of the four operational patterns

Each pattern installs a render or reconciliation job that flows outward from the DSL to the external vocabulary. None of them flows inward. The outward direction is safe because the DSL is typed and the external vocabulary is more permissive; the inward direction is unsafe because the external vocabulary accepts what the DSL does not.

A team that runs all four patterns simultaneously (Jira reconciliation, SysML render, 29148 document build, BDD narration render) has installed the DSL as the hub of a four-spoke registry graph, with each spoke flowing outward and each external system consuming the DSL's output in its preferred shape. This is the target architecture for mature teams. Most teams get there incrementally — one spoke at a time, in whichever order matches their audit pressure — rather than in a single migration.

A short glossary — the five-way translation table

For readers who want a compact reference they can bookmark, one last shape: a flat translation table. Each row is a DSL concept, each column is a target vocabulary, each cell is the concept's name in that vocabulary (or absent if there is no carrier):

DSL Jira SysML ISO 29148 BDD
Requirement class Epic «requirement» block StRS / SyRS entry Feature: file header
Feature class Story «block» realising SyRS / SRS entry Feature: keyword
abstract AC method Sub-task / bullet requirement text fit criterion §5.2.7 Scenario (lossy)
@Refines decorator absent (label convention) «deriveReqt» derivation relation §5.3 absent (tag convention)
@Satisfies decorator Epic Link / label «satisfy» allocation relation absent (folder convention)
@Verifies decorator Xray link / test-name «verify» absent by design step definition body
readonly id field ticket key requirement id identifier attribute absent
readonly title field summary requirement text title attribute Feature: narrative
readonly priority priority field requirement priority priority attribute tag convention
Style selector absent absent modal verb §5.2.4 absent
compliance gate Xray / Zephyr audit model consistency check separate V&V process Cucumber run
registry (runtime) database model repository document set .feature file set

The table repeats the matrix diagram in flat form. Twelve rows, five columns, sixty cells. Count the absent cells and you get the shape of the DSL's particular contribution: it is the one vocabulary with no absent cells, because it was designed after the other four and could take the union of their structural commitments. The DSL is, in this sense, a late synthesis — not because its authors are cleverer, but because they had the earlier vocabularies to learn from.

Closing reflection — vocabulary is not structure

The chapter's central observation deserves a closing rephrasing. Every vocabulary in this chapter — Jira, SysML, ISO 29148, BDD, and the DSL — is adequate to name a Requirement, a Feature, an AC, a Test. The words are different, the shapes are different, the granularities are different, but the nouns are present in each.

What differs across the five is not the nouns. It is the relations between the nouns and, more precisely, the checkability of those relations. A vocabulary that checks its relations at compile time — the DSL — is a different kind of object than a vocabulary that checks its relations at plugin-run time (Jira+Xray), at model-validation time (SysML), at document-review time (29148), or at test-run time (BDD). The same facts are in all five; the confidence that the facts stay coherent under edit is only in the first.

This is why the chapter's red cells matter more than the green ones. Green cells are easy to celebrate — look, the DSL does what your tool does. Red cells are harder to celebrate — look, your tool cannot do this at all — but they are where the DSL's value concentrates. A team that does not need typed refinement, typed cross-references, or typed test binding has four good existing tools to choose from and should not migrate. A team that has silently been bitten by one of the three failures — a refactor that broke a Jira label link, a Gherkin suite that accumulated drift between Scenarios and the predicates they claimed to test, a 29148 document whose test traceability became a spreadsheet nobody updated — knows the cost of the red cells in operational terms, and for that team the DSL offers a specific, typed, compile-time-checked answer.

The answer is not universal. It is particular. It fits a particular team shape (small to medium, TypeScript-native, already running tsc --strict), a particular codebase shape (monorepo or lightly-distributed, version-controlled end-to-end), and a particular organisational shape (engineers who want to own the specification, not delegate it to a separate tool). For that team, in that codebase, in that organisation, the DSL's phrasing of the four vocabularies into one typed set of classes and three typed decorators is a clarifying consolidation. For other teams, other tools. None of the five is wrong. The chapter has named, as carefully as possible, where each one is and is not sufficient.

⬇ Download