feat(adr-0246): §3.4/§3.5 lawful-only identity-path ledger (pure, off-serving)
Second Ring-1 unit per the brief. Stacked on feat/adr-0246-induced-action-primitives.
core/physics/identity_action.py:
+ PathBudget two-level budget (epsilon_turn, epsilon_session) (§3.4)
+ IdentityChainScope pack/geometry/policy/session/biography scope key (§3.5)
+ IdentityPathLedger immutable path snapshot; full-SHA-256 chain_id +
ledger_digest (LE f64 bytes, no default=str) (§4.2/§4.3)
+ advance_identity_path fold one turn: lawful iff d_stab<=epsilon_turn;
only lawful turns compose; refused turns are break
markers (path_break), never soft-projected I;
scope change = hard break onto a fresh chain
+ raw_path_product forensic-only (proves lawful product != raw product)
Enforces the §3.4 doctrine: lawful-only composition; no soft-projection of an
unlawful action onto I (non-goal #11); raw and lawful kept separate. Hard breaks
on every scope dimension (§3.5). Pure numpy+identity_manifold; off-serving; no
gate/threshold/axis/H_id/flag change (identity_wave_gate stays default-off).
Gate admit-surface wiring + eval matrix are subsequent units (brief §0a).
[Verification]: uv run core test --suite smoke -q => 176 passed;
tests/test_adr_0246_path_ledger.py 16 passed (RED-first, incl. small-rotation
session-accumulation, hard-break on each scope dim, raw!=lawful forensic pin,
immutability); identity surfaces (induced_action, mismatch-diagnostic,
identity_manifold, identity_gate_wave) 85 passed together.
This commit is contained in:
parent
4941cf188e
commit
6efe4ad80c
3 changed files with 435 additions and 5 deletions
|
|
@ -25,14 +25,19 @@ lawful action: ``d_stab`` is a pass/fail distance, and callers must NOT soft-pro
|
|||
Enlarging ``H_id`` is a future, explicit, reviewed pack/policy change — never an
|
||||
implicit convenience here.
|
||||
|
||||
Pure (numpy + identity_manifold only), deterministic, float64, off-serving. The
|
||||
lawful-only path composition and hard-break ledger (§3.4/§3.5) are a separate,
|
||||
later unit; this module provides only the per-turn stabilizer defect.
|
||||
Pure (numpy + identity_manifold only), deterministic, float64, off-serving. This
|
||||
module owns both the per-turn stabilizer defect (``d_stab``) and the lawful-only
|
||||
path ledger (§3.4/§3.5): the identity path composes ONLY the induced actions of
|
||||
turns certified lawful, refused turns insert break markers (never a soft-projected
|
||||
``I``), and a scope change forces a hard break onto a new chain.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
|
@ -129,3 +134,199 @@ def stabilizer_defect_for_versor(
|
|||
if stabilizer is None:
|
||||
stabilizer = IdentityStabilizer.singleton(action.shape[0])
|
||||
return stabilizer_defect(action, geometry.gram, stabilizer)
|
||||
|
||||
|
||||
# -- ADR-0246 §3.4/§3.5 lawful-only identity-path ledger -----------------------
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PathBudget:
|
||||
"""Two-level lawfulness budget (ADR-0246 §3.4).
|
||||
|
||||
``epsilon_turn`` bounds a single turn's ``d_stab`` (a large one-turn departure
|
||||
refuses immediately); ``epsilon_session`` bounds the composed lawful path's
|
||||
``d_stab`` (slow accumulation of individually-lawful turns eventually refuses).
|
||||
"""
|
||||
|
||||
epsilon_turn: float
|
||||
epsilon_session: float
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class IdentityChainScope:
|
||||
"""The scope that keys an identity-action chain (ADR-0246 §3.5).
|
||||
|
||||
Any change to these forces a **hard break** — a new chain that does NOT
|
||||
continue the previous composed path. The frame, its geometry, the lawfulness
|
||||
policy, the session, and (when explicit) the biography epoch each redefine
|
||||
what "the path" means, so a path may only compose within a single scope.
|
||||
"""
|
||||
|
||||
pack_content_digest: str
|
||||
geometry_version: str
|
||||
policy_version: str
|
||||
session_id: str
|
||||
biography_epoch: str | None = None
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"pack_content_digest": self.pack_content_digest,
|
||||
"geometry_version": self.geometry_version,
|
||||
"policy_version": self.policy_version,
|
||||
"session_id": self.session_id,
|
||||
"biography_epoch": self.biography_epoch,
|
||||
}
|
||||
|
||||
|
||||
def _chain_id(scope: IdentityChainScope, chain_index: int) -> str:
|
||||
"""Deterministic full-SHA-256 chain id (ADR-0245 §2.3 — no truncation).
|
||||
|
||||
Includes ``chain_index`` so a scope that recurs later in a session (e.g. a
|
||||
pack A → B → A cycle) still yields a distinct chain id.
|
||||
"""
|
||||
payload = json.dumps(
|
||||
{**scope.as_dict(), "chain_index": int(chain_index)},
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
)
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class IdentityPathLedger:
|
||||
"""Immutable snapshot of the lawful-only identity path (ADR-0246 §4.2).
|
||||
|
||||
``a_path_lawful`` is the time-forward product of the induced actions of the
|
||||
turns certified lawful within this chain (``I`` for an empty chain). Refused
|
||||
turns are counted in ``break_count`` and excluded from the product — never
|
||||
composed as ``I`` (that would be the soft-projection §3.4 forbids).
|
||||
"""
|
||||
|
||||
chain_id: str
|
||||
scope: IdentityChainScope
|
||||
chain_index: int
|
||||
dimension: int
|
||||
a_path_lawful: np.ndarray
|
||||
d_stab_path: float
|
||||
composed_turn_count: int
|
||||
break_count: int
|
||||
session_admit: bool
|
||||
|
||||
def ledger_digest(self) -> str:
|
||||
"""Full-SHA-256 content id over the path state (LE f64 byte-order)."""
|
||||
digest = hashlib.sha256()
|
||||
digest.update(self.chain_id.encode("utf-8"))
|
||||
digest.update(
|
||||
np.ascontiguousarray(self.a_path_lawful, dtype=np.dtype("<f8")).tobytes()
|
||||
)
|
||||
digest.update(
|
||||
json.dumps(
|
||||
[self.chain_index, self.composed_turn_count, self.break_count],
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
)
|
||||
return digest.hexdigest()
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": "identity_path_v1",
|
||||
"chain_id": self.chain_id,
|
||||
"scope": self.scope.as_dict(),
|
||||
"chain_index": self.chain_index,
|
||||
"dimension": self.dimension,
|
||||
"a_path_lawful": [
|
||||
[float(x) for x in row] for row in self.a_path_lawful
|
||||
],
|
||||
"d_stab_path": float(self.d_stab_path),
|
||||
"composed_turn_count": self.composed_turn_count,
|
||||
"break_count": self.break_count,
|
||||
"session_admit": self.session_admit,
|
||||
"ledger_digest": self.ledger_digest(),
|
||||
}
|
||||
|
||||
|
||||
def advance_identity_path(
|
||||
ledger: IdentityPathLedger | None,
|
||||
scope: IdentityChainScope,
|
||||
action: np.ndarray,
|
||||
gram: np.ndarray,
|
||||
budget: PathBudget,
|
||||
) -> tuple[IdentityPathLedger, dict[str, Any]]:
|
||||
"""Fold one turn's raw induced action into the lawful-only path (§3.4/§3.5).
|
||||
|
||||
Returns ``(new_ledger, turn_record)``. ``turn_record`` reports this turn's
|
||||
``lawful`` / ``d_stab_turn`` / ``path_break`` / ``hard_break``. A turn is
|
||||
lawful iff ``d_stab(action) ≤ epsilon_turn`` under the locked singleton
|
||||
``H_id={I}``; only lawful turns compose. A scope change (or an absent prior
|
||||
ledger) is a hard break that starts a fresh chain — the previous path is NOT
|
||||
continued. Immutable: ``ledger`` is never mutated.
|
||||
"""
|
||||
action = np.asarray(action, dtype=np.float64)
|
||||
if action.ndim != 2 or action.shape[0] != action.shape[1]:
|
||||
raise ValueError(
|
||||
f"induced action must be a square matrix, got shape {action.shape}"
|
||||
)
|
||||
dimension = action.shape[0]
|
||||
stabilizer = IdentityStabilizer.singleton(dimension)
|
||||
d_stab_turn = stabilizer_defect(action, gram, stabilizer)
|
||||
lawful = d_stab_turn <= budget.epsilon_turn
|
||||
|
||||
hard_break = ledger is None or ledger.scope != scope
|
||||
if hard_break:
|
||||
chain_index = 0 if ledger is None else ledger.chain_index + 1
|
||||
a_path = np.eye(dimension, dtype=np.float64)
|
||||
composed = 0
|
||||
breaks = 0
|
||||
else:
|
||||
assert ledger is not None # not a hard break ⇒ prior ledger exists
|
||||
chain_index = ledger.chain_index
|
||||
a_path = ledger.a_path_lawful
|
||||
composed = ledger.composed_turn_count
|
||||
breaks = ledger.break_count
|
||||
|
||||
if lawful:
|
||||
# time-forward: later turns act on the left of the accumulated frame action
|
||||
a_path = action @ a_path
|
||||
composed += 1
|
||||
path_break = False
|
||||
else:
|
||||
# break marker — excluded from the product; NOT composed as identity
|
||||
breaks += 1
|
||||
path_break = True
|
||||
|
||||
d_stab_path = stabilizer_defect(a_path, gram, stabilizer)
|
||||
new_ledger = IdentityPathLedger(
|
||||
chain_id=_chain_id(scope, chain_index),
|
||||
scope=scope,
|
||||
chain_index=chain_index,
|
||||
dimension=dimension,
|
||||
a_path_lawful=a_path,
|
||||
d_stab_path=d_stab_path,
|
||||
composed_turn_count=composed,
|
||||
break_count=breaks,
|
||||
session_admit=(d_stab_path <= budget.epsilon_session),
|
||||
)
|
||||
turn_record = {
|
||||
"lawful": lawful,
|
||||
"d_stab_turn": d_stab_turn,
|
||||
"path_break": path_break,
|
||||
"hard_break": hard_break,
|
||||
}
|
||||
return new_ledger, turn_record
|
||||
|
||||
|
||||
def raw_path_product(actions: Sequence[np.ndarray]) -> np.ndarray:
|
||||
"""Time-forward product of ALL actions, lawful or not — **forensic only**.
|
||||
|
||||
This is the category error §3.4 forbids for the live path (it mixes refused,
|
||||
ill-conditioned, and leaked actions into a fake "holonomy"). It exists solely
|
||||
so tests and forensics can demonstrate that the lawful-only product differs
|
||||
from the naive raw product. Never use it to admit a turn.
|
||||
"""
|
||||
if not actions:
|
||||
raise ValueError("raw_path_product requires at least one action")
|
||||
result = np.eye(np.asarray(actions[0]).shape[0], dtype=np.float64)
|
||||
for action in actions:
|
||||
result = np.asarray(action, dtype=np.float64) @ result
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ structure is stabilized would instrument lawfulness on a frame the dynamics igno
|
|||
| Unit | Scope | Status |
|
||||
|------|-------|--------|
|
||||
| **Slice 0** — mismatch diagnostic | evidence-only classification of the benign mismatch (foreign leakage vs in-span-unlawful vs numerical vs path vs semantic-coupling-absent) | **merged** `main` (quarantined diagnostic artifact); found: structural e4/e5 foreign leakage, declared frame dynamically unspecial |
|
||||
| **§3 primitives** (this unit) | pure `A(F)`, `d_orth`, `d_stab` vs locked `H_id={I}`, typed residual channels in `core/physics/identity_manifold.py` + `identity_action.py`; slice-0 eval rewired to consume them (single source of truth) | in progress — RED→GREEN, off-serving, flag untouched |
|
||||
| §3.4/§3.5 path ledger | lawful-only composition + hard breaks | next unit |
|
||||
| **§3 primitives** | pure `A(F)`, `d_orth`, `d_stab` vs locked `H_id={I}`, typed residual channels in `core/physics/identity_manifold.py` + `identity_action.py`; slice-0 eval rewired to consume them (single source of truth) | **committed** `feat/adr-0246-induced-action-primitives` (RED→GREEN, off-serving, flag untouched) — awaiting review |
|
||||
| **§3.4/§3.5 path ledger** (this unit) | lawful-only composition + hard breaks: `PathBudget`, `IdentityChainScope`, `IdentityPathLedger`, `advance_identity_path` in `identity_action.py`; refused turns = break markers (never soft-projected `I`); scope change = hard break onto new chain | in progress — RED→GREEN, off-serving; stacked on the primitives branch |
|
||||
| §3.7 gate admit surface | wire d_orth/d_stab/typed channels into `IdentityScore` (flag default-off) | after ledger |
|
||||
| §6 eval matrix + §11 feasibility | synthetic + path suites + discrimination report; the invariant/semantic-grounding feasibility study runs here as the **first consumer** of the §3 primitives (fixed cohort splits, synthetic recovery controls, typed e4/e5 generator analysis, precision pairs, adversarial discrimination) | after gate surface |
|
||||
| ADR-0246 body + acceptance packet | §10 criteria; no self-Accept | last |
|
||||
|
|
|
|||
229
tests/test_adr_0246_path_ledger.py
Normal file
229
tests/test_adr_0246_path_ledger.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
"""ADR-0246 §3.4/§3.5 — lawful-only identity-path ledger pins (§6.2 path suite).
|
||||
|
||||
The path ledger composes ONLY the induced actions of turns that were individually
|
||||
certified lawful (``d_stab ≤ ε_turn`` under the locked singleton ``H_id={I}``).
|
||||
Refused turns insert a break marker and are excluded from the product — never a
|
||||
soft-projected identity matrix masquerading as a pass (brief §3.4 / non-goal #11).
|
||||
A scope change (pack digest / geometry / policy / session / biography epoch) forces
|
||||
a hard break: a new ``chain_id`` and a fresh path (§3.5). Pure, off-serving.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from algebra.cl41 import N_COMPONENTS
|
||||
from core.physics.identity_manifold import IdentityManifoldGeometry
|
||||
from core.physics.identity_action import (
|
||||
IdentityChainScope,
|
||||
PathBudget,
|
||||
advance_identity_path,
|
||||
raw_path_product,
|
||||
)
|
||||
|
||||
_E12, _E13 = 6, 7
|
||||
|
||||
|
||||
def _rotor(biv: int, theta: float) -> np.ndarray:
|
||||
r = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
r[0] = np.cos(theta / 2.0)
|
||||
r[biv] = np.sin(theta / 2.0)
|
||||
return r
|
||||
|
||||
|
||||
def _identity_versor() -> np.ndarray:
|
||||
v = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
v[0] = 1.0
|
||||
return v
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def geometry() -> IdentityManifoldGeometry:
|
||||
return IdentityManifoldGeometry.from_directions(
|
||||
((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0))
|
||||
)
|
||||
|
||||
|
||||
def _scope(pack="packA", geom="geomV1", policy="polV1", session="sess1", bio=None):
|
||||
return IdentityChainScope(
|
||||
pack_content_digest=pack,
|
||||
geometry_version=geom,
|
||||
policy_version=policy,
|
||||
session_id=session,
|
||||
biography_epoch=bio,
|
||||
)
|
||||
|
||||
|
||||
_BUDGET = PathBudget(epsilon_turn=0.1, epsilon_session=0.3)
|
||||
|
||||
|
||||
def _action(geometry, versor):
|
||||
return geometry.induced_action(versor)
|
||||
|
||||
|
||||
def test_first_lawful_turn_starts_chain_near_identity(geometry):
|
||||
ledger, rec = advance_identity_path(
|
||||
None, _scope(), _action(geometry, _identity_versor()), geometry.gram, _BUDGET
|
||||
)
|
||||
assert rec["hard_break"] is True
|
||||
assert rec["lawful"] is True and rec["path_break"] is False
|
||||
assert ledger.composed_turn_count == 1 and ledger.break_count == 0
|
||||
assert np.allclose(ledger.a_path_lawful, np.eye(3), atol=1e-12)
|
||||
assert ledger.d_stab_path < 1e-12
|
||||
assert ledger.session_admit is True
|
||||
|
||||
|
||||
def test_lawful_identity_sequence_stays_admitted(geometry):
|
||||
ledger = None
|
||||
for _ in range(20):
|
||||
ledger, _ = advance_identity_path(
|
||||
ledger, _scope(), _action(geometry, _identity_versor()), geometry.gram, _BUDGET
|
||||
)
|
||||
assert ledger.composed_turn_count == 20 and ledger.break_count == 0
|
||||
assert np.allclose(ledger.a_path_lawful, np.eye(3), atol=1e-12)
|
||||
assert ledger.session_admit is True
|
||||
|
||||
|
||||
def test_small_rotations_accumulate_to_session_refusal(geometry):
|
||||
small = _action(geometry, _rotor(_E12, 0.05)) # each turn is lawful (small d_stab)
|
||||
ledger = None
|
||||
admitted_turns = 0
|
||||
for _ in range(40):
|
||||
ledger, rec = advance_identity_path(ledger, _scope(), small, geometry.gram, _BUDGET)
|
||||
assert rec["lawful"] is True # each small step passes ε_turn
|
||||
admitted_turns += 1
|
||||
if not ledger.session_admit:
|
||||
break
|
||||
# per-turn always lawful, but the composed path eventually breaches ε_session
|
||||
assert ledger.session_admit is False
|
||||
assert ledger.d_stab_path > _BUDGET.epsilon_session
|
||||
assert ledger.composed_turn_count == admitted_turns
|
||||
|
||||
|
||||
def test_refused_turn_is_break_and_excluded(geometry):
|
||||
ledger, _ = advance_identity_path(
|
||||
None, _scope(), _action(geometry, _identity_versor()), geometry.gram, _BUDGET
|
||||
)
|
||||
before = ledger.a_path_lawful.copy()
|
||||
big = _action(geometry, _rotor(_E12, np.pi / 2.0)) # 90° rotation: d_stab huge
|
||||
ledger, rec = advance_identity_path(ledger, _scope(), big, geometry.gram, _BUDGET)
|
||||
assert rec["lawful"] is False and rec["path_break"] is True
|
||||
assert ledger.break_count == 1 and ledger.composed_turn_count == 1
|
||||
# the refused action does NOT compose (no soft-projected I either): path unchanged
|
||||
assert np.allclose(ledger.a_path_lawful, before, atol=1e-12)
|
||||
|
||||
|
||||
def test_interleaved_refuse_admit_records_raw(geometry):
|
||||
ident = _action(geometry, _identity_versor())
|
||||
big = _action(geometry, _rotor(_E12, np.pi / 2.0))
|
||||
seq = [ident, big, ident, big, ident]
|
||||
ledger = None
|
||||
records = []
|
||||
for a in seq:
|
||||
ledger, rec = advance_identity_path(ledger, _scope(), a, geometry.gram, _BUDGET)
|
||||
records.append(rec)
|
||||
assert ledger.composed_turn_count == 3 and ledger.break_count == 2
|
||||
assert [r["path_break"] for r in records] == [False, True, False, True, False]
|
||||
|
||||
|
||||
def test_hard_break_on_pack_digest_change(geometry):
|
||||
ident = _action(geometry, _identity_versor())
|
||||
ledger, _ = advance_identity_path(None, _scope(pack="packA"), ident, geometry.gram, _BUDGET)
|
||||
id_a = ledger.chain_id
|
||||
# drift the path a little so "not continued" is observable
|
||||
small = _action(geometry, _rotor(_E12, 0.05))
|
||||
ledger, _ = advance_identity_path(ledger, _scope(pack="packA"), small, geometry.gram, _BUDGET)
|
||||
drifted = ledger.a_path_lawful.copy()
|
||||
# pack change → hard break
|
||||
ledger, rec = advance_identity_path(ledger, _scope(pack="packB"), ident, geometry.gram, _BUDGET)
|
||||
assert rec["hard_break"] is True
|
||||
assert ledger.chain_id != id_a
|
||||
assert ledger.composed_turn_count == 1 and ledger.break_count == 0 # fresh chain
|
||||
assert not np.allclose(ledger.a_path_lawful, drifted) # old path not continued
|
||||
assert np.allclose(ledger.a_path_lawful, np.eye(3), atol=1e-12)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"changed",
|
||||
[
|
||||
{"geom": "geomV2"},
|
||||
{"policy": "polV2"},
|
||||
{"session": "sess2"},
|
||||
{"bio": "epoch2"},
|
||||
],
|
||||
)
|
||||
def test_hard_break_on_each_scope_dimension(geometry, changed):
|
||||
ident = _action(geometry, _identity_versor())
|
||||
ledger, _ = advance_identity_path(None, _scope(), ident, geometry.gram, _BUDGET)
|
||||
base_id = ledger.chain_id
|
||||
ledger, rec = advance_identity_path(ledger, _scope(**changed), ident, geometry.gram, _BUDGET)
|
||||
assert rec["hard_break"] is True
|
||||
assert ledger.chain_id != base_id
|
||||
|
||||
|
||||
def test_raw_product_differs_from_lawful_product(geometry):
|
||||
ident = _action(geometry, _identity_versor())
|
||||
big = _action(geometry, _rotor(_E12, np.pi / 2.0))
|
||||
seq = [ident, big, ident]
|
||||
ledger = None
|
||||
for a in seq:
|
||||
ledger, _ = advance_identity_path(ledger, _scope(), a, geometry.gram, _BUDGET)
|
||||
# forensic pin: composing ALL raw actions (incl. the refused 90°) gives a very
|
||||
# different result than the lawful-only product — the category error §3.4 forbids.
|
||||
raw = raw_path_product(seq)
|
||||
assert not np.allclose(raw, ledger.a_path_lawful)
|
||||
assert np.allclose(ledger.a_path_lawful, np.eye(3), atol=1e-12) # lawful excludes the big turn
|
||||
|
||||
|
||||
def test_chain_id_is_deterministic_full_sha256(geometry):
|
||||
ident = _action(geometry, _identity_versor())
|
||||
l1, _ = advance_identity_path(None, _scope(), ident, geometry.gram, _BUDGET)
|
||||
l2, _ = advance_identity_path(None, _scope(), ident, geometry.gram, _BUDGET)
|
||||
assert l1.chain_id == l2.chain_id
|
||||
assert len(l1.chain_id) == 64
|
||||
int(l1.chain_id, 16) # valid hex
|
||||
|
||||
|
||||
def test_ledger_digest_deterministic_and_path_sensitive(geometry):
|
||||
ident = _action(geometry, _identity_versor())
|
||||
small = _action(geometry, _rotor(_E13, 0.05))
|
||||
l1, _ = advance_identity_path(None, _scope(), ident, geometry.gram, _BUDGET)
|
||||
l1b, _ = advance_identity_path(None, _scope(), ident, geometry.gram, _BUDGET)
|
||||
assert l1.ledger_digest() == l1b.ledger_digest()
|
||||
assert len(l1.ledger_digest()) == 64
|
||||
l2, _ = advance_identity_path(l1, _scope(), small, geometry.gram, _BUDGET)
|
||||
assert l2.ledger_digest() != l1.ledger_digest()
|
||||
|
||||
|
||||
def test_advance_is_immutable(geometry):
|
||||
ident = _action(geometry, _identity_versor())
|
||||
ledger, _ = advance_identity_path(None, _scope(), ident, geometry.gram, _BUDGET)
|
||||
snapshot = ledger.a_path_lawful.copy()
|
||||
count_before = ledger.composed_turn_count
|
||||
_new, _ = advance_identity_path(
|
||||
ledger, _scope(), _action(geometry, _rotor(_E12, 0.05)), geometry.gram, _BUDGET
|
||||
)
|
||||
assert ledger.composed_turn_count == count_before # original untouched
|
||||
assert np.allclose(ledger.a_path_lawful, snapshot)
|
||||
|
||||
|
||||
def test_as_dict_shape(geometry):
|
||||
ledger, _ = advance_identity_path(
|
||||
None, _scope(), _action(geometry, _identity_versor()), geometry.gram, _BUDGET
|
||||
)
|
||||
d = ledger.as_dict()
|
||||
assert d["schema_version"] == "identity_path_v1"
|
||||
assert d["chain_id"] == ledger.chain_id
|
||||
assert d["composed_turn_count"] == 1
|
||||
assert d["break_count"] == 0
|
||||
assert d["session_admit"] is True
|
||||
assert "a_path_lawful" in d and "d_stab_path" in d and "ledger_digest" in d
|
||||
|
||||
|
||||
def test_module_is_pure_offserving():
|
||||
import core.physics.identity_action as a
|
||||
|
||||
with open(a.__file__, encoding="utf-8") as fh:
|
||||
src = fh.read()
|
||||
assert "chat.runtime" not in src and "import chat" not in src
|
||||
Loading…
Reference in a new issue