core/workbench/lived_life.py
Shay 7ead395eeb feat(workbench): Lived Life surface — the always-on heartbeat made felt (read-only, persist-first)
The L10 heartbeat (chat/always_on, #747) holds CORE alive over uptime but had no
surface. This adds the read-only Lived Life route that renders the continuous-life
evidence: the heartbeat over uptime + the resume-as-same-life verdict — both halves
of "one continuous life" on one surface.

Persist-first, never recompute engine-owned values:
- chat/always_on: serialize_report + write_lived_life persist a run deterministically
  (sorted keys) to engine_state/lived_life.json; run_continuous gains an opt-in
  report_path so a real continuous-life run leaves its evidence where the workbench
  reads it. closure_ceiling is persisted so the artifact is self-describing.
- workbench/lived_life.py: projection + a fail-closed validate() gate — the wrong=0
  analogue for the continuity surface: closure_held/closure_observed/totals/converged
  are each re-checked against the per-beat measurements, and resume_status is re-checked
  against identity-vs-current-substrate, so a tampered artifact RAISES rather than
  rendering a false claim (a beat lying about a breached ceiling, an inflated closure,
  a miscounted total, a falsely-claimed resume).
- workbench/readers.lived_life(): reads the artifact, computes the resume verdict from
  the persisted identity vs the canonical engine_identity_for_config (fail-soft ->
  "unknown", like IdentityContinuity). Honest absence when no run has been persisted.
- GET /lived-life + LivedLife/LivedLifeHeartbeat schemas (snapshot regenerated; the
  schema-drift gate proves every field is mirrored in src/types/api.ts).

The resume verdict made felt: would_resume / substrate_changed / unknown IS the L11
reboot guarantee (a reboot recomputes identity and refuses if it differs) — so the
surface shows this life wakes up as ITSELF, not a copy. The per-reboot lineage chain
stays owned by Runs > Identity (honest cross-link).

Frontend: /lived-life route (Evidence section, route count 15 -> 16). Renders the
headline (heartbeats, closure-held-by-construction, learned-while-idle, at-rest, resume
pill), the summary, the heartbeat timeline (closure flat below the ceiling — read, never
repaired), and the resume verdict. Honest absence + ADR-0162 route conformance
(loading/error/empty), fail-closed like Vault/Calibration.

Tests (non-vacuous): 12 backend (every tamper case raises; resume tracks identity vs
substrate; run_continuous(report_path) round-trips a readable+valid artifact) + 4
frontend render tests (recorded life renders; a breached beat shows the warning, never a
false "held"; a changed substrate shows would-refuse, never a false resume; absent shows
honest absence). All workbench Python (167) + affected vitest (167) + tsc + vite build
green; architectural invariants green.

engine_state/lived_life.json is runtime state (gitignored, ADR-0146 pattern).
2026-06-14 16:45:03 -07:00

199 lines
8.3 KiB
Python

"""L10 lived-life surface — read-only projection of the persisted always-on run.
The always-on heartbeat (``chat.always_on``) holds CORE alive over uptime and persists
its evidence to ``engine_state/lived_life.json`` (``write_lived_life``). This module
projects that artifact into the read-only :class:`~workbench.schemas.LivedLife` model the
workbench renders, and fail-closes: a ``recorded`` surface must be CONSISTENT with the
persisted per-beat measurements — it can never claim the field stayed valid while a beat
breached the ``versor_condition`` ceiling (the wrong=0 analogue for the continuity
surface).
No engine math is re-implemented here. The scalars (``versor_condition``, the closure
ceiling, the learning counts) are exactly what ``chat.always_on`` measured and persisted;
this module only re-projects and re-checks them.
"""
from __future__ import annotations
from typing import Any, Literal
from chat.always_on import CLOSURE_CEILING, LIVED_LIFE_SCHEMA_VERSION
from workbench.schemas import ArtifactRef, LivedLife, LivedLifeHeartbeat
ResumeStatus = Literal["would_resume", "substrate_changed", "unknown"]
_RESUME_SUMMARY: dict[ResumeStatus, str] = {
"would_resume": "a reboot resumes this life — its identity matches the current substrate",
"substrate_changed": "the substrate changed — a reboot would refuse (IdentityContinuityError)",
"unknown": "resume verdict unavailable — the current substrate identity could not be recomputed",
}
def _resume_status(identity: str | None, current_identity: str | None) -> ResumeStatus:
"""The L11 reboot guarantee as a self-contained verdict: a reboot recomputes the engine
identity and refuses if it differs from the persisted one, so ``would_resume`` ⟺ the
persisted life's identity equals the current substrate identity."""
if not identity or not current_identity:
return "unknown"
return "would_resume" if identity == current_identity else "substrate_changed"
def missing_lived_life(reason: str) -> LivedLife:
"""Honest absence — no always-on run has been persisted yet."""
return LivedLife(
schema_version="lived_life_v1",
status="missing_evidence",
missing_reason=reason,
identity=None,
heartbeats=0,
closure_observed=False,
closure_held=False,
closure_ceiling=CLOSURE_CEILING,
final_checkpoint_ok=False,
converged=False,
total_facts_consolidated=0,
total_proposals_created=0,
current_identity=None,
resume_status="unknown",
resume_summary=_RESUME_SUMMARY["unknown"],
records=[],
artifact=None,
)
def validate(record: LivedLife) -> None:
"""Fail-closed honesty gate for a recorded lived-life surface.
Every claim the surface makes must agree EXACTLY with the per-beat measurements it is
built from, so the continuity card can never overstate the life:
* each beat's ``field_valid`` agrees with ``versor_condition (None or < ceiling)``;
* ``closure_observed`` agrees with "some beat observed a field";
* ``closure_held`` agrees with "every OBSERVED versor_condition < ceiling";
* ``heartbeats`` and the learning totals agree with the records;
* ``converged`` agrees with "records exist and the final beat did no work".
This is the wrong=0 analogue for the continuity surface: a tampered artifact (a beat
whose ``field_valid`` lies about a breached ceiling, an inflated ``closure_held``, a
miscounted total) makes ``validate`` raise rather than render a false claim.
"""
if record.status != "recorded":
return
for beat in record.records:
expected_valid = (
beat.versor_condition is None
or beat.versor_condition < record.closure_ceiling
)
if beat.field_valid != expected_valid:
raise ValueError(
f"lived-life beat {beat.tick}: field_valid={beat.field_valid} disagrees "
f"with versor_condition vs ceiling "
f"({beat.versor_condition} / {record.closure_ceiling:.3e})"
)
observed = [
b.versor_condition for b in record.records if b.versor_condition is not None
]
if record.closure_observed != bool(observed):
raise ValueError(
"lived-life closure_observed disagrees with the per-beat measurements"
)
if record.closure_held != all(vc < record.closure_ceiling for vc in observed):
raise ValueError(
"lived-life closure_held disagrees with the per-beat measurements"
)
if record.heartbeats != len(record.records):
raise ValueError("lived-life heartbeats disagrees with the record count")
if record.total_facts_consolidated != sum(
b.facts_consolidated for b in record.records
):
raise ValueError(
"lived-life total_facts_consolidated disagrees with the records"
)
if record.total_proposals_created != sum(
b.proposals_created for b in record.records
):
raise ValueError(
"lived-life total_proposals_created disagrees with the records"
)
expected_converged = bool(record.records) and not record.records[-1].did_work
if record.converged != expected_converged:
raise ValueError("lived-life converged disagrees with the final beat")
expected_resume = _resume_status(record.identity, record.current_identity)
if record.resume_status != expected_resume:
raise ValueError(
"lived-life resume_status disagrees with identity vs current_identity"
)
if record.resume_summary != _RESUME_SUMMARY[record.resume_status]:
raise ValueError("lived-life resume_summary disagrees with resume_status")
def _coerce_heartbeat(raw: Any) -> LivedLifeHeartbeat:
if not isinstance(raw, dict):
raise ValueError("lived-life record must be an object")
versor_condition = raw.get("versor_condition")
return LivedLifeHeartbeat(
tick=int(raw["tick"]),
versor_condition=(
None if versor_condition is None else float(versor_condition)
),
field_valid=bool(raw["field_valid"]),
facts_consolidated=int(raw["facts_consolidated"]),
proposals_created=int(raw["proposals_created"]),
pending_proposals=int(raw["pending_proposals"]),
did_work=bool(raw["did_work"]),
)
def lived_life_from_payload(
raw: Any,
*,
artifact: ArtifactRef | None = None,
current_identity: str | None = None,
) -> LivedLife:
"""Project a persisted ``lived_life.json`` payload into the validated read model.
An unrecognized ``schema_version`` is honest absence (a forward/old artifact), not an
error. ``converged`` and ``resume_status`` are DERIVED here (not trusted from the
artifact) — ``resume_status`` from the persisted ``identity`` vs the caller-supplied
``current_identity`` (the live substrate identity); ``validate`` then re-checks every
artifact-sourced claim against the records.
"""
if not isinstance(raw, dict):
raise ValueError("lived_life artifact must be an object")
if raw.get("schema_version") != LIVED_LIFE_SCHEMA_VERSION:
return missing_lived_life(
f"unrecognized lived_life schema_version: {raw.get('schema_version')!r}"
)
records = [_coerce_heartbeat(item) for item in raw.get("records", [])]
identity = str(raw["identity"]) if raw.get("identity") else None
resume_status = _resume_status(identity, current_identity)
record = LivedLife(
schema_version="lived_life_v1",
status="recorded",
missing_reason=None,
identity=identity,
heartbeats=int(raw.get("heartbeats", len(records))),
closure_observed=bool(raw["closure_observed"]),
closure_held=bool(raw["closure_held"]),
closure_ceiling=float(raw.get("closure_ceiling", CLOSURE_CEILING)),
final_checkpoint_ok=bool(raw["final_checkpoint_ok"]),
converged=bool(records) and not records[-1].did_work,
total_facts_consolidated=int(raw["total_facts_consolidated"]),
total_proposals_created=int(raw["total_proposals_created"]),
current_identity=current_identity,
resume_status=resume_status,
resume_summary=_RESUME_SUMMARY[resume_status],
records=records,
artifact=artifact,
)
validate(record)
return record