An obligation is one falsifiable statement your model makes about runtime behaviour. You never write one: they are derived from what you have already declared, which is what makes the list complete rather than aspirational.
They are the denominator everything else is scored against.
What gets derived, and from what
| Obligation kind | Derived from | The statement it makes |
|---|---|---|
| Guard | a guard on a command | a caller failing this predicate is refused |
| Policy | a policy, inline or referenced | this operation is reachable only by callers the policy admits |
| Spec | a spec on a field | a value violating this constraint is rejected |
| Invariant | an invariant on an aggregate | this must hold after every mutation |
| Uniqueness | a unique index | two rows cannot share these values |
| Emission | a command's emits | performing this operation publishes this event |
| RouteContract | a route and its errors | this endpoint answers on this path, with these statuses |
| Isolation | isolation_axes on an entity | a caller on one axis value cannot read another's rows |
A real application produces more of these than people expect. The booking example in these docs — a services business with a catalog, a diary, reviews and payments — derives 278, most of them route contracts and emissions.
That number is useful on its own, before any test exists. It is the size of the testing problem your model has already committed you to.
Every obligation has an identity
This is the load-bearing detail.
An obligation carries a stable id derived from what it came from. When a test run reports its outcomes, assurance folds them in by that id — never by matching a test's name against a model element.
The alternative is worse than it sounds. Name matching appears to work, degrades silently as names drift, and the failure mode is an assurance report claiming coverage that does not exist. A report that is confidently wrong is worse than no report, because people stop checking.
Generating the cases
You can write the tests by hand, and their outcomes fold in like any others. But the framework will also generate one case per obligation, each carrying its obligation id, by declaring a testkit slot in your project manifest:
testkit go {
tiers [domain]
scope [booking, catalog, scheduling, payments, reviews]
}tiersnames the substrate.domainis hermetic: the application assembled with in-memory adapters and driven through the real handler chain, so caller resolution, tenancy, permission and policy middleware all run.scopenames which bounded contexts to generate cases for. Leaving one out is a decision, not an oversight — an append-only audit trail with no client-facing write surface would pad the suite without testing anything you claim.
Running that suite produces the results file; assure reads it.
Verified, unverified, unverifiable
Three states, and the third is the interesting one.
- Verified — a passing case discharged it.
- Unverified — nothing demonstrated it. This is the default and it is not an accusation; it is the starting point.
- Unverifiable in this run — a passing case exists, but the substrate it ran on cannot establish the claim. An isolation obligation cannot be discharged by in-memory repositories, because they have no row-level security to enforce.
That third state is what stops the report from lying in the most tempting direction. A hermetic run that counted isolation obligations as passed would report a security guarantee that had never been exercised — and it would do so on every project, by default, which is precisely how a control becomes theatre.
The report records the reason instead of the tick, and the isolation dimension stays capped and annotated until the run happens against a real deployed stack.
Waivers
Some obligations will not be discharged, and pretending otherwise helps nobody. A waiver accepts one explicitly:
vishwakarma assure --waivers waivers.yamlA waiver names the obligation by id, states a reason, and expires. The expiry is the whole design: a permanent exception is indistinguishable from a forgotten one, and a list of accepted risks nobody revisits is how an exception becomes a policy by accident.
Read next: The dimensions — how obligations roll up into what the report actually scores.