Integrity states and "why do we believe this?"
Every fact Horologium reports can answer one question with a precise, checkable citation: why do we believe this? That answer is built from three layers that are never collapsed into each other.
The three layers
| Layer | Answers | Mutability |
|---|---|---|
| Evidence | What did a source say, exactly where, and when? | Immutable, append-only, never closed |
| Claim / Relationship | What does that mean about a subject? | Temporally versioned — closed, never deleted |
| Finding | What follows from all the claims together? | Derived, re-computed deterministically, closed when its basis changes |
Evidence never becomes wrong, even when the world turns out to disagree with it. If Jira said the refund window was 30 days, the evidence "Jira said 30 days" stays true forever — the claim built from it is what closes when someone corrects the ticket. This separation is what lets Horologium say "we used to believe X, now we believe Y, here's exactly when and why that changed" instead of silently overwriting history.
Evidence: a citation you can paste
Every evidence item carries a source_ref_canonical — an exact, re-fetchable
citation string a human (or an agent) can follow straight to the source:
| Source | Example canonical citation |
|---|---|
| A git commit | git+https://github.com/acme/store@9f2c1ab… |
| A specific line range in a file | git+https://github.com/acme/store@9f2c1ab…:src/refund.rs#L12-L14 |
| A Jira issue field | jira+acme.atlassian.net/SHOP-412#status@2026-06-01T00:00:00Z |
| A documentation section | doc+https://docs.acme.com/refunds@3f9c1e2…#refund-window:840-902 |
| A deployment record | deploy+cloud-run/production#rev-00042 |
A source that can't produce a re-fetchable reference doesn't get to emit evidence at all — that's a conformance requirement on every provider, not a convention.
Claims: multiple, possibly conflicting, all valid at once
A claim is "this subject has this property with this value, according to this evidence." Claims are per-source assertions, and several conflicting claims about the same property being simultaneously true is normal, not an error — it's the entire reason Horologium exists. If Jira says the refund window is 30 days, the docs say 14, and the code says 30, all three claims stand at once, each citing its own evidence.
Claim values are typed, not free text (duration, quantity, boolean,
enum_label, string_exact, or unresolved) specifically so that
"30 days" vs. "1 month" vs. "30 day" can be compared exactly instead of by
string similarity. Comparing two claims returns one of three results:
- Agree — same kind, same unit, identical canonical value.
- Conflict — same kind, same unit, different canonical value.
- Incomparable — different kind or unit, or either side is
unresolved. This never produces a conflict finding — a truth system that guesses when it can't actually compare two values would be manufacturing certainty it doesn't have.
Every claim also carries a derivation: observed (read directly from
structured data — a Jira status field, a git tag) or inferred (extracted from
prose by a model, then validated and accepted as a proposal). The two are never
conflated in storage, in the API, or in the UI.
Unresolved is a real answer, not a null
When Horologium can't determine something, it says so explicitly, with a reason, rather than defaulting to a guess or a blank field:
| Reason | Meaning |
|---|---|
no_provider_configured | No evidence source exists for this dimension on this install |
no_evidence_found | A provider ran and found nothing relevant |
conflicting_evidence | Signals exist but can't be reduced to one value |
below_confidence_threshold | A model proposal existed but wasn't accepted |
provider_error | Collection failed, and the failure is recorded, not swallowed |
not_computed | Reconciliation hasn't run since the inputs last changed |
This is why a fresh self-host install with only Git connected will correctly
show most of a capability's lifecycle as Unresolved(no_provider_configured)
rather than a false No — see Evidence sources
for what each connected source unlocks.
Findings: the deterministic conclusions
A finding is what follows from the claim set, produced only by deterministic reconciliation rules — never by a model:
| Finding | Fires when |
|---|---|
claim_conflict | Two or more open claims on the same subject/property disagree |
documentation_conflict | As above, and at least one participating claim comes from a documentation source |
released_not_live | A capability is implemented and released, deployed on at least one target, but not on the target marked primary production |
implemented_not_released | A capability is implemented, but no release yet contains the change |
unresolved_lifecycle | Any lifecycle dimension is Unresolved for a reason other than "no provider configured" |
A finding is content-addressed — re-running reconciliation over unchanged
evidence produces zero new rows, and when the underlying claim closes, the
finding closes with it (status: resolved). A finding may carry a
model-written narrative explaining it in prose, but the narrative is
never load-bearing: the finding's existence and its citations are entirely
deterministic. See the hosted demo for these findings
firing against a real (synthetic) fixture, and
docs/architecture/product-ontology-v0.0.0.md in the repository for the full
field-level model.