docs/truth-kinds.md
What kinds of truths exist?
⚠️ Superseded in part by truth-reduction.md. Constraint was subsequently reduced to a state on a policy subject, and interpolability was reduced to absent-evidence semantics. The classification of the ledger below stands; the three-kind result does not.
Identity failed as a universal function. The question underneath it: what kind of truth is being represented? Identity should follow from that.
Derived from the phenomenon ledger, not from the fact families. The four families are treated here as hypotheses, and two of them do not survive as truth-kinds.
1. Deriving the distinction — without circularity
The level/occurrence split cannot be derived from separation semantics, because separation is what we are trying to explain. A prior, independent discriminator is needed.
Interpolability. Take two observations with nothing observed between them. Does the truth hold in the gap?
- Temperature: between a reading at 09:00 and one at 09:05, the temperature had some value. It never stopped being true.
- Payment: between a payment at 09:00 and one at 09:05, there is no payment. The gap is empty, not unobserved.
That is a claim about reality, testable per phenomenon, and it makes no reference to identity. And separation then falls out:
| Holds in the gap? | Therefore two equal adjacent observations are… | |
|---|---|---|
| State | yes — it persisted | one continuing truth — episode separation |
| Occurrence | no — the gap is empty | two truths — always separate |
The separation semantics are not a design choice and not an implementation detail. They are entailed by whether the truth exists between its observations. That is the derivation.
2. The ledger, classified by semantic behaviour
| Phenomenon | Holds between observations? | Class |
|---|---|---|
| temperature | yes | state |
| concentration | yes | state |
| inventory | yes | state |
| location | yes | state |
| employment | yes | state (2-ary) |
| ownership | yes | state (2-ary) |
| shipment | ambiguous | both — see below |
| payment | no | occurrence |
| heartbeat | no | occurrence |
| commit | no | occurrence |
| package delivery | no | occurrence |
| chemical reaction | no | occurrence |
Three things fall out that the fact families obscure.
Heartbeat is decisive. It is an occurrence that changes no state at all.
Nothing rises, falls, or moves. So occurrences cannot be modelled as
state-transitions — there is no transition. Together with the earlier result
(two simultaneous transfers a→b and c→b of 5 give the same net levels as one
of 10), occurrence is irreducible to state on two independent grounds: one
where the deltas collide, one where there are no deltas.
Occurrences become subjects. A commit, a payment and a delivery each acquire properties after they occur — a commit has an author, a payment has a status. So an occurrence can be promoted to a subject that then bears states. The classes are not disjoint over things; they are disjoint over truths.
"Shipment" names two truths at once — the parcel (a state-bearer with a location) and the act of shipping (an occurrence). This is a warning about method: phenomenon names do not map one-to-one onto truth kinds, so any classification that starts from vocabulary rather than from the interpolability test will inherit natural language's conflations.
3. Are there only two classes?
The six candidates, tested rather than assumed.
Eliminated: relation
Employment and ownership are interpolable, so they are states. What
distinguishes them is that their subject-tuple has arity 2. Arity is not a
truth kind. exchange already demonstrates a 3-ary tuple without anyone
proposing a class for it. Relation is a state over a tuple.
Eliminated: measurement — a category error
Temperature is a state. Measurement is how we come to know it. Putting
measurement in a list of truth kinds confuses what is true with how it is
known, which is precisely the layer the evidence field occupies.
This matters because the family is named measure. The name records the
observation method, not the kind of truth — and that is why MeasureFact ended
up carrying limit and enforced (below).
Unsettled: capability
⚠️ Retracted by semantic-typing.md.
capability → statematches algebra and layer and mismatches role — modal versus actual. That is the over-collapse pattern, committed here before the pattern was known.
"This machine can produce 100 units/hour" is interpolable, so by the test it is a state whose dimension is dispositional. But it differs from every other state in one respect: non-exercise is not evidence of absence. A machine idle all day does not thereby lack the capability, whereas a thermometer reading nothing does mean the temperature is unobserved.
Recorded as state, least settled. It may need a modal treatment; that has not been demonstrated, and one unusual property is not a class.
Survives: constraint — a third class
"Inventory must not exceed 100." Two discriminators, either sufficient:
- It is not observed. It is stipulated. No observer reports it; it is
adopted by an authority. The
evidencefield is meaningless for it — and evidence is otherwise mandatory on every fact. - It can be violated. This is the sharp one. A state cannot be violated; it simply is. A temperature of 200° does not violate anything. But a constraint can be true as a rule and false in fact at the same instant — a truth mode no state or occurrence has.
Violability is not a property of anything else in the ledger. Constraint earns its class.
And it is already here, unclassified, in two places:
MeasureFact.limit+enforced— a constraint riding inside a state fact.enforcedexists to distinguish a real constraint from a decorative one, which is a constraint-kind question being answered by a boolean flag on a measure.constitution.tsCONSTANTS, each withassumed: true | false— a constraint table with the same shape and the same flag, in a different subsystem.
Two independent subsystems grew the same structure, neither named it, and both needed a flag for is this constraint real. That is convergent evidence for the class, found rather than invented.
4. Identity follows from truth kind
The payoff. Separation is not one rule with exceptions; it is one rule per truth kind, each entailed by that kind's relationship to observation:
| Class | Exists between observations? | Separates on | Third identity component |
|---|---|---|---|
| state | yes | a change of value | episode ordinal |
| occurrence | no | always | occurrence ordinal |
| constraint | not observed at all | amendment | version ordinal |
A constraint has no observation series, so it cannot have an episode or an occurrence index. It has a validity period running from adoption to repeal, and it separates when it is amended — a third rule, derived the same way as the first two, from the kind's relation to observation.
Identity is not universal, and it does not need to be. It is a function of truth kind, and truth kind is derivable from a single test. That is a stronger result than a universal identity function would have been: it explains why no universal function exists.
The four fact families are not four truth kinds. condition, measure and
placement are three states differing only in value type; exchange is an
occurrence. The families encode value type and observation method, and the
truth-kind distinction is nowhere in the type system.
5. PlacementFact.since — the partial discovery, confirmed
Placement is a state. since is its episode start. So one family already
carries interval semantics — and the suspicion that the distinction was
"partially discovered but never generalized" holds, with a mechanism:
- It is derivable, not primitive. Under correct episode identity,
sinceis thevalidAtof the first observation in the run. It is a materialized cache of what identity should compute, which is exactly why it can go stale and disagree with its own fact. - It is required at ingest (
ingest.ts:30—since: Instant, not optional). So every connector is asked to compute episode boundaries. That is the same layering error asidbelow: canonicalization work pushed onto the observer, who cannot see the other observers' facts and therefore cannot know where an episode begins. - Why placement got it and the others did not: location change is the most perceptually salient episode — a thing visibly moves. The field was added under rendering pressure, not derived, so it never generalised to temperature or employment, which are episodic in exactly the same way.
Window in core/time.ts remains unused because the one place that needed
interval semantics hand-rolled half of them.
6. The id field — which layer?
Measured, not reasoned.
ingest.ts:94 idOf() is byte-for-byte identical to fact.ts:92
factIdentity() — same four cases, same string formats, same order.
Against the five candidates:
| Candidate | Verdict |
|---|---|
| connector identity | no — no connector value enters it. ConnectorManifest.id is documented "Provenance only. Never enters an identity." |
| observation identity | no — by construction two observers of one fact produce one id; an observation identity would distinguish them |
| evidence identity | no — evidence carries its own observer and citation |
| provenance identity | no — it records nothing about origin |
| semantic identity | yes — a pure function of subjects and dimension |
Same layer. The dual-ownership diagnosis stands — and is worse than stated:
this is not merely a competing identity, it is a duplicated implementation of
the same function. That is the exact defect fact.ts already documents having
fixed once, when lib/hq/findings.ts re-implemented conditionIdentity
byte-identically. The duplicate was removed there and left here, in the module
the comment was written in.
Two further measurements sharpen it:
- Nothing reads
fact.id.collapsekeys onfactIdentity, not onid. The field is dead weight that is also authoritative-looking. - A test asserts the duplication.
core-invariants.test.ts:232assertsmerged.facts[0].id === "stalled:" + urn(shared). So divergence is currently caught — for theconditionfamily, in one case. The other three families are unguarded, and the guard tests the duplicate rather than removing it.
And when collapse merges, the surviving id is whichever fact was encountered
first — the same order-dependence already recorded for validAt.
Classification
| Question | Result |
|---|---|
| Is the level/occurrence distinction fundamental? | yes — derived from interpolability, independent of identity |
| Do separation semantics emerge automatically? | yes — entailed, not chosen |
| Are there only two classes? | no — three. state, occurrence, constraint |
| relation | eliminated — a state over a tuple; arity is not a truth kind |
| measurement | eliminated — a category error; it is the evidence layer |
| capability | state, least settled; non-exercise ≠ absence |
PlacementFact.since |
partial discovery confirmed; derivable, and demanded of connectors |
id |
semantic identity — same layer; dual ownership stands, as a duplicated implementation |
Nothing implemented. The constraint class is derived from the ledger and from two subsystems that independently grew its shape; whether it warrants a representation is a separate question, not answered here.