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:
| Value | Meaning |
|---|---|
Yes | The deterministic predicate for this dimension holds |
No | The predicate does not hold |
Unresolved(reason) | Horologium cannot determine this — and says so, with a reason, instead of guessing |
| Dimension | Scope | How it's computed |
|---|---|---|
| Implemented | global | A change exists that implements the capability, and that change's commit is an ancestor of the default branch head |
| Released | global | A published release contains such a change (observed git tag containment — never inferred from version numbers) |
| Deployed | per target | An open deployment on that target runs a release that contains the change |
| Enabled | per target | v0.0.1: always Unresolved(no_provider_configured) — no feature-flag provider exists yet |
| Observed | per target | v0.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_itementities and the claims/relationships they carry (specifies(work_item → capability)) — what a tracker ticket asserts about a capability. - Documented shows up as
documententities anddocuments(document → capability)relationships, and as claims whose evidence hassource_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.