Open Standard · Working Draft
mekaniskt·kontrakt

Requests for comment  ·  RFC 0005

Time: the operator set, and the calendar it resolves against

The standard bans the clock, then never writes down the arithmetic that replaced it. Every fixture compares and shifts timestamps with operators the registry does not list, against a civil calendar no contract declares. This RFC proposes the closed temporal operator set and a calendar block - civil timezone with pinned timezone rules, working and banking day calendars, and a day-count convention - and shows late-payment interest computed with no clock and no external call.

STATUS
Open for comment
TOPICS
Deadlines, notice periods, working and banking days, late-payment interest, day-count conventions, timezone rules under replay

This is a request for comment, not part of the specification. The syntax is a strawman and much of it is pseudo-code; it exists to be argued with, not adopted by default.

Two problems, one root

The machine has no clock. Time enters only as event timestamps already in the log, and that is the right call - it is most of why replay reproduces state bit-for-bit.

But the language is soaked in time: rate limits, due windows, notice periods, cancellation ladders, index revision windows, heartbeat rhythms, interest. The standard banned the clock and then never wrote down the arithmetic that replaced it.

  • The operators are unlisted. The expression prelude has sum, avg, count, min, max, abs, clamp and four arithmetic operators. Not one temporal operator - yet the worked contracts are full of them.
  • The calendar is undeclared. “30 days”, “window January”, “working-hours”, “banking-day” are civil-time notions. Receipts stamp UTC. Nothing in a contract says which civil calendar its dates live in.

What the worked contracts already use

The honest starting point is the vocabulary already in service across the standard’s own examples:

In use Roughly Status
working-days (10 working-days, 5 working-days) 9 uses the unit is lexed; the counting convention is undefined
within <duration> (30 days, 6 days, 1 banking-day) 12+ uses undeclared operator
at-most N per <period> / at-least N per <period> 6 uses declared (rate limits)
at <period> (rates.senior at line.period) 3 uses undeclared, and spelled two different ways
not-before <event> 2 uses declared (rate limits)
due within <n> days of <event> 2 uses declared - with a second spelling elsewhere
working-hours, banking-days 4 uses referenced as calendar abstractions that no block declares
window January, revise yearly, stale > 13 months, resets per <period> 5 uses undeclared
before <date> - <duration>, within <n> days of <event> 3 uses undeclared date arithmetic

Two findings in that table are worse than “unlisted”.

within means three different things

Same word, in the same contracts, doing three unrelated jobs:

payment.confirm  due within 30 days of invoice accept    # 1. a temporal deadline
if line.qty within basket                                # 2. set membership
guards: output.vat-rates within declared-vat-rates        # 2. set membership again
allocation.request unlimited within validity              # 3. scope restriction
tick.business at-least 1 per hour within working-hours    # 3. scope restriction

The language already has a membership operator - the worked framework agreement writes if payload.role not in rates. So this is discipline, not invention:

Job Operator Example
temporal deadline within within 30 days of invoice.accept
set or range membership in line.item in basket
scope restriction during during validity, during working-hours

Three meanings, three words. This is the unit-agnostic-verb law applied to a preposition, and it is the highest-value edit here: within is the most-used construct in the corpus and the most overloaded.

Duration literals come in two forms

30 days and 90d. 24 months and 13mo. 24h. Only the spelled form is lexed, so every abbreviation in the standard’s prose is non-conforming. One form - spelled, always, because a signed document is read aloud and 13mo is not a clause.

The operator set

Closed, and nothing in it can observe a clock:

Form Meaning Types
event.at the timestamp of the event being judged - a reserved field, not a function -> instant
<ref>.at the timestamp of any referenced receipt or event in the log -> instant
before / after strict ordering (instant|date, instant|date) -> bool
on <date> the same civil day, resolved via the calendar (instant, date) -> bool
within <duration> of <event-ref> the relative deadline form -> bool
between <a> and <b> closed interval -> bool
<date> in <period> civil containment (event.at in 2027-03) -> bool
<date> + <duration>, <date> - <duration> shift, calendar-resolved -> date
<date> - <date> elapsed -> duration
at <period> as-of resolution: a governed artifact, indexed rate or derived view as it stood at that date (governed-value, date) -> value

Not admissible: duration x duration, date x anything, duration x rate without a declared day-count, and any operator whose result depends on when it was evaluated rather than on the log.

event.at rather than a bare event or now: the event record keeps its name, .at is its timestamp, and no word in this language means “the current moment.”

The calendar block

calendar:
  civil-time    Europe/Stockholm                          # IANA zone: all civil-date
                                                          #   arithmetic resolves here
  tzdata        art:iana-tzdata  pin 2026b  sha256:...    # the rules are an ARTIFACT
  working-days  art:se-working-days   track latest-approved
  banking-days  art:se-banking-days   track latest-approved
  day-count     actual/365-fixed                          # for interest

Four declarations, four holes closed:

  1. civil-time. Receipts stamp UTC; contracts speak civil dates. With no declared zone, two conformant hosts in different regions can disagree about which day an event fell on, and therefore about whether a 30-day window was met. That is a determinism failure in a standard whose first property is determinism.
  2. tzdata, pinned. The subtle one. Timezone rules are political and they change - abolished daylight saving, moved transitions, new zones. A 2035 replay of a 2027 contract must resolve civil dates under 2027’s rules, so the timezone database is an effective-dated artifact like a rate card, pinned at signing; re-pinning is an amendment. Almost no software treats timezone rules this way. A replay covenant leaves no choice.
  3. working-days / banking-days. Already referenced by working-hours windows, by 10 working-days approval timeouts, and by every banking day on an expectation ladder - and declared nowhere. Named calendars, so one contract can hold several.
  4. day-count. The standard claims late-payment interest becomes a computed claim rather than a forgotten right. It does not, today: interest is principal x rate x days / basis, and basis is a convention nobody declared. The closed set to adopt is the one the derivatives market settled on: 30/360, actual/360, actual/365-fixed, actual/actual.

The payoff, worked

Late-payment interest, with no clock, no external call, and every term traceable:

rule on payment.confirm:
  check payload.ref in invoices          else reject unknown-invoice
  let due     := invoices[payload.ref].due-date              # a civil date
  let days    := event.at - due                              # -> duration (actual days)
  which days is
    <= 0 days:  record invoices[payload.ref].payment = paid   # in time
    otherwise:
      let rate     := fixing:reference-rate at due + 8%       # statutory: reference + 8 pp
      let interest := invoices[payload.ref].amount
                    x rate x (days / day-count.basis)         # declared basis and rounding
      record claims { kind: late-interest, amount: interest,
                      from: due, to: event.at, clause: #klausul-11 }
      accept

Every operand is either in the log or in a pinned artifact, so a replay years later reproduces the same minor units without the original rate feed still existing.

What this does not add

No clock, no timers, no scheduler, no background evaluation. event.at is a value that was already in the envelope; date + duration is arithmetic over declared data; heartbeats remain the only liveness mechanism and stay ordinary signed events. Timeouts remain predicates, evaluated when something happens.

Honest creaks

  1. Daylight-saving gaps and ambiguity. A civil timestamp can be nonexistent (spring forward) or ambiguous (fall back), and any window with time-of-day precision inherits that. Proposed as a stated v1 simplification rather than a later discovery: declared windows are day-grained. Durations are absolute elapsed time, civil arithmetic works on calendar days, and time-of-day precision inside a declared window is refused by the linter. Machine-speed metering uses instants and durations, never civil windows, so it is unaffected.
  2. Working-day counting has conventions, and they matter. “5 working days after a Friday” and “1 month after 31 January” both need a rule. The derivatives market solved this long ago - following, modified following, preceding, plus an end-of-month rule. Adopt the vocabulary rather than improvise.
  3. Pinned timezone rules go stale on purpose. A contract pinned to a 2026 release will not see a 2029 correction even if that correction is right. That is the replay covenant charging rent; the answer is an amendment, but a very long-lived contract can end up holding a civil calendar the country no longer uses.
  4. One civil zone per contract may be too few. A cross-border deal can want one country’s delivery days and another’s payment days. Named calendars cover it; a single governing civil zone is still the right default - one governing law, one civil clock.
  5. date currently conflates two types. An event timestamp is an instant (absolute, UTC); 2027-01-01 is a civil date that needs a zone before it can be compared. The type vocabulary needs both, distinctly - the same split every serious date library eventually arrives at.
  6. Leap seconds are ignored: second-granular UTC, no smearing, and no contract in the corpus can tell.

Prior art

Day-count fractions and business-day conventions come from the 2006 derivatives definitions - the reason none of the interest machinery needs inventing. Statutory late-payment interest (reference rate plus eight percentage points, actual days) is the national default a profile binds. Invoice payment terms and due dates are already carried by the European e-invoice standard. Duration and interval literals are ISO 8601-1/-2. The timezone database is versioned data with a published maintenance process (RFC 6557), which is exactly why it can be pinned like an artifact - and versioned-locale practice (CLDR/ICU, Java’s timezone updater) is the industrial precedent. The instant-versus-civil-date split is the settled shape of the modern Temporal API. And timed automata are already this standard’s formal home for rate limits: guards over timestamps, never running clocks.

This RFC is exploratory. It is published as a basis for discussion, not as normative text; where it touches the standard, the standard governs until a decision is recorded.