docs/cessation-composition.md
Is cessation already representable?
The standing rule requires exhausting composition before accepting architectural pressure. Applied to temporal extent.
Verdict: already representable — and blocked by a canonicalization defect, not by the ontology.
The six encodings
Each uses only existing concepts. None requires a new primitive.
| Phenomenon | Encoding | Loses truth? |
|---|---|---|
| employee leaves | condition: unavailable on the actor |
no — the person still exists, and is unavailable |
| service decommissioned | condition: unavailable on the resource |
no |
| contract expires | condition: unavailable on the agreement |
no |
| metabolite consumed | measure: concentration = 0 |
no — and it is more precise than cessation |
| package delivered | placement changes to the new place |
no — the package persists, its location changed |
| connection terminated | condition: unreachable |
no |
The metabolite case is the strongest. concentration = 0 is better than
"ceases to exist": it preserves the missing-versus-negative distinction the whole
provenance apparatus rests on. Absence of a fact means nobody measured;
a fact with value 0 means someone measured and found none. Modelling
cessation as subject-deletion would destroy that distinction.
So the ontology does not need cessation. It needs magnitudes that can change.
Why the composition fails anyway
It fails at collapse, not in the vocabulary. Measured with a correctly typed
fixture:
identity: measure:concentration:resource:cell/glucose
same identity for both observations? true
early first -> value=1 validAt=00:10 ← the LATE timestamp
late first -> value=0 validAt=00:10 ← with the EARLY value
Two defects, in one function:
1. collapse is order-dependent
The same two facts produce a different world depending on array order. A canonical model whose output depends on input ordering is not canonical.
2. It fabricates a fact that was never observed
collapse advances validAt to the latest while keeping the first-encountered
value. The surviving fact therefore asserts "at 00:10, concentration was
1 mM" — when the observation at 00:10 said 0 mM. That is not information loss;
it is a fact nobody reported.
The root
factIdentity for a measure is measure:${metric}:${subject} — time-invariant
identity for a time-varying quantity. Two measurements of the same quantity at
different times therefore collapse as duplicates.
For condition the assumption holds: a condition is present or absent, and two
observers reporting it are reporting one fact. For measure and placement it
does not: the same subject and metric at two times is two observations of a
changing quantity, not one fact seen twice.
The vocabulary carries validAt on every fact. The identity function discards
it. The information is present and canonicalization throws it away — which is
the same failure shape as every other defect this audit has found.
Classification
| Candidate | Verdict |
|---|---|
| already representable | yes — by composition |
| composition | condition: unavailable, or measure = 0, or a placement change |
| architectural pressure | no |
| false lead | no — the phenomenon is real, the diagnosis was wrong |
Temporal extent is not architectural pressure. Window remains unused
because nothing needs it: a bounded existence is expressible as a magnitude
falling to zero or a condition becoming true. What is needed is that a later
observation of the same quantity supersede an earlier one, which is a defect in
collapse and factIdentity.
Hypothesis 3 confirmed — the mapping into the vocabulary was wrong. The ontology is not implicated.
A retraction
I first ran this with any[] fixtures and reported that factIdentity produced
[object Object], concluding that every measure of a metric collapsed
regardless of subject. That was my error. Fact.subject is typed Urn, a
string; my fixture passed a SubjectRef object. Using any[] disabled exactly
the type check that would have caught it.
Same shape as every measurement error in this project: the instrument was wrong, and it was wrong because I had switched off the thing that would have told me.
Why this defect has never been observed
core has no consumers. lib/world uses its own mergeFacts (later
substrate wins — correct), not core.collapse. So the defect is latent: it lives
in the most heavily verified module in the repository, and the verification never
exercised a changing quantity, only duplicate observations of a static one.
That closes a loop with the platform-boundary finding. core's zero-consumer
status is not merely an adoption gap — it means core's defects go undetected,
because nothing pushes real time-varying data through it.