Concept 3
Capabilities
A capability is what an actor is permitted to do next, answered from the same predicates
that enforce it — never from a second copy.
In plain language
Interfaces need to know which buttons to disable. The usual approach is to write that logic
in the interface: if status is voided and the user is an admin, show Restore. The rule now
exists twice — once where it is enforced and once where it is displayed — and only one of
them is tested.
A capability query asks the domain instead: given this entry, in this state, for this actor,
what is legal right now? The interface renders the answer without knowing the rule.
Precisely
A capability projection is a read-only report of legality, computed by calling the same named
predicates the transition functions use as preconditions. The requirement it satisfies is not
"the two agree" but "there is only one".
In real code
Two agents built the same requirements independently, without seeing each other's work.
Both arrived at the same architecture for this: one parameterised validator, plus named
boolean predicates shared by the transitions and by the capability projection. Both
documented why in nearly the same words — never a second, independently maintained copy.
One of them proved it rather than asserting it. A mutation test deliberately broke the
shared predicate and confirmed that both the mutating action and the capability report broke
together. If they had not, the sharing would have been nominal.
Across a comparison of 12 product requirements, 20 business rules and 9 open questions, the
two builds differed on 1 of 41Requirements-scope divergences between two independent builds — and it was a scope question about what to
build, not a disagreement about a rule.
What an agent should do differently
-
Do not reimplement a rule in a view, a controller, or a client. Ask the domain what is
legal and render the answer.
-
When a capability report and an enforcement path disagree, the bug is that there are two of
them. Fix the duplication, not the discrepancy.
-
When you add a guard to a transition, check that the capability projection calls the same
predicate. If it re-states the condition, you have just created the drift.
How this is most often got wrong
Sharing in name only. A shared helper that each caller wraps in its own
extra condition. The predicate is shared; the rule is not. A mutation test — break the
predicate, confirm both callers break — is how you find out which one you have.
Permission and legality conflated. "This user may not do this" and "this
entry cannot be in that state" are different refusals and deserve different diagnostics.
Collapsing them produces an error message that cannot tell a caller which problem to fix.