Universe Write Contract — Design
Five verbs live in every universe: create, update, touch, affirm, delete. What differs between a subject that keeps an occurrence log and one that does not is the act — and the difference is carried by a type parameter rather than by a default value.
“Act” here is the entry written to a subject’s occurrence log, not the domain’s Actions — the actuations and observations that behavior descriptions are built from. The two are unrelated and the word is shared.
One base, two bindings
Section titled “One base, two bindings”One write base carries the five verbs, parameterized on the act A, over a separate read base. It has two bindings:
Nothing?— a subject with no log. The act isnull, and a convenience overload means basic callers keep today’s signatures exactly.Occurrence<K, R>— a subject with a log.Ais non-nullable here, so an act-less call has no overload to bind to.
That last sentence is the whole point. The illegal call does not fail a guard; it fails to compile, because nothing exists for it to resolve against.
The table’s declaration selects the binding. OccurrenceUniverse already carries a where TBL : UniverseTable, TBL : OccurrenceColumns bound, so the shape follows from what the table declares. The mechanism is in the tree today, applied to the universe rather than to the write signatures.
One argument carrier per verb
Section titled “One argument carrier per verb”Each verb takes a carrier — Create, Update, Touch, Delete, Affirm — each parameterized on A so that each degrades the same way, with convenience bindings as default implementations so existing call sites compile unchanged.
The reason is not symmetry. update is the only verb that can carry idempotency or effectiveTimeOrdering today, and a delete writes a tombstone successor row with no way to say how it should be ordered. Giving each verb its own carrier lets each say what it can actually mean.
| Carrier | Carries | Notes |
|---|---|---|
Create | payload, metadata, asOf, act | No ordering — there is no predecessor |
Update | payload, metadata, asOf, act, idempotency, effective-time ordering | Serializable; crosses the wire |
Touch | eId, asOf, act, idempotency, effective-time ordering | Today it can carry neither ordering field |
Delete | origin eId, metadata, asOf, effective-time ordering | Act fixed to the retire kind |
Affirm | eId, affirmed record id, asOf | Act fixed to the affirm kind; the staleness guard is itself the monotonic condition |
Two verbs fix their own act
Section titled “Two verbs fix their own act”delete and affirm supply their own act; create, update and touch take one.
So delete needs no act parameter — the universe supplies it — and retireKind sits on the interface for the same reason the affirm kind does: the universe must know its own act in order to fix it.
retireKind is mandatory on every occurrence universe. An earlier proposal put it on a sub-interface so that a log with no delete-shaped kind could have no delete at all; that proposal is withdrawn, along with the RetirableOccurrenceUniverse it needed. The decision log records why, because the way it failed is more useful than the proposal.
retireKind names which act delete fixes. It does not define what counts as a retire. A log may have several retire-shaped kinds, and the model’s lifecycle family has no notion of the retire kind. The consequence matters more than the rule: terminality attaches to the act, not to the verb — the guard forbidding commits after a retire keys on the kind being retire-shaped, however it was written.
affirm on a subject with no log
Section titled “affirm on a subject with no log”affirm on a basic universe is touch with a staleness guard.
With no log, “I checked and it is unchanged” has nowhere to be recorded as an act, so it is a re-stamp — and a re-stamp on a subject with no log is not a false log entry. Basic rows still carry record ids, so a stale claim can still be refused, which is why the guard is kept rather than making it a bare alias: an affirmation that cannot be stale asserts nothing.
Scope: the split to Table and Persistence
Section titled “Scope: the split to Table and Persistence”The change reaches below the universe interfaces. In scope: OccurrenceTable and ScopedOccurrenceTable (no child variant), with Persistence.kt and AbstractUniverse.kt split along the same seam, and both throwing branches of fillOccurrence deleted rather than shrunk.
No migrations are touched and no second flag day is needed. The LargeSourceFile suppression comes off as a by-product.
This removes two of the three runtime guards, not three. The one that stays is the subject of Illegal Call Classes.
What it costs the estate
Section titled “What it costs the estate”Measured rather than estimated: zero of 1,788 call sites in operations hold a bare universe reference.
DataAuthorityService is the only consumer generic over every subject, and it is the reason the shape is one parameterized base rather than two disjoint interfaces. Under two interfaces it would have to be split, or made generic over which interface it holds; under one base it is generic over A, and that is all. The entire generic-consumer cost of the alternative would have landed on the single class least able to absorb it.
Copyright: © Arda Systems 2025-2026, All rights reserved