Removes code_revision from the engine-identity hash: engine_identity is now the sha256 of the 5 ratified packs ONLY. The build revision is provenance (the manifest's written_at_revision), not identity — so a behavior-neutral rebuild is the SAME identity and the always-on daemon no longer flag-day strict-breaks on every commit (the ADR-0220 defect). Core: - core/engine_identity.py: ratified_substrate/compute_engine_identity drop the git_revision arg (packs-only); add compute_legacy_engine_identity (reproduces the pre-split packs+rev hash, for migration verification), ENGINE_IDENTITY_SCHEME=2, IdentityReconciliation enum, and reconcile_loaded_identity — the single source of truth for the runtime guard AND the workbench reader. - chat/runtime.py: the load guard reconciles via scheme. Current scheme -> direct packs-only compare. Legacy (code_revision-folded) stamp -> a VERIFYING migration: reconstruct the legacy hash from the persisted written_at_revision; a match proves packs unchanged (warn + re-stamp, resume, no break) — a mismatch means the packs genuinely changed (DIVERGED -> strict-refuse). Preserves 'distinct packs => refuse'. - engine_state/save_manifest: stamp identity_scheme alongside engine_identity (additive-optional; no schema_version bump). - workbench/readers.py: continuity reader uses the same reconcile (no phantom break on legacy checkpoints). cli.py break message reworded (packs, not 'build revision'). Callsite cleanup: drop the now-unused git_revision arg + imports across always_on.py, evals/l10_always_on/runner.py, workbench/readers.py. Tests: - test_identity_provenance_split.py (new): 5 reconcile unit proofs + 3 runtime integration proofs incl. the wrong=identity defense (legacy stamp of DIFFERENT packs still strict-refuses) and the re-stamp migration. - test_engine_identity.py: invert the code-revision test (rev no longer changes identity); assert the substrate is packs-only. - Restore 3 lineage tests silently red since ADR-0219 (flat-path _manifest helper now resolves the gen-dir). - Update remaining callsites/monkeypatches for the new signature. Hygiene: .gitignore now covers the ADR-0219 gen-dir runtime files (current, gen-*/, session_state.json, proposals.jsonl). Verified: 59 identity/migration/lineage/workbench + 32 L10 + 76 invariants/cli + 34 smoke pass; serving lane SHAs unchanged (no derivation/reliability_gate touch).
71 lines
3.1 KiB
Python
71 lines
3.1 KiB
Python
"""The identity-continuity PROOF — L11 Phase 4.
|
|
|
|
"Same identity, not just same bytes" is proven by composing three legs:
|
|
|
|
1. **Continuity (sufficient):** under a fixed identity, a rebooted resume-mode
|
|
run is BYTE-IDENTICAL to the uninterrupted run (Shape B+ P2b) AND the engine
|
|
identity is unchanged (no break). The resumed life behaves AND is-identified
|
|
as the same.
|
|
2. **Load-bearing (structural):** distinct identity packs are distinct engine
|
|
identities — identity is causal, not incidental. (The `identity_divergence`
|
|
lane separately proves distinct identities also *behave* differently.)
|
|
3. **Causal contrapositive (necessary):** resuming a checkpoint under a
|
|
DIFFERENT identity raises the continuity break — continuity holds iff the
|
|
identity is the same. Identity is necessary for continuity.
|
|
|
|
Legs 1 + 3 bracket it: same identity ⟹ continuous; different identity ⟹ break.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from chat.runtime import ChatRuntime
|
|
from core.cognition.pipeline import CognitiveTurnPipeline
|
|
from core.config import RuntimeConfig
|
|
from core.engine_identity import engine_identity_for_config
|
|
from evals.l10_continuity.runner import run_soak
|
|
|
|
_PRECISION = RuntimeConfig(identity_pack="precision_first_v1")
|
|
|
|
|
|
def test_resumed_life_is_byte_identical_and_same_identity(tmp_path: Path) -> None:
|
|
# Leg 1: under a fixed (non-default) identity, reboot is fully transparent.
|
|
rebooted = run_soak(
|
|
6, engine_state_dir=tmp_path / "r", reboot_at=(3,), config=_PRECISION
|
|
)
|
|
baseline = run_soak(6, engine_state_dir=tmp_path / "b", config=_PRECISION)
|
|
assert rebooted.trace_hashes() == baseline.trace_hashes() # behavior continuous
|
|
# And the reboot carried the SAME identity (a fresh runtime over the dir
|
|
# finds no continuity break).
|
|
rt = ChatRuntime(config=_PRECISION, engine_state_path=tmp_path / "r")
|
|
assert rt.identity_continuity_break is False
|
|
|
|
|
|
def test_identity_is_load_bearing_distinct_packs_distinct_identity() -> None:
|
|
# Leg 2: three identity packs -> three distinct engine identities.
|
|
default_id = engine_identity_for_config(RuntimeConfig())
|
|
precision_id = engine_identity_for_config(_PRECISION)
|
|
generosity_id = engine_identity_for_config(
|
|
RuntimeConfig(identity_pack="generosity_first_v1")
|
|
)
|
|
assert len({default_id, precision_id, generosity_id}) == 3
|
|
|
|
|
|
def test_continuity_breaks_under_a_different_identity(tmp_path: Path) -> None:
|
|
# Leg 3 (contrapositive): a life lived under one identity, resumed under
|
|
# another, raises the break — identity is NECESSARY for continuity.
|
|
state_dir = tmp_path / "es"
|
|
writer = ChatRuntime(config=_PRECISION, engine_state_path=state_dir)
|
|
pipe = CognitiveTurnPipeline(runtime=writer)
|
|
pipe.run("What causes light?")
|
|
pipe.run("What is a concept?")
|
|
|
|
import pytest
|
|
|
|
with pytest.warns(RuntimeWarning, match="identity continuity break"):
|
|
resumed = ChatRuntime(
|
|
config=RuntimeConfig(identity_pack="generosity_first_v1"),
|
|
engine_state_path=state_dir,
|
|
)
|
|
assert resumed.identity_continuity_break is True
|