Merge pull request #748 from AssetOverflow/codex/lived-life-surface
feat(workbench): Lived Life surface — the always-on heartbeat made felt
This commit is contained in:
commit
99a52746ae
20 changed files with 1321 additions and 10 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -59,6 +59,7 @@ teaching/math_proposals/proposals.jsonl
|
|||
engine_state/manifest.json
|
||||
engine_state/recognizers.jsonl
|
||||
engine_state/discovery_candidates.jsonl
|
||||
engine_state/lived_life.json
|
||||
|
||||
# Private outreach / personal packet — never tracked
|
||||
01_Anthropic/
|
||||
|
|
|
|||
|
|
@ -36,14 +36,21 @@ the idle/heartbeat half.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
from algebra.versor import versor_condition
|
||||
from core.engine_identity import engine_identity_for_config
|
||||
from engine_state import get_git_revision
|
||||
|
||||
# The name of the persisted lived-life artifact (one per always-on run) in the
|
||||
# engine-state dir, read-only, for the workbench Lived Life surface.
|
||||
LIVED_LIFE_FILENAME = "lived_life.json"
|
||||
LIVED_LIFE_SCHEMA_VERSION = "lived_life_v1"
|
||||
|
||||
# The non-negotiable field invariant (CLAUDE.md). The heartbeat READS this as evidence;
|
||||
# it never repairs to keep it true — closure is owned by ``algebra/versor.py``.
|
||||
CLOSURE_CEILING = 1e-6
|
||||
|
|
@ -88,6 +95,45 @@ class AlwaysOnReport:
|
|||
return len(self.records)
|
||||
|
||||
|
||||
def serialize_report(report: AlwaysOnReport) -> dict[str, Any]:
|
||||
"""Deterministic, JSON-able projection of an always-on run — the persisted lived-life
|
||||
evidence the workbench reads."""
|
||||
return {
|
||||
"schema_version": LIVED_LIFE_SCHEMA_VERSION,
|
||||
"identity": report.identity,
|
||||
"heartbeats": report.heartbeats,
|
||||
"closure_ceiling": CLOSURE_CEILING,
|
||||
"closure_observed": report.closure_observed,
|
||||
"closure_held": report.closure_held,
|
||||
"final_checkpoint_ok": report.final_checkpoint_ok,
|
||||
"total_facts_consolidated": report.total_facts_consolidated,
|
||||
"total_proposals_created": report.total_proposals_created,
|
||||
"records": [
|
||||
{
|
||||
"tick": r.tick,
|
||||
"versor_condition": r.versor_condition,
|
||||
"field_valid": r.field_valid,
|
||||
"facts_consolidated": r.facts_consolidated,
|
||||
"proposals_created": r.proposals_created,
|
||||
"pending_proposals": r.pending_proposals,
|
||||
"did_work": r.did_work,
|
||||
}
|
||||
for r in report.records
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def write_lived_life(report: AlwaysOnReport, path: Path) -> None:
|
||||
"""Persist the lived-life evidence deterministically (sorted keys) so the workbench can
|
||||
read it as a read-only artifact. Overwrites — the artifact is the latest always-on run
|
||||
(a cumulative whole-life log is a future enhancement)."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(
|
||||
json.dumps(serialize_report(report), indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _live_versor_condition(runtime) -> float | None:
|
||||
"""Read the closure of the runtime's live field, or None if no turn built one yet.
|
||||
|
||||
|
|
@ -106,6 +152,7 @@ def run_continuous(
|
|||
sleep_seconds: float = 0.0,
|
||||
on_heartbeat: Callable[[HeartbeatRecord], None] | None = None,
|
||||
stop: Callable[[], bool] | None = None,
|
||||
report_path: Path | None = None,
|
||||
) -> AlwaysOnReport:
|
||||
"""Run the always-on heartbeat for up to ``heartbeats`` beats.
|
||||
|
||||
|
|
@ -118,6 +165,13 @@ def run_continuous(
|
|||
Bounded for falsifiable soaks; a daemon passes a large ``heartbeats`` + a ``stop``
|
||||
predicate (and a real ``sleep_seconds`` cadence). ``stop`` is checked BEFORE each
|
||||
beat so a clean shutdown still persists the final state.
|
||||
|
||||
When ``report_path`` is given, the run's lived-life evidence is persisted there after
|
||||
the loop exits — point it at ``<engine_state>/lived_life.json`` so the workbench Lived
|
||||
Life surface reads the continuous life. A clean ``stop`` still writes the full report
|
||||
(the report captures everything accumulated up to the stop). On a crash the engine
|
||||
state is still checkpointed in ``finally`` for recovery, but the workbench report is
|
||||
best-effort and simply not refreshed — the surface keeps the last good run.
|
||||
"""
|
||||
if heartbeats < 0:
|
||||
raise ValueError("heartbeats must be >= 0")
|
||||
|
|
@ -161,7 +215,7 @@ def run_continuous(
|
|||
final_checkpoint_ok = False
|
||||
|
||||
observed = [r.versor_condition for r in records if r.versor_condition is not None]
|
||||
return AlwaysOnReport(
|
||||
report = AlwaysOnReport(
|
||||
records=tuple(records),
|
||||
identity=identity,
|
||||
closure_observed=bool(observed),
|
||||
|
|
@ -170,3 +224,6 @@ def run_continuous(
|
|||
total_facts_consolidated=sum(r.facts_consolidated for r in records),
|
||||
total_proposals_created=sum(r.proposals_created for r in records),
|
||||
)
|
||||
if report_path is not None:
|
||||
write_lived_life(report, report_path)
|
||||
return report
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ successful proof.
|
|||
## 4. Current Route Map
|
||||
|
||||
The route registry in `workbench-ui/src/app/routes.ts` is the source of truth.
|
||||
Current route count: 15.
|
||||
Current route count: 16.
|
||||
|
||||
| Section | Route | Path | Shortcut | Purpose |
|
||||
|---|---|---|---|---|
|
||||
|
|
@ -71,6 +71,7 @@ Current route count: 15.
|
|||
| Determinism | Demos | `/demos` | Palette | Run registered determinism demos. |
|
||||
| Evidence | Proposals | `/proposals` | `⌘4` | Review cognition and math proposal evidence. |
|
||||
| Evidence | Runs | `/runs` | `⌘6` | Browse recorded run/session evidence. |
|
||||
| Evidence | Lived Life | `/lived-life` | Palette | Watch the always-on heartbeat hold one continuous life (closure read-not-repaired over uptime + learned-while-idle). |
|
||||
| Evidence | Vault | `/vault` | `⌘8` | Inspect persisted vault metadata when persistence exists. |
|
||||
| Evidence | Audit | `/audit` | `⌘9` | Read deterministic audit events. |
|
||||
| Discipline | Evals | `/evals` | `⌘5` | Run/read allowlisted eval lanes and wrong=0 ledgers. |
|
||||
|
|
@ -93,6 +94,7 @@ the command palette.
|
|||
| Demos | Registered demo scenarios pass or fail with recorded scenario evidence and proof/entailment DAGs where the demo emits them. |
|
||||
| Proposals | Proposal evidence, replay facts, and ratification commands are inspectable without applying mutation. |
|
||||
| Runs | Recorded run/session references, checkpoint gaps, and identity-continuity verdicts are discoverable. |
|
||||
| Lived Life | A persisted always-on run shows the engine living + learning over uptime, with closure (`versor_condition < 1e-6`) read as evidence each beat (never repaired); the surface's `closure_held` is consistency-checked against the per-beat measurements, so it can never paint a breached field as valid. |
|
||||
| Vault | Persisted vault metadata is inspectable when persistence is configured. |
|
||||
| Audit | Audit events are readable with payload digests and mutation-boundary flags. |
|
||||
| Evals | Allowlisted eval lanes and wrong/correct/refused metrics are visible. |
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ Wrong=0 / no-hot-path-repair are preserved by composition — the heartbeat only
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from chat.always_on import CLOSURE_CEILING, run_continuous
|
||||
import json
|
||||
|
||||
from chat.always_on import CLOSURE_CEILING, LIVED_LIFE_FILENAME, run_continuous
|
||||
from chat.runtime import ChatRuntime, RuntimeConfig
|
||||
from core.cognition.pipeline import CognitiveTurnPipeline
|
||||
from generate.meaning_graph.reader import comprehend
|
||||
|
|
@ -93,6 +95,30 @@ def test_life_survives_interruption_as_the_same_life(tmp_path) -> None:
|
|||
assert "mortal" in _stored_members(resumed._context, "socrates")
|
||||
|
||||
|
||||
def test_persisted_run_is_a_readable_lived_life_surface(tmp_path) -> None:
|
||||
"""run_continuous(report_path=...) leaves the workbench a readable, validated surface —
|
||||
the persist seam the Lived Life route reads (engine_state/lived_life.json)."""
|
||||
from workbench.lived_life import lived_life_from_payload
|
||||
|
||||
state_dir = tmp_path / "engine_state"
|
||||
runtime = ChatRuntime(config=_RESUME_CONFIG, engine_state_path=state_dir)
|
||||
_seed_continuous_life(runtime)
|
||||
|
||||
report_path = state_dir / LIVED_LIFE_FILENAME
|
||||
report = run_continuous(runtime, heartbeats=4, report_path=report_path)
|
||||
|
||||
# The artifact exists where the workbench reads it, and round-trips through the
|
||||
# read-model honesty gate (closure_held / totals consistent with the per-beat bytes).
|
||||
assert report_path.exists()
|
||||
raw = json.loads(report_path.read_text(encoding="utf-8"))
|
||||
surface = lived_life_from_payload(raw)
|
||||
assert surface.status == "recorded"
|
||||
assert surface.heartbeats == report.heartbeats == 4
|
||||
assert surface.closure_observed and surface.closure_held
|
||||
assert surface.total_facts_consolidated == report.total_facts_consolidated >= 1
|
||||
assert surface.converged is True # the saturated life is at rest by the final beat
|
||||
|
||||
|
||||
def test_heartbeat_never_repairs_closure(tmp_path) -> None:
|
||||
"""The heartbeat READS versor_condition as evidence; it must never repair the field.
|
||||
A no-op idle life (nothing to learn) keeps closure stable without any mutation."""
|
||||
|
|
|
|||
239
tests/test_workbench_lived_life.py
Normal file
239
tests/test_workbench_lived_life.py
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
"""Lived Life surface — the read-only projection of a persisted always-on run.
|
||||
|
||||
These prove the surface is HONEST BY CONSTRUCTION: an absent artifact is honest absence,
|
||||
a recorded artifact projects faithfully, and a TAMPERED artifact (a beat that lies about
|
||||
its closure, an inflated aggregate, a miscounted total) makes ``validate`` raise rather
|
||||
than render a false continuity claim. That fail-loud-on-tamper property is the wrong=0
|
||||
analogue for the continuity surface (CLAUDE.md Schema-Defined Proof Obligations): each
|
||||
mutation below would silently pass if the gate were decoration.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from chat.always_on import (
|
||||
AlwaysOnReport,
|
||||
HeartbeatRecord,
|
||||
serialize_report,
|
||||
write_lived_life,
|
||||
)
|
||||
from workbench import readers
|
||||
from workbench.lived_life import (
|
||||
lived_life_from_payload,
|
||||
missing_lived_life,
|
||||
validate,
|
||||
)
|
||||
from workbench.schemas import LivedLife, LivedLifeHeartbeat
|
||||
|
||||
|
||||
def _report() -> AlwaysOnReport:
|
||||
"""A representative life: closure observed + held, learns on beat 0, at rest on beat 1."""
|
||||
records = (
|
||||
HeartbeatRecord(
|
||||
tick=0,
|
||||
versor_condition=8.2e-13,
|
||||
field_valid=True,
|
||||
facts_consolidated=1,
|
||||
proposals_created=0,
|
||||
pending_proposals=0,
|
||||
did_work=True,
|
||||
),
|
||||
HeartbeatRecord(
|
||||
tick=1,
|
||||
versor_condition=8.2e-13,
|
||||
field_valid=True,
|
||||
facts_consolidated=0,
|
||||
proposals_created=0,
|
||||
pending_proposals=0,
|
||||
did_work=False,
|
||||
),
|
||||
)
|
||||
return AlwaysOnReport(
|
||||
records=records,
|
||||
identity="sha256:lived-life-test",
|
||||
closure_observed=True,
|
||||
closure_held=True,
|
||||
final_checkpoint_ok=True,
|
||||
total_facts_consolidated=1,
|
||||
total_proposals_created=0,
|
||||
)
|
||||
|
||||
|
||||
def test_missing_artifact_is_honest_absence() -> None:
|
||||
surface = missing_lived_life("no always-on run has been persisted yet")
|
||||
assert surface.status == "missing_evidence"
|
||||
assert surface.identity is None
|
||||
assert surface.heartbeats == 0
|
||||
# An absent life claims nothing.
|
||||
assert surface.closure_observed is False and surface.closure_held is False
|
||||
assert surface.records == []
|
||||
|
||||
|
||||
def test_recorded_artifact_projects_faithfully() -> None:
|
||||
surface = lived_life_from_payload(serialize_report(_report()))
|
||||
assert surface.status == "recorded"
|
||||
assert surface.identity == "sha256:lived-life-test"
|
||||
assert surface.heartbeats == 2
|
||||
assert surface.closure_observed and surface.closure_held
|
||||
assert surface.total_facts_consolidated == 1
|
||||
# CONVERGED is derived from the records (final beat at rest), not trusted from bytes.
|
||||
assert surface.converged is True
|
||||
assert [b.tick for b in surface.records] == [0, 1]
|
||||
# No current identity supplied -> the resume verdict is honestly unknown.
|
||||
assert surface.resume_status == "unknown"
|
||||
|
||||
|
||||
def test_resume_verdict_tracks_identity_vs_substrate() -> None:
|
||||
payload = serialize_report(_report()) # identity == "sha256:lived-life-test"
|
||||
# Matching substrate identity -> a reboot resumes THIS life.
|
||||
same = lived_life_from_payload(payload, current_identity="sha256:lived-life-test")
|
||||
assert same.resume_status == "would_resume"
|
||||
assert "resumes this life" in same.resume_summary
|
||||
# Changed substrate identity -> a reboot would refuse (IdentityContinuityError).
|
||||
changed = lived_life_from_payload(payload, current_identity="sha256:other")
|
||||
assert changed.resume_status == "substrate_changed"
|
||||
assert "refuse" in changed.resume_summary
|
||||
# No recomputable substrate identity -> honest unknown.
|
||||
unknown = lived_life_from_payload(payload, current_identity=None)
|
||||
assert unknown.resume_status == "unknown"
|
||||
|
||||
|
||||
def test_reader_reads_persisted_artifact(tmp_path, monkeypatch) -> None:
|
||||
state_dir = tmp_path / "engine_state"
|
||||
state_dir.mkdir()
|
||||
write_lived_life(_report(), state_dir / "lived_life.json")
|
||||
monkeypatch.setattr(readers, "ENGINE_STATE_ROOT", state_dir)
|
||||
|
||||
surface = readers.lived_life()
|
||||
assert surface.status == "recorded"
|
||||
assert surface.closure_held and surface.converged
|
||||
# The surface links to the raw artifact for provenance.
|
||||
assert surface.artifact is not None
|
||||
assert surface.artifact.kind == "lived_life"
|
||||
|
||||
|
||||
def test_reader_absent_artifact(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setattr(readers, "ENGINE_STATE_ROOT", tmp_path / "engine_state")
|
||||
surface = readers.lived_life()
|
||||
assert surface.status == "missing_evidence"
|
||||
|
||||
|
||||
def test_unrecognized_schema_is_absence_not_error() -> None:
|
||||
payload = serialize_report(_report())
|
||||
payload["schema_version"] = "lived_life_v999"
|
||||
surface = lived_life_from_payload(payload)
|
||||
assert surface.status == "missing_evidence"
|
||||
assert "schema_version" in (surface.missing_reason or "")
|
||||
|
||||
|
||||
# --- Non-vacuous tamper rejections: each mutation MUST make validate raise. ---
|
||||
|
||||
|
||||
def test_tamper_field_valid_lies_about_breach_is_rejected() -> None:
|
||||
payload = serialize_report(_report())
|
||||
# A beat breaches the ceiling but still claims a valid field.
|
||||
payload["records"][0]["versor_condition"] = 5e-3
|
||||
payload["records"][0]["field_valid"] = True
|
||||
with pytest.raises(ValueError, match="field_valid"):
|
||||
lived_life_from_payload(payload)
|
||||
|
||||
|
||||
def test_tamper_inflated_closure_held_is_rejected() -> None:
|
||||
payload = serialize_report(_report())
|
||||
# An honest beat breaches the ceiling, but the aggregate lies that closure held.
|
||||
payload["records"][0]["versor_condition"] = 5e-3
|
||||
payload["records"][0]["field_valid"] = False
|
||||
payload["closure_held"] = True
|
||||
with pytest.raises(ValueError, match="closure_held"):
|
||||
lived_life_from_payload(payload)
|
||||
|
||||
|
||||
def test_tamper_closure_observed_is_rejected() -> None:
|
||||
payload = serialize_report(_report())
|
||||
# No beat observed a field, but the surface claims one did.
|
||||
for rec in payload["records"]:
|
||||
rec["versor_condition"] = None
|
||||
rec["field_valid"] = True
|
||||
payload["closure_observed"] = True
|
||||
payload["closure_held"] = True
|
||||
with pytest.raises(ValueError, match="closure_observed"):
|
||||
lived_life_from_payload(payload)
|
||||
|
||||
|
||||
def test_tamper_miscounted_total_is_rejected() -> None:
|
||||
payload = serialize_report(_report())
|
||||
payload["total_facts_consolidated"] = 99 # records sum to 1
|
||||
with pytest.raises(ValueError, match="total_facts_consolidated"):
|
||||
lived_life_from_payload(payload)
|
||||
|
||||
|
||||
def test_tamper_converged_mismatch_is_rejected() -> None:
|
||||
# Direct construction: a recorded surface whose `converged` contradicts the final beat.
|
||||
beats = [
|
||||
LivedLifeHeartbeat(
|
||||
tick=0,
|
||||
versor_condition=8.2e-13,
|
||||
field_valid=True,
|
||||
facts_consolidated=0,
|
||||
proposals_created=0,
|
||||
pending_proposals=0,
|
||||
did_work=True, # final beat still working -> NOT converged
|
||||
)
|
||||
]
|
||||
surface = LivedLife(
|
||||
schema_version="lived_life_v1",
|
||||
status="recorded",
|
||||
missing_reason=None,
|
||||
identity="sha256:x",
|
||||
heartbeats=1,
|
||||
closure_observed=True,
|
||||
closure_held=True,
|
||||
closure_ceiling=1e-6,
|
||||
final_checkpoint_ok=True,
|
||||
converged=True, # lies: the final beat did work
|
||||
total_facts_consolidated=0,
|
||||
total_proposals_created=0,
|
||||
current_identity="sha256:x",
|
||||
resume_status="would_resume",
|
||||
resume_summary="a reboot resumes this life — its identity matches the current substrate",
|
||||
records=beats,
|
||||
)
|
||||
with pytest.raises(ValueError, match="converged"):
|
||||
validate(surface)
|
||||
|
||||
|
||||
def test_tamper_resume_status_mismatch_is_rejected() -> None:
|
||||
# Direct construction: a surface that claims "would_resume" while the persisted identity
|
||||
# differs from the current substrate identity — a reboot would actually refuse.
|
||||
surface = LivedLife(
|
||||
schema_version="lived_life_v1",
|
||||
status="recorded",
|
||||
missing_reason=None,
|
||||
identity="sha256:persisted",
|
||||
heartbeats=0,
|
||||
closure_observed=False,
|
||||
closure_held=True, # vacuously true with no observed field (passes the closure gate)
|
||||
closure_ceiling=1e-6,
|
||||
final_checkpoint_ok=True,
|
||||
converged=False,
|
||||
total_facts_consolidated=0,
|
||||
total_proposals_created=0,
|
||||
current_identity="sha256:current-DIFFERENT",
|
||||
resume_status="would_resume", # lies: identities differ -> substrate_changed
|
||||
resume_summary="a reboot resumes this life — its identity matches the current substrate",
|
||||
records=[],
|
||||
)
|
||||
with pytest.raises(ValueError, match="resume_status"):
|
||||
validate(surface)
|
||||
|
||||
|
||||
def test_persist_is_deterministic() -> None:
|
||||
report = _report()
|
||||
once = serialize_report(report)
|
||||
twice = serialize_report(report)
|
||||
assert once == twice
|
||||
# And the written bytes are stable (sorted keys), so the artifact is replay-citable.
|
||||
import json
|
||||
|
||||
assert json.dumps(once, sort_keys=True) == json.dumps(twice, sort_keys=True)
|
||||
|
|
@ -237,6 +237,34 @@
|
|||
"source_digest",
|
||||
"calibration_evidence_ref"
|
||||
],
|
||||
"LivedLife": [
|
||||
"schema_version",
|
||||
"status",
|
||||
"missing_reason",
|
||||
"identity",
|
||||
"heartbeats",
|
||||
"closure_observed",
|
||||
"closure_held",
|
||||
"closure_ceiling",
|
||||
"final_checkpoint_ok",
|
||||
"converged",
|
||||
"total_facts_consolidated",
|
||||
"total_proposals_created",
|
||||
"current_identity",
|
||||
"resume_status",
|
||||
"resume_summary",
|
||||
"records",
|
||||
"artifact"
|
||||
],
|
||||
"LivedLifeHeartbeat": [
|
||||
"tick",
|
||||
"versor_condition",
|
||||
"field_valid",
|
||||
"facts_consolidated",
|
||||
"proposals_created",
|
||||
"pending_proposals",
|
||||
"did_work"
|
||||
],
|
||||
"LogosAlignmentRow": [
|
||||
"edge_id",
|
||||
"source_id",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import type {
|
|||
FieldEvidence,
|
||||
EvidenceBundle,
|
||||
DeterminismTour,
|
||||
LivedLife,
|
||||
TurnJournalEntry,
|
||||
TurnJournalSummary,
|
||||
ContemplationRunDetail,
|
||||
|
|
@ -210,6 +211,10 @@ export async function fetchTour(): Promise<DeterminismTour> {
|
|||
return apiFetch<DeterminismTour>("/tour");
|
||||
}
|
||||
|
||||
export async function fetchLivedLife(): Promise<LivedLife> {
|
||||
return apiFetch<LivedLife>("/lived-life");
|
||||
}
|
||||
|
||||
export async function fetchAuditEvents(
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
fetchTraceField,
|
||||
fetchTraceBundle,
|
||||
fetchTour,
|
||||
fetchLivedLife,
|
||||
fetchTraceTurns,
|
||||
fetchContemplationRun,
|
||||
fetchContemplationRuns,
|
||||
|
|
@ -71,6 +72,7 @@ import type {
|
|||
FieldEvidence,
|
||||
EvidenceBundle,
|
||||
DeterminismTour,
|
||||
LivedLife,
|
||||
TurnJournalEntry,
|
||||
TurnJournalSummary,
|
||||
EvalLaneSummary,
|
||||
|
|
@ -255,6 +257,16 @@ export function useTour() {
|
|||
});
|
||||
}
|
||||
|
||||
export function useLivedLife() {
|
||||
return useQuery<LivedLife, WorkbenchApiError>({
|
||||
queryKey: ["api", "lived-life"],
|
||||
queryFn: fetchLivedLife,
|
||||
// The life advances over uptime — keep it fresh without hammering.
|
||||
refetchInterval: 30_000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAuditEvents(limit?: number, offset?: number) {
|
||||
return useQuery<{ items: AuditEvent[] }, WorkbenchApiError>({
|
||||
queryKey: ["api", "audit", "events", limit ?? null, offset ?? null],
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ const ProposalsRoute = lazy(() =>
|
|||
const RunsRoute = lazy(() =>
|
||||
import("./runs/RunsRoute").then((module) => ({ default: module.RunsRoute })),
|
||||
);
|
||||
const LivedLifeRoute = lazy(() =>
|
||||
import("./lived-life/LivedLifeRoute").then((module) => ({
|
||||
default: module.LivedLifeRoute,
|
||||
})),
|
||||
);
|
||||
const VaultRoute = lazy(() =>
|
||||
import("./vault/VaultRoute").then((module) => ({ default: module.VaultRoute })),
|
||||
);
|
||||
|
|
@ -87,6 +92,7 @@ export const ROUTE_ELEMENTS: RouteElementMap = {
|
|||
demos: lazyRoute(<DemoTheaterRoute />),
|
||||
proposals: lazyRoute(<ProposalsRoute />),
|
||||
runs: lazyRoute(<RunsRoute />),
|
||||
"lived-life": lazyRoute(<LivedLifeRoute />),
|
||||
vault: lazyRoute(<VaultRoute />),
|
||||
audit: lazyRoute(<AuditRoute />),
|
||||
evals: lazyRoute(<EvalsRoute />),
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@ describe("Shell", () => {
|
|||
expect(document.querySelector('[data-density="compact"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("LeftNav has exactly 15 items in section-grouped order", () => {
|
||||
it("LeftNav has exactly 16 items in section-grouped order", () => {
|
||||
renderShell();
|
||||
const nav = document.querySelector('[data-region="leftnav"]')!;
|
||||
const links = nav.querySelectorAll("a");
|
||||
expect(links).toHaveLength(15);
|
||||
expect(links).toHaveLength(16);
|
||||
const labels = Array.from(links).map((l) => l.textContent);
|
||||
// Grouped by section (Converse → Cognition → Determinism → Evidence →
|
||||
// Discipline → Substrate → Settings), derived from the route registry.
|
||||
|
|
@ -102,6 +102,7 @@ describe("Shell", () => {
|
|||
"Demos",
|
||||
"Proposals",
|
||||
"Runs",
|
||||
"Lived Life",
|
||||
"Vault",
|
||||
"Audit",
|
||||
"Evals",
|
||||
|
|
|
|||
198
workbench-ui/src/app/lived-life/LivedLifeRoute.test.tsx
Normal file
198
workbench-ui/src/app/lived-life/LivedLifeRoute.test.tsx
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestQueryClient } from "../../test/createTestQueryClient";
|
||||
import { LivedLifeRoute, LIVED_LIFE_ABSENCE_STATEMENT } from "./LivedLifeRoute";
|
||||
import type { LivedLife } from "../../types/api";
|
||||
|
||||
/**
|
||||
* The Lived Life surface must render the continuous life HONESTLY: it shows the
|
||||
* heartbeat-over-uptime facts when a life is recorded, and it can NEVER paint a
|
||||
* breached field as valid. These assert the rendered content, not just that the
|
||||
* route mounts (the frontend analogue of the backend's non-vacuous tamper tests).
|
||||
*/
|
||||
|
||||
const GENERATED_AT = "2026-06-14T00:00:00Z";
|
||||
|
||||
function stubFetch(life: LivedLife) {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(() =>
|
||||
Promise.resolve({
|
||||
json: async () => ({ ok: true, generated_at: GENERATED_AT, data: life }),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function renderRoute() {
|
||||
return render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<MemoryRouter initialEntries={["/lived-life"]}>
|
||||
<LivedLifeRoute />
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
const HEALTHY: LivedLife = {
|
||||
schema_version: "lived_life_v1",
|
||||
status: "recorded",
|
||||
missing_reason: null,
|
||||
identity: "sha256:abcdef0123456789",
|
||||
heartbeats: 3,
|
||||
closure_observed: true,
|
||||
closure_held: true,
|
||||
closure_ceiling: 1e-6,
|
||||
final_checkpoint_ok: true,
|
||||
converged: true,
|
||||
total_facts_consolidated: 2,
|
||||
total_proposals_created: 0,
|
||||
current_identity: "sha256:abcdef0123456789", // matches identity -> would_resume
|
||||
resume_status: "would_resume",
|
||||
resume_summary:
|
||||
"a reboot resumes this life — its identity matches the current substrate",
|
||||
records: [
|
||||
{
|
||||
tick: 0,
|
||||
versor_condition: 8.2e-13,
|
||||
field_valid: true,
|
||||
facts_consolidated: 2,
|
||||
proposals_created: 0,
|
||||
pending_proposals: 0,
|
||||
did_work: true,
|
||||
},
|
||||
{
|
||||
tick: 1,
|
||||
versor_condition: 8.2e-13,
|
||||
field_valid: true,
|
||||
facts_consolidated: 0,
|
||||
proposals_created: 0,
|
||||
pending_proposals: 0,
|
||||
did_work: false,
|
||||
},
|
||||
{
|
||||
tick: 2,
|
||||
versor_condition: 8.2e-13,
|
||||
field_valid: true,
|
||||
facts_consolidated: 0,
|
||||
proposals_created: 0,
|
||||
pending_proposals: 0,
|
||||
did_work: false,
|
||||
},
|
||||
],
|
||||
artifact: {
|
||||
artifact_id: "engine_state/lived_life.json",
|
||||
kind: "lived_life",
|
||||
path: "engine_state/lived_life.json",
|
||||
digest: "sha256:deadbeefcafe",
|
||||
created_at: null,
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("LivedLifeRoute", () => {
|
||||
it("renders the continuous life: heartbeats, closure held, learned-while-idle, beats", async () => {
|
||||
stubFetch(HEALTHY);
|
||||
renderRoute();
|
||||
|
||||
expect(await screen.findByText("One continuous life")).toBeInTheDocument();
|
||||
// Learned while idle (non-zero, from the records).
|
||||
expect(screen.getByText("2 learned while idle")).toBeInTheDocument();
|
||||
// Closure held below the ceiling (never claimed without observation).
|
||||
expect(screen.getAllByText(/closure held/i).length).toBeGreaterThan(0);
|
||||
// The heartbeat over uptime: each beat is shown.
|
||||
expect(screen.getByText("beat 0")).toBeInTheDocument();
|
||||
expect(screen.getByText("beat 2")).toBeInTheDocument();
|
||||
// A saturated life is at rest.
|
||||
expect(screen.getAllByText(/at rest/i).length).toBeGreaterThan(0);
|
||||
// The resume-as-same-life verdict is made felt: this life wakes up as itself.
|
||||
expect(screen.getByText(/resumes as same life/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/its identity matches the current substrate/i),
|
||||
).toBeInTheDocument();
|
||||
// The per-reboot lineage chain is honestly cross-linked to Runs.
|
||||
expect(screen.getByRole("link", { name: /Runs/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("a breached beat surfaces the closure warning, never a false 'held'", async () => {
|
||||
const breached: LivedLife = {
|
||||
...HEALTHY,
|
||||
heartbeats: 1,
|
||||
closure_held: false,
|
||||
converged: true,
|
||||
records: [
|
||||
{
|
||||
tick: 0,
|
||||
versor_condition: 5e-3, // breaches the 1e-6 ceiling
|
||||
field_valid: false,
|
||||
facts_consolidated: 0,
|
||||
proposals_created: 0,
|
||||
pending_proposals: 0,
|
||||
did_work: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
stubFetch(breached);
|
||||
renderRoute();
|
||||
|
||||
expect(await screen.findByText(/closure BREACHED/i)).toBeInTheDocument();
|
||||
// The breached beat is marked INVALID, never painted valid.
|
||||
expect(screen.getByText("INVALID")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/closure held/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("a changed substrate surfaces the would-refuse warning, never a false resume", async () => {
|
||||
const changed: LivedLife = {
|
||||
...HEALTHY,
|
||||
current_identity: "sha256:0000different0000",
|
||||
resume_status: "substrate_changed",
|
||||
resume_summary:
|
||||
"the substrate changed — a reboot would refuse (IdentityContinuityError)",
|
||||
};
|
||||
stubFetch(changed);
|
||||
renderRoute();
|
||||
|
||||
expect(
|
||||
(await screen.findAllByText(/substrate changed/i)).length,
|
||||
).toBeGreaterThan(0);
|
||||
expect(
|
||||
screen.getByText(/a reboot would refuse/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText(/resumes as same life/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("an absent artifact shows honest absence, never a fabricated life", async () => {
|
||||
const absent: LivedLife = {
|
||||
...HEALTHY,
|
||||
status: "missing_evidence",
|
||||
missing_reason: "no always-on run has been persisted yet",
|
||||
identity: null,
|
||||
heartbeats: 0,
|
||||
closure_observed: false,
|
||||
closure_held: false,
|
||||
converged: false,
|
||||
total_facts_consolidated: 0,
|
||||
total_proposals_created: 0,
|
||||
current_identity: null,
|
||||
resume_status: "unknown",
|
||||
resume_summary:
|
||||
"resume verdict unavailable — the current substrate identity could not be recomputed",
|
||||
records: [],
|
||||
artifact: null,
|
||||
};
|
||||
stubFetch(absent);
|
||||
renderRoute();
|
||||
|
||||
expect(
|
||||
await screen.findByText(LIVED_LIFE_ABSENCE_STATEMENT),
|
||||
).toBeInTheDocument();
|
||||
// No fabricated heartbeat content.
|
||||
expect(screen.queryByText("One continuous life")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("beat 0")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
365
workbench-ui/src/app/lived-life/LivedLifeRoute.tsx
Normal file
365
workbench-ui/src/app/lived-life/LivedLifeRoute.tsx
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
import { useState, type ReactNode } from "react";
|
||||
import { Activity, HeartPulse } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { WorkbenchApiError } from "../../api/client";
|
||||
import { useLivedLife } from "../../api/queries";
|
||||
import { DigestBadge } from "../../design/components/DigestBadge/DigestBadge";
|
||||
import { MetadataTable } from "../../design/components/MetadataTable/MetadataTable";
|
||||
import { Panel } from "../../design/components/Panel/Panel";
|
||||
import { StableJsonViewer } from "../../design/components/StableJsonViewer";
|
||||
import { Button } from "../../design/components/primitives/Button";
|
||||
import { EmptyState } from "../../design/components/states/EmptyState";
|
||||
import { ErrorState } from "../../design/components/states/ErrorState";
|
||||
import { LoadingState } from "../../design/components/states/LoadingState";
|
||||
import type { LivedLife, LivedLifeHeartbeat } from "../../types/api";
|
||||
|
||||
// Conformance contract strings (ADR-0162 §6) — kept verbatim in
|
||||
// routeConformance.test.tsx; the absence state IS the primary state until an
|
||||
// always-on run lands an artifact (fail-closed, like Vault/Calibration).
|
||||
export const LIVED_LIFE_LOADING = "Loading lived life...";
|
||||
export const LIVED_LIFE_ABSENCE_STATEMENT =
|
||||
"No always-on run recorded yet. The continuous-life heartbeat persists its evidence here when it runs.";
|
||||
export const LIVED_LIFE_ABSENCE_ACTION =
|
||||
"Run the always-on heartbeat to persist engine_state/lived_life.json";
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof WorkbenchApiError
|
||||
? error.message
|
||||
: "Lived-life request failed.";
|
||||
}
|
||||
|
||||
function digestPayload(value: string | null | undefined): string | null {
|
||||
if (!value) return null;
|
||||
return value.replace(/^sha256:/, "");
|
||||
}
|
||||
|
||||
function closureDisplay(versorCondition: number | null): string {
|
||||
return typeof versorCondition === "number"
|
||||
? versorCondition.toExponential(3)
|
||||
: "no field yet";
|
||||
}
|
||||
|
||||
/** A small status pill. `tone` maps to a state color token. */
|
||||
function Pill({
|
||||
tone,
|
||||
children,
|
||||
}: {
|
||||
tone: "verified" | "warning" | "muted";
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const color =
|
||||
tone === "verified"
|
||||
? "var(--color-state-verified)"
|
||||
: tone === "warning"
|
||||
? "var(--color-state-warning-text)"
|
||||
: "var(--color-text-secondary)";
|
||||
return (
|
||||
<span
|
||||
className="inline-flex h-6 items-center gap-1 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface-inset)] px-2 text-xs"
|
||||
style={{ color }}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** The headline: one continuous life, stated as falsifiable facts. */
|
||||
function LifeHeadline({ life }: { life: LivedLife }) {
|
||||
const closureTone = life.closure_held ? "verified" : "warning";
|
||||
return (
|
||||
<Panel
|
||||
title="One continuous life"
|
||||
toolbar={
|
||||
<span className="inline-flex items-center gap-1 text-[var(--color-text-muted)]">
|
||||
<HeartPulse size={14} aria-hidden />
|
||||
<span className="text-xs">always-on heartbeat</span>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-3">
|
||||
<p className="m-0 text-sm text-[var(--color-text-secondary)] [text-wrap:balance]">
|
||||
CORE held itself alive over {life.heartbeats}{" "}
|
||||
{life.heartbeats === 1 ? "heartbeat" : "heartbeats"} with no user turn,
|
||||
learned while idle, and kept its field valid by construction — the
|
||||
heartbeat READS closure as evidence, it never repairs it.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Pill tone="muted">
|
||||
{life.heartbeats}{" "}
|
||||
{life.heartbeats === 1 ? "heartbeat" : "heartbeats"}
|
||||
</Pill>
|
||||
<Pill tone={closureTone}>
|
||||
{life.closure_observed
|
||||
? life.closure_held
|
||||
? `closure held < ${life.closure_ceiling.toExponential(0)}`
|
||||
: "closure BREACHED"
|
||||
: "no field observed"}
|
||||
</Pill>
|
||||
<Pill tone={life.total_facts_consolidated > 0 ? "verified" : "muted"}>
|
||||
{life.total_facts_consolidated} learned while idle
|
||||
</Pill>
|
||||
{life.total_proposals_created > 0 ? (
|
||||
<Pill tone="muted">
|
||||
{life.total_proposals_created} proposals (reviewable)
|
||||
</Pill>
|
||||
) : null}
|
||||
<Pill tone={life.converged ? "verified" : "muted"}>
|
||||
{life.converged ? "at rest" : "still churning"}
|
||||
</Pill>
|
||||
<Pill tone={resumeTone(life.resume_status)}>
|
||||
{life.resume_status === "would_resume"
|
||||
? "resumes as same life"
|
||||
: life.resume_status === "substrate_changed"
|
||||
? "substrate changed"
|
||||
: "resume unknown"}
|
||||
</Pill>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
function resumeTone(
|
||||
status: LivedLife["resume_status"],
|
||||
): "verified" | "warning" | "muted" {
|
||||
if (status === "would_resume") return "verified";
|
||||
if (status === "substrate_changed") return "warning";
|
||||
return "muted";
|
||||
}
|
||||
|
||||
function LifeSummary({ life }: { life: LivedLife }) {
|
||||
const identity = digestPayload(life.identity);
|
||||
const current = digestPayload(life.current_identity);
|
||||
const artifact = digestPayload(life.artifact?.digest);
|
||||
return (
|
||||
<MetadataTable
|
||||
rows={[
|
||||
{
|
||||
key: "identity",
|
||||
value: identity ? (
|
||||
<DigestBadge digest={identity} truncate={16} />
|
||||
) : (
|
||||
<span className="text-[var(--color-text-muted)]">not recorded</span>
|
||||
),
|
||||
},
|
||||
{ key: "heartbeats", value: String(life.heartbeats), mono: true },
|
||||
{
|
||||
key: "closure_observed",
|
||||
value: life.closure_observed ? "yes" : "no field this run",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "closure_held",
|
||||
value: life.closure_held
|
||||
? `held (< ${life.closure_ceiling.toExponential(0)})`
|
||||
: "BREACHED",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "facts_consolidated",
|
||||
value: String(life.total_facts_consolidated),
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "proposals_created",
|
||||
value: String(life.total_proposals_created),
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "converged",
|
||||
value: life.converged ? "yes (final beat at rest)" : "no (still working)",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "final_checkpoint_ok",
|
||||
value: life.final_checkpoint_ok ? "yes" : "FAILED",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "current_identity",
|
||||
value: current ? (
|
||||
<DigestBadge digest={current} truncate={16} />
|
||||
) : (
|
||||
<span className="text-[var(--color-text-muted)]">not recomputed</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "artifact",
|
||||
value: artifact ? (
|
||||
<DigestBadge digest={artifact} truncate={12} />
|
||||
) : (
|
||||
<span className="text-[var(--color-text-muted)]">not linked</span>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function HeartbeatRow({ beat }: { beat: LivedLifeHeartbeat }) {
|
||||
const valid = beat.field_valid;
|
||||
return (
|
||||
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 border-b border-[var(--color-border-subtle)] px-3 py-2 last:border-b-0">
|
||||
<span className="font-mono text-xs text-[var(--color-text-muted)]">
|
||||
beat {beat.tick}
|
||||
</span>
|
||||
<span className="flex items-center gap-2 text-xs">
|
||||
<span
|
||||
className="font-mono"
|
||||
style={{
|
||||
color: valid
|
||||
? "var(--color-state-verified)"
|
||||
: "var(--color-state-warning-text)",
|
||||
}}
|
||||
title="versor_condition (field closure) — read, never repaired"
|
||||
>
|
||||
{closureDisplay(beat.versor_condition)}
|
||||
</span>
|
||||
<span className="text-[var(--color-text-muted)]">
|
||||
{valid ? "valid" : "INVALID"}
|
||||
</span>
|
||||
</span>
|
||||
<span className="justify-self-end text-xs text-[var(--color-text-secondary)]">
|
||||
{beat.did_work ? (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Activity size={12} aria-hidden />
|
||||
{beat.facts_consolidated > 0
|
||||
? `+${beat.facts_consolidated} fact${beat.facts_consolidated === 1 ? "" : "s"}`
|
||||
: null}
|
||||
{beat.proposals_created > 0
|
||||
? ` +${beat.proposals_created} prop${beat.proposals_created === 1 ? "" : "s"}`
|
||||
: null}
|
||||
{beat.facts_consolidated === 0 && beat.proposals_created === 0
|
||||
? "working"
|
||||
: null}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[var(--color-text-muted)]">at rest</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeartbeatTimeline({ life }: { life: LivedLife }) {
|
||||
if (life.records.length === 0) {
|
||||
return (
|
||||
<p className="m-0 text-sm text-[var(--color-text-secondary)]">
|
||||
This run recorded no heartbeats.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<p className="m-0 text-xs text-[var(--color-text-muted)]">
|
||||
Each beat advances continuous learning, then reads the field's closure
|
||||
(<span className="font-mono">versor_condition</span>) as evidence. Closure
|
||||
stays flat below the ceiling across the life — the engine never patches the
|
||||
field to keep it valid.
|
||||
</p>
|
||||
<div className="rounded-md border border-[var(--color-border-subtle)]">
|
||||
{life.records.map((beat) => (
|
||||
<HeartbeatRow key={beat.tick} beat={beat} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ResumeVerdict({ life }: { life: LivedLife }) {
|
||||
const tone = resumeTone(life.resume_status);
|
||||
const color =
|
||||
tone === "verified"
|
||||
? "var(--color-state-verified)"
|
||||
: tone === "warning"
|
||||
? "var(--color-state-warning-text)"
|
||||
: "var(--color-text-muted)";
|
||||
return (
|
||||
<div
|
||||
className="grid gap-1 border-l-2 pl-3"
|
||||
style={{ borderColor: color }}
|
||||
>
|
||||
<p className="m-0 text-sm" style={{ color }}>
|
||||
{life.resume_summary}
|
||||
</p>
|
||||
<p className="m-0 text-xs text-[var(--color-text-secondary)] [text-wrap:balance]">
|
||||
This is the resume guarantee made felt: a reboot recomputes the engine
|
||||
identity and refuses if it differs from the persisted one, so this life
|
||||
wakes up as <em>itself</em>, not a copy. The per-reboot lineage chain
|
||||
lives under{" "}
|
||||
<Link
|
||||
to="/runs"
|
||||
className="text-[var(--color-text-primary)] underline underline-offset-2"
|
||||
>
|
||||
Runs › Identity
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RawJson({ life }: { life: LivedLife }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
return expanded ? (
|
||||
<StableJsonViewer source={JSON.stringify(life, null, 2)} />
|
||||
) : (
|
||||
<div className="grid justify-items-start gap-2">
|
||||
<p className="m-0 text-sm text-[var(--color-text-secondary)]">
|
||||
Raw lived-life JSON is collapsed by default.
|
||||
</p>
|
||||
<Button type="button" variant="quiet" onClick={() => setExpanded(true)}>
|
||||
Expand raw JSON
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LivedLifeRoute() {
|
||||
const query = useLivedLife();
|
||||
|
||||
if (query.isLoading) {
|
||||
return <LoadingState label={LIVED_LIFE_LOADING} />;
|
||||
}
|
||||
|
||||
if (query.isError) {
|
||||
return (
|
||||
<ErrorState
|
||||
whatFailed={errorMessage(query.error)}
|
||||
mutationStatus="No lived-life mutation occurred."
|
||||
reproducer="curl /lived-life"
|
||||
retrySafety="Retry: safe"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const life = query.data;
|
||||
if (!life || life.status !== "recorded") {
|
||||
return (
|
||||
<EmptyState
|
||||
statement={LIVED_LIFE_ABSENCE_STATEMENT}
|
||||
nextAction={LIVED_LIFE_ABSENCE_ACTION}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full min-h-0 overflow-y-auto">
|
||||
<div className="mx-auto grid max-w-3xl gap-4 p-1">
|
||||
<LifeHeadline life={life} />
|
||||
<Panel title="Life summary">
|
||||
<div className="grid gap-4">
|
||||
<LifeSummary life={life} />
|
||||
<ResumeVerdict life={life} />
|
||||
</div>
|
||||
</Panel>
|
||||
<Panel title="Heartbeat timeline">
|
||||
<HeartbeatTimeline life={life} />
|
||||
</Panel>
|
||||
<Panel title="Raw">
|
||||
<RawJson life={life} />
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -15,6 +15,12 @@ import { EvalsRoute } from "./evals/EvalsRoute";
|
|||
import { ReplayRoute } from "./replay/ReplayRoute";
|
||||
import { DemoTheaterRoute } from "./demos/DemoTheaterRoute";
|
||||
import { RunsRoute } from "./runs/RunsRoute";
|
||||
import {
|
||||
LivedLifeRoute,
|
||||
LIVED_LIFE_LOADING,
|
||||
LIVED_LIFE_ABSENCE_STATEMENT,
|
||||
LIVED_LIFE_ABSENCE_ACTION,
|
||||
} from "./lived-life/LivedLifeRoute";
|
||||
import { PacksRoute } from "./packs/PacksRoute";
|
||||
import { LogosRoute } from "./logos/LogosRoute";
|
||||
import { VaultRoute } from "./vault/VaultRoute";
|
||||
|
|
@ -168,6 +174,17 @@ const MOUNT_ROUTES: MountRouteSpec[] = [
|
|||
emptyStatement: "No runs recorded yet. Use Chat to create evidence.",
|
||||
emptyCommand: "core chat",
|
||||
},
|
||||
{
|
||||
name: "Lived Life",
|
||||
element: <LivedLifeRoute />,
|
||||
path: "/lived-life",
|
||||
initialEntry: "/lived-life",
|
||||
loadingLabel: LIVED_LIFE_LOADING,
|
||||
// Fail-closed: until an always-on run persists an artifact, honest absence
|
||||
// is the primary state (no fabricated life).
|
||||
emptyStatement: LIVED_LIFE_ABSENCE_STATEMENT,
|
||||
emptyCommand: LIVED_LIFE_ABSENCE_ACTION,
|
||||
},
|
||||
{
|
||||
name: "Replay",
|
||||
element: <ReplayRoute />,
|
||||
|
|
|
|||
|
|
@ -170,6 +170,19 @@ export const WORKBENCH_ROUTES: readonly WorkbenchRoute[] = [
|
|||
keyboardDigit: "6",
|
||||
routeConformanceRequired: true,
|
||||
},
|
||||
{
|
||||
id: "lived-life",
|
||||
path: "/lived-life",
|
||||
routePattern: "lived-life",
|
||||
label: "Lived Life",
|
||||
description: "Watch the always-on heartbeat hold one continuous life.",
|
||||
section: "Evidence",
|
||||
leftNavVisible: true,
|
||||
commandPaletteVisible: true,
|
||||
landingRouteAllowed: true,
|
||||
keyboardDigit: null,
|
||||
routeConformanceRequired: true,
|
||||
},
|
||||
{
|
||||
id: "vault",
|
||||
path: "/vault",
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ describe("CommandPalette keyboard contract", () => {
|
|||
expect(screen.getByRole("button", { name: "Open Trace" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Open Replay" })).toBeInTheDocument();
|
||||
|
||||
// One Navigate command per palette-visible route (15), derived from the
|
||||
// route registry — Demos, Calibration, Contemplation, Tour, and CORE-Logos
|
||||
// are included (the prior hand-maintained list of 10 dropped them).
|
||||
// One Navigate command per palette-visible route (16), derived from the
|
||||
// route registry — Demos, Calibration, Contemplation, Tour, CORE-Logos, and
|
||||
// Lived Life are included (the prior hand-maintained list of 10 dropped them).
|
||||
const items = dialog.querySelectorAll('[role="option"]');
|
||||
expect(items.length).toBe(15);
|
||||
expect(items.length).toBe(16);
|
||||
const lastIndex = items.length - 1;
|
||||
|
||||
// Initially first item (index 0) is focused — check aria-selected
|
||||
|
|
|
|||
|
|
@ -114,6 +114,53 @@ export interface FieldEvidence {
|
|||
transition_inner_product: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* One beat of the continuous life (read-only telemetry mirrored from
|
||||
* `chat.always_on.HeartbeatRecord`): the closure of the live field that beat
|
||||
* (`versor_condition`, READ never repaired), whether it held the `< ceiling`
|
||||
* invariant, and what the life learned that beat.
|
||||
*/
|
||||
export interface LivedLifeHeartbeat {
|
||||
tick: number;
|
||||
versor_condition: number | null;
|
||||
field_valid: boolean;
|
||||
facts_consolidated: number;
|
||||
proposals_created: number;
|
||||
pending_proposals: number;
|
||||
did_work: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* L10 lived-life surface — evidence that CORE is ONE continuous life. A read-only
|
||||
* projection of the persisted always-on run (`engine_state/lived_life.json`): the engine
|
||||
* holds itself alive over uptime with no user turn, learns while idle (Step-D
|
||||
* consolidation + proposal-only proposals), and holds closure BY CONSTRUCTION
|
||||
* (`versor_condition` is READ as evidence each beat, never repaired). `closure_held` is
|
||||
* consistency-checked against the per-beat measurements at construction (the wrong=0
|
||||
* analogue for the continuity surface); `converged` is honest telemetry (a saturated life
|
||||
* stops churning — the final beat did no work); `identity` is the life's content identity
|
||||
* (the "same life" thread, not a continuity proof — that is owned by Runs).
|
||||
*/
|
||||
export interface LivedLife {
|
||||
schema_version: "lived_life_v1";
|
||||
status: PipelineEvidenceStatus;
|
||||
missing_reason: string | null;
|
||||
identity: string | null;
|
||||
heartbeats: number;
|
||||
closure_observed: boolean;
|
||||
closure_held: boolean;
|
||||
closure_ceiling: number;
|
||||
final_checkpoint_ok: boolean;
|
||||
converged: boolean;
|
||||
total_facts_consolidated: number;
|
||||
total_proposals_created: number;
|
||||
current_identity: string | null;
|
||||
resume_status: "would_resume" | "substrate_changed" | "unknown";
|
||||
resume_summary: string;
|
||||
records: LivedLifeHeartbeat[];
|
||||
artifact: ArtifactRef | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* D3 shareable evidence bundle — a turn's deterministic evidence exported as one
|
||||
* content-addressed, citable artifact. `bundle_digest` content-addresses the
|
||||
|
|
@ -452,6 +499,7 @@ export type ArtifactKind =
|
|||
| "contemplation_report"
|
||||
| "telemetry"
|
||||
| "engine_state_manifest"
|
||||
| "lived_life"
|
||||
| "unknown";
|
||||
|
||||
export type ContentType = "json" | "jsonl" | "text" | "unknown";
|
||||
|
|
|
|||
|
|
@ -149,6 +149,8 @@ class WorkbenchApi:
|
|||
return ApiResponse(200, ok({"status": "ok"}))
|
||||
if method == "GET" and path == "/runtime/status":
|
||||
return ApiResponse(200, ok(readers.runtime_status()))
|
||||
if method == "GET" and path == "/lived-life":
|
||||
return ApiResponse(200, ok(readers.lived_life()))
|
||||
if method == "GET" and path == "/artifacts":
|
||||
limit = int(query.get("limit", ["100"])[0])
|
||||
return ApiResponse(200, ok({"items": readers.list_artifacts(limit=limit)}))
|
||||
|
|
|
|||
199
workbench/lived_life.py
Normal file
199
workbench/lived_life.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
"""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
|
||||
|
|
@ -11,6 +11,7 @@ from dataclasses import dataclass
|
|||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any, Callable, cast, get_args
|
||||
|
||||
from chat.always_on import LIVED_LIFE_FILENAME
|
||||
from core.config import RuntimeConfig
|
||||
from core.engine_identity import EngineIdentityError, engine_identity_for_config
|
||||
from engine_state import EngineStateStore, get_git_revision
|
||||
|
|
@ -35,6 +36,7 @@ from workbench.schemas import (
|
|||
IdentityContinuity,
|
||||
IdentityContinuityStatus,
|
||||
IdentityLineageRelation,
|
||||
LivedLife,
|
||||
MathProposalDetail,
|
||||
MathProposalSummary,
|
||||
MathRatifyResult,
|
||||
|
|
@ -50,6 +52,7 @@ from workbench.schemas import (
|
|||
VaultEntry,
|
||||
VaultSummary,
|
||||
)
|
||||
from workbench.lived_life import lived_life_from_payload, missing_lived_life
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SAFE_EVAL_LANES = frozenset({"contemplation_quality"})
|
||||
|
|
@ -326,6 +329,8 @@ def _artifact_kind(path: Path) -> str:
|
|||
rel = _relative(path)
|
||||
if rel == "engine_state/manifest.json":
|
||||
return "engine_state_manifest"
|
||||
if rel == f"engine_state/{LIVED_LIFE_FILENAME}":
|
||||
return "lived_life"
|
||||
if rel.startswith("teaching/proposals/"):
|
||||
return "proposal"
|
||||
if rel.startswith("evals/") and "/results/" in rel:
|
||||
|
|
@ -1735,6 +1740,32 @@ def _identity_continuity_from_manifest(
|
|||
)
|
||||
|
||||
|
||||
def lived_life() -> LivedLife:
|
||||
"""Read the persisted always-on run (``engine_state/lived_life.json``) as the L10
|
||||
lived-life surface.
|
||||
|
||||
Honest absence (``missing_evidence``) when no always-on run has been persisted yet —
|
||||
the heartbeat (``chat.always_on.run_continuous`` + ``write_lived_life``) is what
|
||||
produces the artifact; until a continuous-life run lands one, the surface says so
|
||||
rather than fabricating a life."""
|
||||
path = ENGINE_STATE_ROOT / LIVED_LIFE_FILENAME
|
||||
if not path.exists():
|
||||
return missing_lived_life("no always-on run has been persisted yet")
|
||||
payload = _read_json_object(path)
|
||||
artifact = _artifact_ref_for_path(path, "lived_life")
|
||||
# The resume verdict: would a reboot resume THIS life? Recompute the current substrate
|
||||
# identity with the canonical function (fail-soft -> "unknown", like IdentityContinuity).
|
||||
try:
|
||||
current_identity: str | None = engine_identity_for_config(
|
||||
RuntimeConfig(), get_git_revision()
|
||||
)
|
||||
except EngineIdentityError:
|
||||
current_identity = None
|
||||
return lived_life_from_payload(
|
||||
payload, artifact=artifact, current_identity=current_identity
|
||||
)
|
||||
|
||||
|
||||
def _journal_entries(journal: Any) -> list[Any]:
|
||||
path = getattr(journal, "path", None)
|
||||
if isinstance(path, Path) and path.exists():
|
||||
|
|
|
|||
|
|
@ -173,6 +173,67 @@ class FieldEvidence:
|
|||
transition_inner_product: float | None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LivedLifeHeartbeat:
|
||||
"""One beat of the continuous life (read-only telemetry).
|
||||
|
||||
Mirrors ``chat.always_on.HeartbeatRecord`` across the firewall: the closure of the
|
||||
live field that beat (``versor_condition``, READ never repaired), whether it held the
|
||||
``< ceiling`` invariant, and what the life learned that beat (Step-D facts +
|
||||
proposal-only proposals)."""
|
||||
|
||||
tick: int
|
||||
versor_condition: float | None
|
||||
field_valid: bool
|
||||
facts_consolidated: int
|
||||
proposals_created: int
|
||||
pending_proposals: int
|
||||
did_work: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LivedLife:
|
||||
"""L10 lived-life surface — evidence that CORE is ONE continuous life.
|
||||
|
||||
A read-only projection of the persisted always-on run
|
||||
(``chat.always_on.write_lived_life`` -> ``engine_state/lived_life.json``): the engine
|
||||
holds itself alive over uptime with no user turn, learns while idle (Step-D
|
||||
consolidation + proposal-only proposals), and holds closure BY CONSTRUCTION (the
|
||||
heartbeat reads ``versor_condition`` as evidence, never repairs it).
|
||||
|
||||
``closure_held`` is consistency-checked at construction against the per-beat
|
||||
measurements (``workbench.lived_life.validate``) — the surface can NEVER claim the
|
||||
field stayed valid while a beat breached the ceiling (the wrong=0 analogue for the
|
||||
continuity surface). ``converged`` is honest telemetry: a saturated life stops
|
||||
churning (the final beat did no work).
|
||||
|
||||
The two halves of "one continuous life" are both here: ``records`` are the lived
|
||||
experience over uptime (T-experience), and ``resume_status`` is the resume guarantee
|
||||
(T-resume) — it compares the life's content ``identity`` to the ``current_identity``
|
||||
recomputed from the live substrate. That is exactly the check the runtime load guard
|
||||
makes on reboot (``would_resume`` ⟺ a reboot resumes THIS life; ``substrate_changed``
|
||||
⟺ it would raise ``IdentityContinuityError``). The per-run lineage chain stays owned by
|
||||
Runs ``IdentityContinuity``; this is the self-contained substrate verdict."""
|
||||
|
||||
schema_version: Literal["lived_life_v1"]
|
||||
status: PipelineEvidenceStatus
|
||||
missing_reason: str | None
|
||||
identity: str | None
|
||||
heartbeats: int
|
||||
closure_observed: bool
|
||||
closure_held: bool
|
||||
closure_ceiling: float
|
||||
final_checkpoint_ok: bool
|
||||
converged: bool
|
||||
total_facts_consolidated: int
|
||||
total_proposals_created: int
|
||||
current_identity: str | None
|
||||
resume_status: Literal["would_resume", "substrate_changed", "unknown"]
|
||||
resume_summary: str
|
||||
records: list[LivedLifeHeartbeat] = field(default_factory=list)
|
||||
artifact: ArtifactRef | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class EvidenceBundle:
|
||||
"""D3 shareable evidence bundle — a turn's deterministic evidence, citable.
|
||||
|
|
|
|||
Loading…
Reference in a new issue