Specification · Section 6 of 13
Universal by design: one core, many profiles
Describes a jurisdiction-neutral semantic core constrained by CIUS-style profiles, the template/instance split for high-volume contracts, and versioning through signed declarative transforms.
6.1 The governance model is already proven - copy it
This exact problem - one semantic core, many jurisdictions - is how the e-invoice went international: EN 16931 defines a jurisdiction-neutral semantic core, and each country/domain publishes a CIUS (Core Invoice Usage Specification - a constraining profile; Peppol BIS Billing 3 is a CIUS, Sweden’s B2G mandate binds to it). Twenty years of EDIFACT before that taught the same lesson from the other side: one global document grammar, national subsets. The mechanical contract adopts the architecture wholesale:
mk-core the universal artifact - jurisdiction-free, sector-free
│ parties · roles · ports · pools · cadence · approval
│ escalation · artifacts · receipts · money/rounding/tax-scheme
│
├─ mk-se-public LOU gates (direktupphandling ceiling, ändrings-headroom),
│ OSL disclosure defaults, attestordning binding,
│ dröjsmålsränta per räntelagen, BankID/Freja identity
├─ mk-se-b2b two ABs shaking hands: no LOU, räntelagen defaults,
│ Bolagsverket identity, allmänna villkor templates
├─ mk-dk, mk-eu… later: NemKonto/MitID bindings, EUDI-native profile
- The core knows no kommun. Nothing in mk-core mentions LOU, ramavtal, attest, OSL, or öre. Core vocabulary is deliberately anglo-neutral (party, framework, pool, approval, escalation, disclosure); every profile carries the localized display terms - the glossary is literally the sv↔core mapping table, maintained since day one.
- Profiles CONSTRAIN, never extend computation. A profile adds bound terms (which eID counts as a signature, which statutory interest rate, which disclosure defaults, which tax scheme), tightens rules, and supplies templates. It cannot add computational power - a mk-se-public contract is always a valid mk-core contract. This is the CIUS discipline, and it is what keeps one evaluator serving every jurisdiction.
- The rails are already international. Peppol runs in Australia, Japan, Singapore, not just the EU; ISO 20022 is global; eIDAS has foreign equivalents the identity slot can bind to. The core rides rails that already crossed borders.
6.2 The two-person-AB test
The universality bar, concretely: two people with an AB and no lawyer must be able to sign a mechanical contract from a template in an afternoon - a consulting gig, a rental, a retainer with a monthly cadence and one approval rule. If the standard only works with a procurement department attached, it failed as a standard (and, commercially, it abandoned the long tail where bluffakturor actually kill companies). The design already carries the ingredients: a starved primitive set, template authoring from a corpus, coverage % (a tiny contract formalizes 100% trivially), and referee mode (nobody’s money moves by machine until they opt in). The same properties that make a kommun auditable make a two-person AB safe.
Multi-territory corporations come from the same split, not from extra machinery: each contract binds ONE governing law + profile (as contracts always have); what the corporation shares across territories is artifacts (§5.3 - one global rate card, many national contracts tracking it) and one evaluator. Sweden-first stays exactly right (the roadmap unmoved): Sweden is the pilot PROFILE, not the design ceiling - and building mk-se-public first, against the hardest disclosure regime (offentlighetsprincipen), means every later profile inherits the strictest privacy machinery rather than retrofitting it.
6.3 Template and instance: the million-customer shape
The split is class/object, done as signed documents:
- A template is a mechanical contract with unbound parameter slots - and it is a governed artifact (§5). Hashed, versioned, its updates under its own declared governance. An instance is a genesis event: (template @ hash, bound parameters, parties, signatures). The instance’s own text is tiny - a parameter tuple - which is the million-customer economics: ONE template artifact, a million cheap logs referencing it.
- The prior art is exact, twice over. Daml literally names its contracts
template, instantiated per agreement. And law got there first: the ISDA Master Agreement + per-trade confirmations - one negotiated framework, thousands of thin instantiating documents - is this architecture verbatim; standardavtal + orderbekräftelse is the everyday version; every webshop checkout already instantiates the köpvillkor - unread, unhashed, and litigated from screenshots later. Ramavtal → avrop (§7.1) is the procurement special case: instantiation PLUS a draw on the parent’s pools. - The instance pins the template hash at signing, forever. Template v7 shipping next month changes nothing for an instance signed under v6 - “we’ve updated our terms” becomes a per-instance amendment offer, never a silent rewrite. The negative-option / terms-creep fight (§2’s
active-renewal), solved structurally for consumers too. - Per-instance state stays small; the complexity is shared. An instance declares its little pools (items to deliver, the money) and its bindings; the heavy machinery - tax tables by goods category, rebate schedules, coupon registers, the product catalog - lives in SHARED artifacts (§5.3) referenced by hash. A million instances, one tax table. Coupon redemption is a pool draw on a shared coupon artifact (each code a capacity-1 pool - double-redemption is a §3.1 boundedness question, already decided). Tax calculation is not the contract’s job at all - that is a call-out (§7.5).
- A million logs is the boring case, not the scary one. Each instance is its own tiny single-writer serialization domain (§7.3), independent and embarrassingly parallel - §3.5’s execution model was built for exactly this shape.
6.4 Versioning and transforms: V2 = apply(V1, transform)
The transform is the right mechanism, and it should be THE amendment mechanism: a new version is never “here is the new document, trust us it differs how we said” - it is the old version plus a transform: a signed, declarative, hash-referenced recipe:
transform t-2028-007: # an artifact like any other (§5)
from mk-se-2027-000042 @ v2 sha256:...
ops:
set rates.senior 895 -> 910 SEK/h # the semantic diff IS the
extend validity +12 months # amendment - parties sign
split pool hours.senior 3000 h -> hours.senior 2000 h + hours.architect 1000 h
state-migration:
hours.senior.consumed -> hours.senior.consumed # accumulated state maps
(conservation: total capacity + total consumed preserved - the linter checks
that a transform cannot mint or vanish value; §8.3's linear types apply to
transforms too)
requires: signature of both parties # consent class (below)
- Deterministic and verifiable:
apply(V1, transform) = V2is a pure function; anyone holding V1’s hash and the transform’s hash recomputes V2’s hash. The version chain is receipt-chained like everything else -V2's genesis cites (hash V1, hash transform, adoption receipts). This is git’s insight (a commit IS a patch, content-addressed) applied to signed agreements: parties sign the delta - which is what lawyers already do; an ändringsavtal has always been a prose diff (“§7 erhåller följande nya lydelse…”), just never a verifiable one. - Transforms are declarative, from a closed verb set (
set / add / remove / rename / split / merge / extend…) - never programs. The codemod road (arbitrary migration scripts) is Turing-completeness sneaking back in through the version door; the starvation diet (§9) applies to change itself. - Replay is untouched because effective-dating already rules (§5.1): events evaluate against the version in force at their date. The transform marks a boundary event in the log; old events replay under V1, new under V2, and the state-migration block is the declared mapping of accumulators across the boundary - checked for conservation at lint time, receipted at adoption time.
- No down-migrations. Contracts do not roll back, they compensate (§7.3); an unwanted V2 is cured by a new forward transform, honestly labeled. (Database migrations’
down()is a fiction even in databases.) - Consent classes, answering “some have instances, some don’t” - same mechanism, three distribution modes:
| Change | Mechanism | Consent |
|---|---|---|
| Within-bounds artifact update (price list row under its declared bounds) | §5.2 governed update | none beyond the artifact’s own governance - already specified |
| Amendment of a standalone contract | transform, signed by the parties | explicit: signatures ON the transform |
| Template migration across N instances | transform authored ONCE at template level; each instance adopts by adherence - an offer + acceptance event, receipted per instance | per-instance consent; non-adopters stay on their pinned version, visibly |
The third row has industrial-strength legal prior art: the ISDA Protocol mechanism - one published amendment text, thousands of counterparties adhering by letter - is exactly this shape, and the IBOR-fallback protocol amended millions of live contracts with it. We give the adherence letter a hash and a receipt. Tech prior art: database migrations (Flyway/Liquibase/Rails: versioned, ordered, checksummed recipes), event-sourcing upcasters (versioning events without rewriting the log - our effective-dating is the stronger cousin), git (content-addressed patches), and Cambria/lenses (Ink & Switch; Foster-Pierce bidirectional transforms) for the schema-evolution theory - consumed as inspiration, constrained by the closed verb set.
Four sharpenings:
- The transform lives OUTSIDE the contract. A standalone artifact with its own envelope (id, from-hash, to-version, author, seal), distributed and governed separately (§5), loaded only at adoption time. The contract never carries its own migration history as text - the version chain of hashes carries it. Jump transforms (
from v1 to v11, the squashed migration) are legal and need no special machinery: the from-hash guard IS the once-only guarantee - a transform applies only to the exact version it names, so double-application and wrong-version application are structurally impossible (§3.5’s expectedVersion, at the version layer). Application is atomic: one boundary event, one adoption receipt, fully applied or not at all. - Transforms carry recipes AND initialization data. New pools get their opening balances, new artifact bindings their initial pins, new registers their seed rows - declared in the transform, applied at the boundary, receipted.
- Transforms are self-verifying - a Hoare triple with documentation.
{precondition} ops {postcondition}plus example tests that ride inside the artifact: given this sample V1 state, applying yields exactly this V2 state. The examples are executable documentation (the doctest/Rust-doc-test tradition: the docs ARE tests), and the evaluator runs preconditions + examples + conservation checks BEFORE adoption - a transform whose own tests fail is unapplicable, not “applied with warnings”. Design-by-contract (Meyer) applied to change itself. - Computed migrations use the same starved expression grammar as rules (the CEL-class discipline: terminating, side-effect-free, typed) -
new rate = old rate × 1.03, rounded per contract- and may call inline imports only (§7.6: hash-pinned pure functions;deferredmakes no sense at a version boundary). Strict linking applies with full force: an unresolvable import, a signature mismatch, a failed precondition, a failed example - any of these and the transform refuses: FAULTED adoption receipt (§4.3), instance stays on vX, nothing half-migrated. “OR FAILURE” is exactly the rule.