Skip to main content

Reality layers

Horologium's central claim is that a product's reality is not one status field. It is a sequence of layers, each of which can be true independently of the others, and each of which can drift out of sync with the rest:

Intent → Documented → Implemented → Tested → Built → Released → Deployed → Enabled → Observed

A ticket moving to "Done" only tells you about one layer (Intent, as tracked). It tells you nothing about whether the code matches the docs (Documented vs. Implemented), whether the change shipped in a tagged version (Released), or whether that version is actually running where customers hit it (Deployed). Collapsing all of that into a single status — which is what every issue tracker does by default — is precisely the drift Horologium exists to surface.

What v0.0.1 computes today

Not every layer above is a first-class, independently queryable dimension in the current implementation. Being explicit about the gap between the conceptual model and what's actually built is part of what makes this documentation trustworthy — this section is checked against crates/horologium-domain and docs/adr/0013-deployment-and-release-reality-model.md, not aspirational.

Implemented, Released, Deployed, Enabled and Observed are a real, deterministic lifecycle vector computed per capability, per deployment target, at a point in time (GET /v1/capabilities/{id}/lifecycle?target=&at=). Each dimension resolves to exactly one of three values — never a fourth:

ValueMeaning
YesThe deterministic predicate for this dimension holds
NoThe predicate does not hold
Unresolved(reason)Horologium cannot determine this — and says so, with a reason, instead of guessing
DimensionScopeHow it's computed
ImplementedglobalA change exists that implements the capability, and that change's commit is an ancestor of the default branch head
ReleasedglobalA published release contains such a change (observed git tag containment — never inferred from version numbers)
Deployedper targetAn open deployment on that target runs a release that contains the change
Enabledper targetv0.0.1: always Unresolved(no_provider_configured) — no feature-flag provider exists yet
Observedper targetv0.0.1: always Unresolved(no_provider_configured) — no runtime-telemetry provider exists yet

These five are never collapsed into one badge or one "live" boolean anywhere in the schema, the API, or the dashboard. A capability can be Implemented: Yes, Released: Yes, Deployed: Yes on a beta target and Deployed: No on production, simultaneously — that exact shape is Horologium's headline demonstration (see the hosted demo).

Intent, Documented, Tested and Built are represented structurally, but not (yet) as dimensions of the lifecycle vector:

  • Intent shows up as work_item entities and the claims/relationships they carry (specifies(work_item → capability)) — what a tracker ticket asserts about a capability.
  • Documented shows up as document entities and documents(document → capability) relationships, and as claims whose evidence has source_class = documentation — what a docs page asserts, with a citation back to the exact section.
  • Built has a modeled entity (artifact, identified by content digest — tags move, digests don't) but is not yet wired into a deterministic predicate the way Released and Deployed are.
  • Tested has no entity or provider in v0.0.1 at all — it is on the roadmap, not present.

This means Product Truth today answers "is Jira Done consistent with what's released and deployed?" precisely, and answers "does this match a test run?" not at all yet. That gap is intentional scope for an MVP validation slice, not an oversight — see Public Alpha limitations.

Why this matters more than it looks

The reconciliation rules that make the lifecycle vector useful are entirely deterministic — no model involved:

  • released_not_live: a capability is implemented, released, deployed somewhere, but not deployed on the target marked as primary production.
  • implemented_not_released: a capability is implemented but no release contains the change yet.

Unresolved propagates rather than degrading to No. If Horologium can't determine whether a capability is deployed to production (no deployment provider configured, or a failed collection), it reports Unresolved(no_provider_configured) — never a false No, and never a false released_not_live finding. See Integrity states and the evidence model for how Unresolved fits into the broader model, and docs/adr/0013-deployment-and-release-reality-model.md in the repository for the full decision record.