Specification · Section 4 of 13
Approval and escalation
Covers how approval (the attest), escalation (throw/catch/finally), and fault handling are declared and enforced inside the contract rather than left to the surrounding systems.
4.1 Approval: the attest, made first-class
Swedish public sector already runs on this - every kommun has an attestordning (approval hierarchy, mandated by internal-control rules) and the tvåhandsprincipen (four-eyes). Today it lives in the ERP, disconnected from the contract. The mechanical contract pulls it INTO the agreement:
- Legs, not modes. A port’s rules decide validity; the approval block decides the leg: below 50k SEK -> straight-through (the STP leg - valid means done, immediately); mid-range -> one attestant; big -> four-eyes. The thresholds are contract terms - signed, diffable, litigable - not ERP configuration someone quietly changes.
- What approval MEANS is declared, not assumed. An approver is a slot bound to a policy: a named human role signing with eIDAS, or an agent - a signing key bound to a hashed, versioned policy document. “AI approval” is not hand-waving: it is a keypair whose decision policy is content-addressed in the contract, so which robot judgment was delegated, and under which policy version, is itself signed and auditable. Change the policy, that’s an amendment. (This is the robot-economy door from §3.3: at machine speed, both counterparties AND approvers are agents - same primitive, faster cadence.)
- Determinism survives because the machine never decides approvals - it waits, deterministically. An approval is just another signed event through the
approval.grantport; “pending approval” is a first-class state (visible in the picture: money sitting between CONSUMED and INVOICED-approved is the attest backlog, per attestant, for free). Replays reproduce exactly which approvals happened and when. - Referee mode vs endgame: today straight-through means “auto-recommend attest into the ERP” - the human rail stays. The day
payment.instructopens (the last-capability rule), the STP leg becomes literal immediate payout - and the approval primitive is precisely what makes switching that on safe: automation happens only on legs the contract explicitly declared straight-through, with everything else gated exactly as signed.
4.2 Escalation: throw / catch / finally
Real contracts already contain the catch-blocks - they’re called eskaleringstrappa / multi-tiered dispute resolution clauses (negotiate -> steering group -> mediation -> arbitration; NEC4’s early-warning-then-adjudication ladder is the famous one). And they already contain the finally-blocks - survival clauses (sekretess, warranties, return of materials: obligations that run no matter HOW the contract ends). What no one has done is give them execution semantics:
throw a rule or guard raises a condition it cannot resolve locally:
pool-exhausted, obligation-breached, N-times-rejected, approval-timeout
catch declared escalation LEVELS - each names who is summoned, what gets
suspended, and a resolve-within deadline; missing the deadline
throws to the next level (deadlines all the way up)
finally survival obligations that execute on ANY exit - expiry, termination,
level-4 collapse - tracked like every other obligation
Why this beats if/else spread through the rules: escalation is a cross-cutting concern. Pool exhaustion can arise in three different rules; without structured escalation each rule hand-rolls its own “what now”, and the contract’s crisis behavior is smeared across the document. With it, rules stay clean single-purpose guards, and the ENTIRE crisis ladder sits in one signed, reviewable block - which is exactly how the lawyers already drafted it in prose. The mapping to §15/§17 of the prose is 1:1; we are giving the eskaleringstrappa a runtime.
Mechanically, escalation levels are control states (the finite control of §3.1 - active ⇄ suspended was already there; levels refine it), entered and exited by events, with everything visible in the picture: a contract at level 2 renders as suspended port + who owns the clock + days left on the resolve-within deadline. Escalations are the off-track alarm made contractual.
Prior-art honesty: BPMN 2.0 literally has throw/catch escalation events and error/compensation events as first-class constructs - business-process people solved this pattern’s semantics; nobody attached it to a signed agreement. Tiered dispute-resolution clauses are standard legal drafting - all prose, zero runtime. The saga pattern (compensating transactions) is the distributed-systems cousin of finally. The composition into a contract runtime is, again, the novel part.
4.3 Faults: when the machinery fails, as a first-class mechanic
Escalation (§4.2) handles contractual crises - pool exhausted, obligation breached. A fault is categorically different: the machinery failed or the artifact was defective, so no judgment on the merits ever happened. The distinction is not cosmetic, it is load-bearing:
FAULTEDjoins the verdict enum (the envelope’s closed set grows once, deliberately:ACCEPTED / REJECTED / PENDING / ESCALATED / FAULTED). REJECTED means judged against the contract and failed; FAULTED means never judged.- Faults move nothing. A fault receipt always has
state-after == state-before, consumes no cadence counters (a supplier whose invoice FAULTED on a dead tax service has NOT used their one-invoice-per-month), and leaves the submission-id retryable - resubmission after a cleared fault gets a real judgment, not the fault receipt replayed. Receipts about the attempt, not the state. - Every fault names its at-fault domain - because consequences differ legally, not just operationally:
| Fault class | Examples | Attributed to | Default consequence |
|---|---|---|---|
| Input fault | schema-floor failure, invalid signature, unresolvable reference | submitter | FAULTED receipt citing the gate; submitter’s clocks keep running (your defect, your delay) |
| Import/executor fault | deferred call-out timeout, oracle output failing its guards, inline wasm trap, binding unresolvable | the bound import/executor party | fault-policy runs (below); §3.6 expectation ladder throws if exhausted |
| Infrastructure fault | host unreachable, heartbeat silent, §3.5 conflict-retry exhaustion | the host | blame suspension, declared: obligations whose performance required the machinery don’t accrue dröjsmålsränta during an attributed host fault - kontrollansvar (köplagen; CISG art. 79’s impediment-beyond-control) as an executable carve-out, in the contract, not litigated after |
| Contract-logic fault | the “impossible”: an invariant the linter guarantees violated at runtime, bound-exceeding arithmetic | the contract itself (drafting) | fail fast + contain: affected port auto-suspends, escalation summons the parties, resolution is an amendment - never a hotfix to a signed document |
- Fault handling is DECLARED, like everything else. Per port and per import, from a closed verb set:
retry(bounded, safe because idempotent),fallback(alternate binding, if declared),park(PENDING + expectation),escalate(throw into §4.2’s ladder),halt(close the port pending amendment). And the circuit breaker becomes a contract term:fault-policy: after 3 consecutive import faults -> open 24h, park submissions, notify both parties- Nygard’s pattern, signed. The linter enforces that every deferred import carries a fault policy or inherits the profile default; open circuits and fault storms are visible in the expectation register (§3.6). - Containment is free, by earlier decisions. The fold is pure, so a fault cannot corrupt state (there is no mid-transaction state to corrupt - §3.5’s effects-only-after-commit); and one contract faulting cannot touch a sibling, because nothing sits between contracts (§7.3b’s no-middleware rule paying rent again). Erlang’s let-it-crash philosophy arrives here with its work already done: crash small, record, let the declared supervisor - the fault policy, then the escalation ladder - decide.
Prior art: Erlang/OTP supervision trees (declarative fault handling: child fails, supervisor’s declared strategy restarts/escalates - the fault-policy verb set is a supervision strategy for contracts), the circuit breaker (Nygard, Release It!), dead-letter queues (park-with-attention for poison messages), wasm traps (deterministic failure of sandboxed imports - a trap is a value, not chaos), RFC 9457 Problem Details (the standardized machine-readable fault body - our fault receipt’s body schema rides it), the IEEE fault → error → failure chain (the vocabulary discipline), and on the legal side kontrollansvar/CISG art. 79 - the law has always known that whose machinery failed changes who pays; it just never had attribution receipts.