Open Standard · Working Draft
mekaniskt·kontrakt

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) = V2 is 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; deferred makes 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.