Every operator- or agent-facing read model carries one projection_envelope
(loopx_projection_envelope_v0). It says when the projection observed its
sources, how old each source read is at the moment this copy is served, and
how much of the requested scope the projection covers. The rule that
RFC §2.6
makes kernel-owned is simple: ok: true, a passing check, a healthy host, or
a cache hit never implies fresh or complete sources.
The TypeScript kernel (loopx/control_plane/projection_envelope.ts, runtime
method projection.envelope.seal) is the only implementation that decides
freshness, alerts, and completeness. Producers pass compact read facts;
Python-owned projections do so through loopx/control_plane/projection_envelope_facts.py.
| Projection | projection |
Coverage scope |
|---|---|---|
loopx status |
status |
registry, goal with --goal-id, or activation.<state> |
loopx status --use-projection-cache hit |
status, served_from_cache: true |
as stored |
loopx global-summary |
global_summary |
global |
loopx global-gates |
global_gates |
global |
Other read models adopt the envelope in the order listed in the RFC; until then they carry no freshness guarantee and consumer rule 1 below applies.
{
"schema_version": "loopx_projection_envelope_v0",
"projection": "global_summary",
"observed_at": "2026-09-26T10:00:05Z",
"served_at": "2026-09-26T10:00:05Z",
"age_seconds": 0,
"served_from_cache": false,
"fresh": true,
"complete": false,
"alert": true,
"alert_reasons": ["incomplete_coverage"],
"alert_source_ids": [],
"sources": [
{
"source_id": "goal_quota",
"required": true,
"read_status": "read",
"last_read_at": "2026-09-26T10:00:04Z",
"source_updated_at": null,
"window_seconds": 300,
"staleness_seconds": 1,
"status": "fresh",
"item_count": 1,
"missing_count": 0,
"unreadable_count": 0,
"alert": false,
"alert_reasons": []
},
{ "source_id": "registry", "via": "status", "...": "inherited from the status envelope" }
],
"coverage": {
"scope": "global",
"expected_count": 48,
"included_count": 1,
"omitted": [{ "reason": "outside_current_registry", "count": 47, "refs": ["goal-b", "..."] }],
"shown_count": 8,
"available_count": 19,
"truncated": true,
"complete": false
},
"upstream": [{ "projection": "status", "observed_at": "2026-09-26T10:00:03Z", "complete": true }]
}observed_at: when the projection finished reading its sources. A cached or persisted copy keeps it;served_atandage_secondssay when this copy was emitted.last_read_at: when this projection (or its upstream) last read the source.staleness_seconds = served_at - last_read_at; a read source isstaleonce that exceedswindow_seconds(kernel default 300).source_updated_at: the source's own last write when the producer knows it, such as the newest run in the run indexes. An old value means the goal has not moved, not that the read is stale; it never raises an alert.read_statusisread,missing,unreadable, ornot_read.unreadablealways alerts;missingandnot_readalert only whenrequiredis true. A read aggregate withunreadable_count > 0alerts aspartially_unreadable.coverageis relative to the requested scope.completerequires noomittedrows,included_count >= expected_count, and every upstream envelope complete.truncatedonly discloses a requested display limit and never alerts on its own.viamarks a row inherited from an upstream envelope. A derived projection inherits every upstream source row, so its freshness is bounded by the oldest read it depends on, not by its own assembly time.- Envelope-level
alert_reasonsis a sorted subset ofstale_sources,unreadable_sources,missing_required_sources, andincomplete_coverage.
Source ids, scope names, and reason codes are lowercase identifiers. The
envelope never contains filesystem paths, so public-safe projections can carry
it unchanged. Omission refs hold at most 8 identifiers such as goal ids.
Before stating that something is the current or whole state, a consumer reads the envelope and discloses it:
- A missing envelope means freshness and coverage are unknown. Say so; do not
infer freshness from
ok,generated_at, or a recent command. alert: truemust be stated along with the reasons, the affectedalert_source_ids, and the omissions before any conclusion that depends on them. Markdown renderers print this as a🔴 projection alertsline.- A copy read later than
served_at(a pasted packet, a saved file, a chat quote) is as old asnow - observed_at. Re-read the projection instead of re-serving it from memory. truncated: truemeans onlyshown_countofavailable_countitems are listed. Absence from the list is not absence from the scope.
status --use-projection-cache re-serves the stored envelope through the
kernel on every hit, which restamps served_at and recomputes staleness. A
cache record without an envelope, or with one the kernel decoder rejects, is a
cache miss (missing_projection_envelope / invalid_projection_envelope),
never an unlabeled hit.
An unknown expected_count is never complete. Global views require global
membership even when project-local status treats that source as optional;
missing or unreadable membership alerts and preserves a null denominator.
After the source is repaired, a fresh collection can certify coverage again.