Worked examples · 04 of 07
School renovation
A municipality's school renovation under AB 04-family terms, exercising milestone payments, retention, delay penalties, weekly ÄTA changes, an independent inspector, a back-to-back subcontract, and multi-year index adjustment at once.
- SIZE
- very complex
- PROFILE
- mk-se-public
- STRESSES
- Every section of the spec at once: milestones, retention, vite, ÄTA change management, a back-to-back child contract, index math, and a third-party inspector.
A kommun renovates a school: totalentreprenad, AB 04-family terms, a milestone betalplan, materials arriving by despatch advice, ÄTA changes landing weekly, an independent besiktningsman deciding when done is done, 10% retention until approval, vite per started week of delay, a subcontractor back-to-back underneath, and an entreprenadindex adjusting prices across two years. This is the stress test where every section of the spec must show up for work at the same time. It mostly holds; three things creak.
Profile: mk-se-public. Prose backbone: AB 04-style allmänna bestämmelser as the soft-term layer (hashed bilaga); the executable layer below implements the administrativa föreskrifter.
The contract (abridged to the load-bearing blocks)
mechanical-contract v0
id: mk-se-2027-001900
title: Totalentreprenad ombyggnad Parkskolan
prose: kontrakt-parkskolan.pdf sha256:... # AB 04-family AF + kontrakt
annex: [ "AB 04 (soft layer)", "§X ansvar och försäkring", "force majeure" ]
parties:
beställare: org 212000-1355 "Lunds kommun" sign eIDAS-qualified-seal
entreprenör: org 556111-2233 "ByggBolaget AB" sign eIDAS-qualified-seal
besiktningsman: APPOINTED, not signed at genesis # the third-party
slot: accredited per art:rise-certified-inspectors # approver - a party bound
appointed-by: both, via inspector.appoint port # by later mutual event
money: currency SEK vat 25% rounding per-line half-up 0.01
state:
pool kontraktssumma capacity 24_000_000 SEK
stages available|reserved|retained|disbursed # 'retained' is not
# in §3.4's stage set
pool ata.budget capacity 2_400_000 SEK # pre-authorized ÄTA
# headroom (10%) as a POOL,
# not per-change transforms
register deliveries shape despatch-line
register milestones from betalplan
register ata-orders shape { scope: text, amount: money, approved-by: id }
fixings:
index e84 series SCB.entreprenadindex.E84 base 2027-01 signed-by scb-ingest
artifacts:
betalplan class contract-bilaga pin v1 # milestone rows -> event-dues (§3.4)
apd-plan class contract-bilaga pin v2 # soft: site plan, hashed
ue-contract mk-se-2027-001901 pin sha256:... # the back-to-back child
ports:
in material.delivery from entreprenör payload peppol.despatch-advice # BEAst-
# profiled in practice
in milestone.claim from entreprenör payload signed-claim
in ata.propose from entreprenör payload signed-scope+price
in ata.approve from beställare.projektledare payload signed-decision
in inspector.appoint from both payload signed-appointment # appointed slot
in besiktning.utlåtande from besiktningsman payload signed-inspection # slut/garanti
in invoice.submit from entreprenör payload peppol.bis-billing-3
in payment.confirm from beställare payload iso20022.pain.002
out receipt.emit to submitter always
out notice.emit to both on rule-alerts
approval:
milestone.claim: require besiktningsman utlåtande for slutmilstolpe,
else 1 of beställare.projektledare
ata.propose:
if amount <= 100_000 SEK and ata.budget remaining >= amount
-> require 1 of beställare.projektledare # an EVENT,
otherwise -> require transform (ändringsavtal) # not an amendment
# - until the pool
cadence: # or bounds run out
invoice.submit at-most 1 per month
due payment within 30 days of invoice accept
due färdigställande by 2028-08-01 # kontraktstiden
breach: vite 0.5% of kontraktssumma per started week, max 10% # computed from
# the log: (slutbesiktning
rule on milestone.claim: # godkänd date - due date)
if no matching milestone in betalplan -> reject "not in betalplan"
if milestone requires deliveries not in register -> reject "no delivery behind claim"
amount := milestone.amount indexed-by e84 at claim.period # index math, §2 style
reserve kontraktssumma amount * 90% -> due disburse within 30d # 90% flows
reserve kontraktssumma amount * 10% -> retained # 10% held
accept
rule on ata.approve:
if ref not in ata-orders proposed -> reject "unknown ÄTA"
draw ata.budget amount # headroom consumed, receipted
record ata-orders approved; extend milestones per payload # bounded, declared
accept
rule on besiktning.utlåtande:
which payload.result is
godkänd: # slutbesiktning approved:
release retained -> due disburse within 30 days # retention comes home
open garantitid 5 years # AB 04-family: 5y work,
godkänd-med-anmärkning: # 2y varor - the finally
due åtgärda anmärkningar within 60 days # ladder below
underkänd:
vite clock keeps running; due ny slutbesiktning
accept
escalation:
on obligation-breached | pool-exhausted | ata.budget exhausted:
level 1 projektmöte both resolve-within 10 working-days
level 2 styrgrupp 20 working-days, milestone.claim suspended
level 3 mediation per prose draws frozen, receipts keep flowing
finally:
garantiåtagande 5y arbete / 2y varor per prose # survival obligations:
garantibesiktning due within garantitid per prose # outlive termination
försäkring + ansvar per AB 04 soft layer
# ── the back-to-back (a second contract, referenced) ─────────────
# ue-contract mk-se-2027-001901: elentreprenad
# ports: in order.receive from contract mk-se-2027-001900 port ue.order (§7.1)
# terms: mirrors upstream milestones BY HASH REFERENCE - flow-down without
# hand-copying; its own vite, its own escalation
# saga: HE milestone underkänd -> UE rectification obligation opens; compensation
# legs declared per §7.3 - no distributed ACID, receipts embed foreign
# receipt hashes, the dispute is two chains laid side by side
What this example stresses
| Edge | Spec section |
|---|---|
| Milestones as event-dues from a betalplan artifact | §3.4 |
| Fork/join: slutbesiktning godkänd = one event releasing retention AND opening garantitid AND closing vite | §3.7 |
| Third-party approver (besiktningsman) with competence-register binding | §4.1, §8.5 |
| Change management at real-world cadence: ÄTA pool vs transforms | §6.4 |
| Back-to-back child contract, saga compensation | §7.1, §7.3 |
| Index math over multi-year validity | §2, §5 |
| Vite + retention computed purely from the log | §3.2, §3.4 |
| Soft-term layer carrying AB 04 where “skäligt” lives | §2 three tiers |
Questions this example forced
- ÄTA volume vs amendment ceremony. A real project produces tens to hundreds of ÄTA. §6.4 says amendments are signed transforms - correct for structure, absurd at ÄTA cadence (nobody re-signs the contract weekly). Proposed resolution, adopted in the example: anticipated change classes are declared as ports + pools at genesis (an
ata.propose/approveport pair drawing a boundedata.budgetpool with per-item approval thresholds); transforms are reserved for changes the contract did NOT anticipate (scope restructuring, party change, bounds exhausted). The boundary is crisp and lintable: within declared headroom = event; beyond = transform. This also mirrors legal reality - ÄTA rights ARE contract terms in AB 04, not amendments. - The appointed third party. The besiktningsman is neither party, signs no genesis, yet holds an approval slot that gates 2.4M SEK of retention. Proposed: a declared party slot - typed (competence register binding, §5.6), filled by a later mutual event (
inspector.appoint), replaceable the same way. Genesis binds the slot’s constraints; the appointment binds the person. Same pattern serves mediators (level 3) and the beneficiary discussion - the party model wants roles:signatory | appointed | beneficiary | executor. Needs a §-home in the spec. - Pool stages are not a closed set. §3.4 names
available|reserved|disbursed; retention needsretained, escrow wanted its own, and factoring will wantassigned. Proposed: stages are declared per pool (free names, declared legal transitions, linter checks conservation across every transition path) withavailable|reserved|disbursedas the profile default. Cheap generalization, but it touches the §3.4 vocabulary.
The verdict this example was built to deliver: complexity composes without new machinery. Milestones, retention, vite, ÄTA, back-to-back, index, inspection - every one lands on an existing primitive or a declared parameterization of one. The three creaks are all in the same place: the spec’s vocabularies (stages, party roles, change classes) are slightly too closed for the biggest contracts. Loosen by declaration, never by computation.