· 7 min read
The Derivation Ladder
Eighteen constructs, added to a use case one at a time, and exactly what each one turns into downstream. The map nobody prints, with the real numbers attached.
Ask what a use case is and you get a definition. Ask what a use case costs and nobody has a number.
That second question is the practical one. When you add a decision to a model, something happens downstream — more states, more requirements, more tests, possibly a probe somebody now has to build. Most teams find out how much at integration, which is the most expensive possible moment to find out.
So here is the ladder: eighteen constructs, added one at a time to a single small use case, with the real derived output at every rung. Nothing below is estimated. Each figure comes from running the transformation and reading what came out.
The whole map, on one page
| You write | You get |
|---|---|
action 'X' | a state, a requirement, a verification case, a test |
first A then B | a transition, a WHEN trigger, an assume stateBefore == clause |
out p : T | an evidence attribute and a require constraint |
use-case-level out + flow | flips that evidence to boundary-observable |
decide + if/else | guarded transitions; guards become assume clauses |
fork / join | all branches run; the machine serializes them and says so |
actor + a flow across it | an interface requirement and a controllable stimulus |
accept X via port | a waiting state, an item trigger, a port, a driver method |
accept after 30 [SI::s] | a time trigger, carried through verbatim; the test elapses it |
accept when a >= b | a change trigger on the transition |
| any other behavior | a completion trigger, named so it can be referenced |
send X via port to actor | a boundary crossing and an interface requirement |
#Exception + #ClosedWorld | a prohibition and a negative test |
#Terminal | a distinct useCaseFailed terminal state |
constraint def <T>Valid | sharp predicates instead of a bare presence check |
loop … until c | the exit bound, preserved as a constraint |
| a guard on something nothing produces | nothing — plus a loud diagnostic |
That last row is the one worth staring at. Everything else on this list produces something. One thing produces a complaint instead, and it is the row that matters most.
What it costs, rung by rung
What one more construct costs
Running totals as each construct is added to a single use case. Hover any step.
- Requirements
- Verification cases
- States
- Probes needed
Table view
| # | Construct added | Requirements | Verification cases | States | Probes needed |
|---|---|---|---|---|---|
| 0 | One action | 3 | 2 | 2 | 4 |
| 1 | A second action, in sequence | 4 | 3 | 3 | 5 |
| 2 | An output | 4 | 3 | 3 | 5 |
| 3 | A declared outcome | 4 | 3 | 3 | 4 |
| 4 | A supplied input | 5 | 4 | 3 | 4 |
| 5 | A decision | 6 | 5 | 4 | 5 |
| 6 | An actor at the boundary | 8 | 6 | 5 | 5 |
| 7 | An awaited event | 10 | 7 | 6 | 5 |
| 8 | A timeout and a change event | 12 | 9 | 8 | 6 |
| 9 | An error path | 13 | 10 | 8 | 6 |
| 10 | A failure shape, declared once | 13 | 10 | 8 | 6 |
| 11 | A dead end | 13 | 10 | 9 | 6 |
| 12 | What must hold before and after | 14 | 10 | 9 | 8 |
| 13 | What 'good data' means | 13 | 10 | 9 | 6 |
| 14 | Repetition | 13 | 10 | 9 | 6 |
| 15 | A held state — undeclared | 14 | 11 | 10 | 7 |
| 16 | …declared, and it resolves | 14 | 11 | 10 | 8 |
| 17 | Two things at once | 15 | 12 | 11 | 9 |
Two things in that chart are worth naming before anything else.
Requirements outrun states. By the top of the ladder there are 15 requirements over 11 states. Constructs like preconditions and acceptance criteria add obligations without adding behavior, so the two curves separate. Counting states and calling it scope will understate the work.
The probe line does not only go up. It falls at step 3 and again at step 13. Those are the two rungs where something became observable at the boundary — a declared outcome, and acceptance criteria on an item type. Every fact that crosses the boundary is a fact nobody has to build instrumentation to see. That is the whole economics of testability in one wiggle: modeling effort spent in the right place removes downstream engineering, and you can watch it happen.
One structural note, since the chart quietly depends on it. In this ladder, states and transitions are equal at every rung, and so are verification cases and tests. Plotting all four would draw two pairs of identical lines. The one-to-one between a verification case and a test is by construction — the generator emits one per obligation. The states-to-transitions equality is a property of this particular ladder, not a law; a fork or a rejoining error path will break it.
Rungs 0 to 4 — the parts everyone already writes
The bottom of the ladder is the vocabulary every use case already uses, and it is where the one-in-several-out ratio is established.
package Ladder {
use case <'UC-1'> 'Submit Order' {
subject app :> Arch::orderService {
perform 'Record Order';
}
action 'Record Order';
first start then 'Record Order';
first 'Record Order' then done;
}
}
One action. Downstream: two states (recordOrder, useCaseCompleted), four
evidence attributes, three requirements, a verification case, a test, and five
driver methods the harness will need. That ratio holds all the way up.
Add first A then B and the succession becomes a transition, a WHEN trigger in
the requirement prose, and an assume stateBefore == … clause. That last one is
the quiet payoff: the test now has to reach a starting state before it acts,
so ordering is proven rather than assumed.
Then out p : Boolean becomes an evidence attribute and a require constraint
— but an internal one, needing a probe. Promote it to a use-case-level output
with a flow and the same fact flips to boundary-observable, and the probe count
drops. Same information, different reachability, real money.
Rungs 5 to 8 — where the boundary appears
A decision produces guarded transitions, and the guards become assume clauses
so each branch’s test only claims to have exercised its own branch.
An actor at the boundary is the first construct that makes the use case drivable. Every event the use case waits for, and every action a human performs, is a place a harness can push. A use case with neither can only be started and watched.
Then triggers get specific:
accept OrderCommand via app.commandPort // an item
accept after 30 [SI::s] // the clock
accept when balance >= total // a condition becoming true
Every transition names what fires it. The timeout is carried through
verbatim, units and all — 30 [SI::s] arrives in the test as an elapse
instruction, not as a number somebody retyped. Anything without an explicit
trigger gets a completion trigger, named so it can still be referenced.
Rungs 9 to 12 — saying what must not happen
This is the part of the ladder that a use case cannot reach on its own.
A use case is existential: there is a path where this happens. A prohibition is universal: this never happens. No transformation derives the second from the first, so you have to draw the failure branch and then declare the model closed:
#Exception #Terminal
action 'Reject Order' { out rejectionRecorded : Boolean; }
Mark the branch #Exception, declare #ClosedWorld, and the prohibition
appears — with a negative test that exercises the failure deliberately rather
than never. Step 9 is the only rung on the whole ladder where the prohibition
count moves off zero.
Two refinements follow. The keywords are semantic metadata, not decoration:
#Exception action 'Reject' states that the action is an ExceptionBehavior,
which means you can say it once on a definition and have every usage inherit it.
And #Terminal routes the failure to a distinct useCaseFailed state rather
than folding it into useCaseCompleted — because “the use case stopped” and
“the use case succeeded” are different claims, and a test that cannot tell them
apart is not testing much.
Rungs 13 to 17 — the four things only you can say
The top of the ladder is where the model stops being able to guess.
What good data looks like. A use case types its data and never says what
makes an instance acceptable. Declare constraint def <T>Valid once per item
type and every parameter of that type gains real predicates. This is the rung
that turns notEmpty(x) into something worth checking — and note on the chart
that it is also where boundary-observability jumps from 3 to 6.
What the subject holds. Guard on a condition that persists across steps — a session is live, the store is open — and if you never declared it, the derivation produces nothing for that branch and says so loudly:
⚠︎ D17: Guard references a feature nothing in the model accounts for — most
likely a state the subject holds that was never declared. No condition was
derived for this branch.
The use case is in a state; the subject holds other states. A held state is
never inferred, because first A then B says nothing about which of A’s effects
survive into B. Declare it as one attribute with an initial value — nothing in a
flow says what the subject starts at — and the guard resolves.
That both branches ran. A decision runs one branch; a fork runs all of them. But a state machine is in one state at a time, so a fork has to be serialized into an order the use case never stated — and that gets a diagnostic too:
ⓘ D20: A fork was serialized. A state machine is in one state at a time, so
concurrent branches were put in a fixed order the use case did not state.
The requirement over the join stays order-free — WHEN Await Settlement and Notify Customer have completed — which is the honest statement. The serialized order is an artifact of the state machine, and the tool says which order it picked rather than letting you discover it later.
What the ladder is actually for
Read top to bottom, this is a costing table: it tells you what a construct buys and what it charges.
Read a different way, it is a checklist for a model you already have. Ten of these eighteen rungs are things most use cases in the wild simply do not have — no acceptance criteria, no declared outputs, no failure branches, no closed-world declaration. A model missing all of them still parses, still reviews well, and still yields requirements. It just yields requirements that no black-box test could ever settle.
The two loud diagnostics are the point of the whole exercise. Everything else on the ladder produces an artifact. Those two produce a complaint, and a complaint is the only output that can tell you something you did not already know.
More of this, on video
Walkthroughs in Cameo and SysML v2, with the model on screen.
