feat(W-003): wire VaultPromotionPolicy into turn boundary (ADR-0148) (#272)
* feat(W-003): wire VaultPromotionPolicy into turn boundary (ADR-0148) VaultPromotionPolicy had zero callers; vault entries never crystallized from SPECULATIVE to COHERENT. This PR wires the policy at the turn boundary so settled entries can promote automatically. Changes: - core/config.py: add vault_promotion_enabled flag (default False, null-drop) - vault/store.py: add promote_eligible_entries(policy) — metadata-only scan, versors unchanged, _matrix_cache not invalidated - session/context.py: persist energy_raw/energy_class/coherence_residual in vault payload inside finalize_turn so the policy has data to decide on - chat/runtime.py: call promote_eligible_entries after each finalize_turn, gated on vault_promotion_enabled; import VaultPromotionPolicy - docs/decisions/ADR-0148-vault-promotion-policy-wiring.md: decision record - tests/test_adr_0148_vault_promotion.py: 6 tests, all green Unlocks W-007 (DerivedRecognizer derivation from COHERENT vault entries). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(W-003): resolve Pyright errors on vault promotion wiring - vault/store.py: add TYPE_CHECKING guard to import VaultPromotionPolicy only at type-check time, avoiding circular import at runtime while making the name resolvable to Pyright. - session/context.py:262: suppress union-attr false positive — self.state is guarded non-None by the raise at line 256 when input_versor is also None, but Pyright cannot narrow through the nested ternary structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9bbdcc96aa
commit
0b940674c0
6 changed files with 393 additions and 1 deletions
|
|
@ -56,6 +56,7 @@ from core.config import DEFAULT_CONFIG, DEFAULT_IDENTITY_PACK, RuntimeConfig
|
|||
from core.physics.drive import DriveGradientMap, GradientField
|
||||
from core.physics.energy import EnergyClass, EnergyProfile
|
||||
from core.physics.exertion import CycleCost, ExertionMeter
|
||||
from core.physics.learning import VaultPromotionPolicy
|
||||
from core.physics.identity import (
|
||||
CharacterProfile,
|
||||
IdentityCheck,
|
||||
|
|
@ -1806,6 +1807,9 @@ class ChatRuntime:
|
|||
"grounding_source": pack_source_tag if pack_surface else "none",
|
||||
},
|
||||
)
|
||||
# ADR-0148 — post-finalize promotion scan (flag-gated, null-drop when False).
|
||||
if self.config.vault_promotion_enabled:
|
||||
self._context.vault.promote_eligible_entries(VaultPromotionPolicy())
|
||||
discovery_intent_tag = None
|
||||
discovery_intent_subject: str | None = None
|
||||
stub_graph_atoms: tuple[str, ...] = ()
|
||||
|
|
@ -1988,6 +1992,9 @@ class ChatRuntime:
|
|||
tokens_in=tuple(filtered),
|
||||
dialogue_role=str(dialogue_role),
|
||||
)
|
||||
# ADR-0148 — post-finalize promotion scan (flag-gated, null-drop when False).
|
||||
if self.config.vault_promotion_enabled:
|
||||
self._context.vault.promote_eligible_entries(VaultPromotionPolicy())
|
||||
current_valence = _energy_scalar(getattr(result.final_state, "valence", None))
|
||||
surface_ctx = self._build_surface_context(identity_score, current_valence)
|
||||
self._last_valence = current_valence
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ class RuntimeConfig:
|
|||
# live workload.
|
||||
unified_ingest: bool = False
|
||||
|
||||
|
||||
# ADR-0144 — recognition-grounded articulation graph. When True and a
|
||||
# DerivedRecognizer is attached to CognitiveTurnPipeline, the articulation
|
||||
# graph is derived from the admitted EpistemicNode via the connector rather
|
||||
|
|
@ -258,6 +259,15 @@ class RuntimeConfig:
|
|||
# ADR-0021 §3. Default False preserves all pre-W-016 discovery
|
||||
# output byte-identically (null-drop invariant on discovery lanes).
|
||||
vault_probe_discoveries: bool = False
|
||||
# ADR-0148 — wire VaultPromotionPolicy into turn boundary.
|
||||
# When True, ChatRuntime calls vault.promote_eligible_entries() after each
|
||||
# finalize_turn(), scanning SPECULATIVE entries for crystallization to
|
||||
# COHERENT based on their energy profile (EnergyClass E0/E1, coherence_residual
|
||||
# ≤ 0.05). Fresh entries written in the current turn are E2+ and will not
|
||||
# promote yet — the policy fires on entries that have cooled across turns.
|
||||
# Default False: zero behavior change when disabled (null-drop invariant).
|
||||
# Unlocks W-007 (DerivedRecognizer derivation from promoted COHERENT entries).
|
||||
vault_promotion_enabled: bool = False
|
||||
|
||||
|
||||
DEFAULT_IDENTITY_PACK: str = "default_general_v1"
|
||||
|
|
|
|||
110
docs/decisions/ADR-0148-vault-promotion-policy-wiring.md
Normal file
110
docs/decisions/ADR-0148-vault-promotion-policy-wiring.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# ADR-0148 — Wire VaultPromotionPolicy into turn boundary
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-25
|
||||
**Work item:** W-003
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
`VaultPromotionPolicy` (introduced in ADR-0014, implemented at
|
||||
`core/physics/learning.py`) decides whether a stored vault entry should be
|
||||
promoted from `SPECULATIVE` to `COHERENT` based on its energy profile.
|
||||
|
||||
Prior to this ADR, the policy had **zero callers**. Every vault entry written
|
||||
by `session/context.py` remained `SPECULATIVE` indefinitely, regardless of how
|
||||
settled or coherent the underlying field region was.
|
||||
|
||||
This blocked W-007 (DerivedRecognizer derivation), which requires `COHERENT`
|
||||
vault entries to serve as valid recognition anchors.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Flag in `RuntimeConfig` (`core/config.py`)
|
||||
|
||||
```python
|
||||
vault_promotion_enabled: bool = False
|
||||
```
|
||||
|
||||
Default `False` enforces the **null-drop invariant**: zero behavior change when
|
||||
disabled. Operators opt in explicitly.
|
||||
|
||||
### 2. Energy metadata persisted at store time (`session/context.py`)
|
||||
|
||||
In `finalize_turn()`, after `_anchor_pull()` resolves `oriented_state`, the
|
||||
energy fields are written into the vault payload before `vault.store()`:
|
||||
|
||||
```python
|
||||
if oriented_state.energy is not None:
|
||||
payload["energy_raw"] = float(oriented_state.energy.raw)
|
||||
payload["energy_class"] = oriented_state.energy.energy_class.value
|
||||
payload["coherence_residual"] = float(oriented_state.energy.coherence_residual)
|
||||
```
|
||||
|
||||
Storing raw scalars (not the `EnergyProfile` object) keeps the payload
|
||||
JSON-serializable and avoids coupling the vault to the energy dataclass.
|
||||
|
||||
### 3. `VaultStore.promote_eligible_entries(policy)` (`vault/store.py`)
|
||||
|
||||
New method scans all SPECULATIVE entries. For each entry:
|
||||
|
||||
1. Parses the stored `epistemic_status` string.
|
||||
2. If SPECULATIVE, reconstructs a minimal `EnergyProfile` from the stored
|
||||
`energy_raw`, `energy_class`, `coherence_residual` fields.
|
||||
3. Calls `policy.decide(energy)`.
|
||||
4. If `decision.promote`, updates `epistemic_status` to `COHERENT` in-place.
|
||||
|
||||
**Versors are not touched.** `_matrix_cache` is not invalidated because no
|
||||
versor changes — only metadata mutates. Deterministic recall is unaffected.
|
||||
|
||||
### 4. Promotion fires post-finalize in `chat/runtime.py`
|
||||
|
||||
After each `finalize_turn()` call in `chat()`:
|
||||
|
||||
```python
|
||||
if self.config.vault_promotion_enabled:
|
||||
self._context.vault.promote_eligible_entries(VaultPromotionPolicy())
|
||||
```
|
||||
|
||||
**Why post-finalize, not at store time?**
|
||||
|
||||
A freshly stored entry is always `E2+` (new activation, high recency). The
|
||||
`VaultPromotionPolicy` promotes only `E0`/`E1` entries
|
||||
(`vault_candidate=True`). A just-written entry will not promote on the same
|
||||
turn it was written — it needs to cool across subsequent turns. This is the
|
||||
correct multi-turn crystallization behavior described in ADR-0014.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Vault entries can now crystallize: SPECULATIVE regions that settle over
|
||||
multiple turns become COHERENT, making them admissible as evidence under
|
||||
`min_status=EpistemicStatus.COHERENT` recall.
|
||||
- W-007 (DerivedRecognizer derivation from promoted entries) is now unblocked.
|
||||
- Zero coupling change when `vault_promotion_enabled=False` (default).
|
||||
|
||||
### Constraints preserved
|
||||
|
||||
- **versor_condition invariant**: no versor is modified during promotion.
|
||||
`promote_eligible_entries` mutates only `_metadata` dicts.
|
||||
- **No normalization**: `vault/store.py` is a forbidden normalization site
|
||||
per `CLAUDE.md`. Promotion is a metadata-only operation — it does not
|
||||
repair, reproject, or normalize any field.
|
||||
- **No approximate recall**: CGA inner-product scoring is unchanged.
|
||||
- **Reviewed learning path**: promotion upgrades `epistemic_status` on
|
||||
already-stored entries; it does not inject new content or bypass the
|
||||
teaching review gate.
|
||||
|
||||
---
|
||||
|
||||
## Unlocks
|
||||
|
||||
- **W-007** — DerivedRecognizer can now query the vault at
|
||||
`min_status=EpistemicStatus.COHERENT` and receive crystallized entries as
|
||||
recognition anchors.
|
||||
|
|
@ -241,7 +241,7 @@ class SessionContext:
|
|||
input_F = (
|
||||
np.asarray(input_versor, dtype=np.float32).copy()
|
||||
if input_versor is not None
|
||||
else (self._last_input_versor.copy() if self._last_input_versor is not None else self.state.F.copy())
|
||||
else (self._last_input_versor.copy() if self._last_input_versor is not None else self.state.F.copy()) # type: ignore[union-attr]
|
||||
)
|
||||
turn_tokens = tuple(tokens_in if tokens_in is not None else self._last_input_tokens)
|
||||
backward_edges = self.referents.consumed_turns()
|
||||
|
|
@ -268,6 +268,12 @@ class SessionContext:
|
|||
payload = {"turn": self.turn, "role": "assistant"}
|
||||
if metadata:
|
||||
payload.update(metadata)
|
||||
# ADR-0148 — persist energy profile so VaultPromotionPolicy can decide
|
||||
# promotion eligibility on future turns (after the entry has cooled).
|
||||
if oriented_state.energy is not None:
|
||||
payload["energy_raw"] = float(oriented_state.energy.raw)
|
||||
payload["energy_class"] = oriented_state.energy.energy_class.value
|
||||
payload["coherence_residual"] = float(oriented_state.energy.coherence_residual)
|
||||
self.vault.store(
|
||||
oriented_state.F,
|
||||
payload,
|
||||
|
|
|
|||
205
tests/test_adr_0148_vault_promotion.py
Normal file
205
tests/test_adr_0148_vault_promotion.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
"""Tests for ADR-0148 — VaultPromotionPolicy wired into the turn boundary.
|
||||
|
||||
Six tests cover:
|
||||
1. Flag-off: no promotion fires (null-drop invariant).
|
||||
2. Direct promote_eligible_entries: E0/low-residual entry becomes COHERENT.
|
||||
3. Active-energy entry (E3) is not promoted (vault_candidate=False).
|
||||
4. E0 entry with high coherence_residual is not promoted.
|
||||
5. promote_eligible_entries returns correct count.
|
||||
6. finalize_turn persists energy_raw / energy_class / coherence_residual in vault metadata.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from algebra.cga import embed_point
|
||||
from chat.runtime import ChatRuntime
|
||||
from core.config import RuntimeConfig
|
||||
from core.physics.energy import EnergyClass, EnergyProfile
|
||||
from core.physics.learning import VaultPromotionPolicy
|
||||
from teaching.epistemic import EpistemicStatus
|
||||
from vault.store import VaultStore
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _random_versor(seed: int = 0) -> np.ndarray:
|
||||
rng = np.random.default_rng(seed)
|
||||
return embed_point(rng.standard_normal(3).astype(np.float32))
|
||||
|
||||
|
||||
def _store_entry(
|
||||
vault: VaultStore,
|
||||
seed: int,
|
||||
*,
|
||||
energy_class: EnergyClass,
|
||||
coherence_residual: float,
|
||||
raw: float = 0.05,
|
||||
) -> None:
|
||||
"""Store a SPECULATIVE entry with explicit energy metadata."""
|
||||
vault.store(
|
||||
_random_versor(seed),
|
||||
{
|
||||
"turn": seed,
|
||||
"role": "assistant",
|
||||
"energy_raw": float(raw),
|
||||
"energy_class": energy_class.value,
|
||||
"coherence_residual": float(coherence_residual),
|
||||
},
|
||||
epistemic_status=EpistemicStatus.SPECULATIVE,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 1 — flag-off: no promotion when vault_promotion_enabled=False
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_speculative_entry_not_promoted_when_flag_off() -> None:
|
||||
"""Vault entries remain SPECULATIVE when vault_promotion_enabled=False (default)."""
|
||||
runtime = ChatRuntime(
|
||||
config=RuntimeConfig(
|
||||
vault_promotion_enabled=False,
|
||||
output_language="en",
|
||||
frame_pack="en",
|
||||
)
|
||||
)
|
||||
|
||||
for text in ("word truth", "light word", "begin truth"):
|
||||
runtime.chat(text)
|
||||
|
||||
vault = runtime.session.vault
|
||||
assert len(vault) > 0, "Vault must have entries after chat turns"
|
||||
|
||||
statuses = [m.get("epistemic_status") for m in vault._metadata]
|
||||
assert all(s == EpistemicStatus.SPECULATIVE.value for s in statuses), (
|
||||
"All entries should stay SPECULATIVE when vault_promotion_enabled=False"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 2 — promote_eligible_entries promotes E0/low-residual entry to COHERENT
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_promote_eligible_entries_promotes_coherent_entry() -> None:
|
||||
"""An E0 entry with coherence_residual=0.02 should be promoted to COHERENT."""
|
||||
vault = VaultStore()
|
||||
policy = VaultPromotionPolicy(residual_threshold=0.05)
|
||||
|
||||
_store_entry(vault, seed=1, energy_class=EnergyClass.E0, coherence_residual=0.02, raw=0.05)
|
||||
|
||||
count = vault.promote_eligible_entries(policy)
|
||||
|
||||
assert count == 1, "Expected exactly 1 promotion"
|
||||
assert vault._metadata[0]["epistemic_status"] == EpistemicStatus.COHERENT.value, (
|
||||
"Entry should be promoted to COHERENT"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 3 — active-energy entry (E3) is NOT promoted
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_promote_eligible_entries_skips_active_energy() -> None:
|
||||
"""An E3 entry (vault_candidate=False) must not be promoted."""
|
||||
vault = VaultStore()
|
||||
policy = VaultPromotionPolicy(residual_threshold=0.05)
|
||||
|
||||
_store_entry(vault, seed=2, energy_class=EnergyClass.E3, coherence_residual=0.01, raw=0.70)
|
||||
|
||||
count = vault.promote_eligible_entries(policy)
|
||||
|
||||
assert count == 0, "E3 entry must not be promoted (region still active)"
|
||||
assert vault._metadata[0]["epistemic_status"] == EpistemicStatus.SPECULATIVE.value
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 4 — E0 entry with high coherence_residual is NOT promoted
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_promote_eligible_entries_skips_high_residual() -> None:
|
||||
"""An E0 entry with coherence_residual=0.10 must not be promoted (above threshold=0.05)."""
|
||||
vault = VaultStore()
|
||||
policy = VaultPromotionPolicy(residual_threshold=0.05)
|
||||
|
||||
_store_entry(vault, seed=3, energy_class=EnergyClass.E0, coherence_residual=0.10, raw=0.05)
|
||||
|
||||
count = vault.promote_eligible_entries(policy)
|
||||
|
||||
assert count == 0, "High-residual E0 entry must not be promoted"
|
||||
assert vault._metadata[0]["epistemic_status"] == EpistemicStatus.SPECULATIVE.value
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 5 — promote_eligible_entries returns correct count
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_promotion_count_returned() -> None:
|
||||
"""promote_eligible_entries returns the number of entries actually promoted."""
|
||||
vault = VaultStore()
|
||||
policy = VaultPromotionPolicy(residual_threshold=0.05)
|
||||
|
||||
# 2 promotable (E0, low residual)
|
||||
_store_entry(vault, seed=10, energy_class=EnergyClass.E0, coherence_residual=0.01, raw=0.05)
|
||||
_store_entry(vault, seed=11, energy_class=EnergyClass.E1, coherence_residual=0.03, raw=0.18)
|
||||
# 1 NOT promotable (E3)
|
||||
_store_entry(vault, seed=12, energy_class=EnergyClass.E3, coherence_residual=0.00, raw=0.70)
|
||||
# 1 NOT promotable (E0 but high residual)
|
||||
_store_entry(vault, seed=13, energy_class=EnergyClass.E0, coherence_residual=0.08, raw=0.05)
|
||||
|
||||
count = vault.promote_eligible_entries(policy)
|
||||
|
||||
assert count == 2, f"Expected 2 promotions, got {count}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 6 — energy stored in vault metadata after finalize_turn
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_energy_stored_in_vault_metadata() -> None:
|
||||
"""After a finalize_turn call, vault metadata contains energy_raw, energy_class,
|
||||
and coherence_residual for the assistant turn entry."""
|
||||
runtime = ChatRuntime(
|
||||
config=RuntimeConfig(
|
||||
vault_promotion_enabled=False,
|
||||
output_language="en",
|
||||
frame_pack="en",
|
||||
)
|
||||
)
|
||||
|
||||
runtime.chat("light truth word")
|
||||
|
||||
vault = runtime.session.vault
|
||||
assert len(vault) >= 1, "Vault must have at least one entry after a chat turn"
|
||||
|
||||
# Find assistant turn entries (role=="assistant"); they carry energy metadata
|
||||
# when oriented_state.energy is not None.
|
||||
assistant_entries = [
|
||||
m for m in vault._metadata if m.get("role") == "assistant"
|
||||
]
|
||||
assert assistant_entries, "Expected at least one assistant entry in vault"
|
||||
|
||||
# At least one assistant entry should carry energy metadata.
|
||||
# (If oriented_state.energy is None for all entries, the test would fail,
|
||||
# which would correctly surface a gap in energy propagation.)
|
||||
entries_with_energy = [
|
||||
m for m in assistant_entries
|
||||
if "energy_raw" in m and "energy_class" in m and "coherence_residual" in m
|
||||
]
|
||||
assert entries_with_energy, (
|
||||
"Expected at least one assistant vault entry to carry energy metadata "
|
||||
"(energy_raw, energy_class, coherence_residual)"
|
||||
)
|
||||
|
||||
for m in entries_with_energy:
|
||||
assert isinstance(m["energy_raw"], float), "energy_raw must be a float"
|
||||
assert isinstance(m["energy_class"], str), "energy_class must be a string"
|
||||
# Verify the energy_class value is a valid EnergyClass member
|
||||
EnergyClass(m["energy_class"]) # raises ValueError if invalid
|
||||
assert isinstance(m["coherence_residual"], float), "coherence_residual must be a float"
|
||||
assert 0.0 <= m["coherence_residual"] <= 1.0, (
|
||||
f"coherence_residual out of range: {m['coherence_residual']}"
|
||||
)
|
||||
|
|
@ -13,6 +13,7 @@ O(N) np.array_equal scans.
|
|||
from __future__ import annotations
|
||||
|
||||
from collections import deque
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
from algebra.backend import vault_recall, vault_recall_batch
|
||||
|
|
@ -21,6 +22,9 @@ from core.epistemic_state import EpistemicState
|
|||
from core.physics.energy import EnergyClass, EnergyProfile
|
||||
from teaching.epistemic import ADMISSIBLE_AS_EVIDENCE, EpistemicStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.physics.learning import VaultPromotionPolicy
|
||||
|
||||
|
||||
# ADR-0006 §"Integration Points":
|
||||
# "Vault recall re-activates the region to E2 transiently, then lets it
|
||||
|
|
@ -290,6 +294,56 @@ class VaultStore:
|
|||
])
|
||||
return results
|
||||
|
||||
def promote_eligible_entries(self, policy: "VaultPromotionPolicy") -> int:
|
||||
"""Scan SPECULATIVE entries; promote to COHERENT where policy decides.
|
||||
|
||||
For each SPECULATIVE entry that carries stored energy metadata, reconstructs
|
||||
an EnergyProfile and calls policy.decide(). Entries that pass are updated
|
||||
to COHERENT in-place (metadata only — versors are unchanged, so
|
||||
_matrix_cache is not invalidated).
|
||||
|
||||
Returns the count of promotions made in this call.
|
||||
ADR-0148.
|
||||
"""
|
||||
from core.physics.energy import EnergyClass as _EnergyClass, EnergyProfile as _EnergyProfile
|
||||
|
||||
promoted = 0
|
||||
for meta in self._metadata:
|
||||
raw_status = meta.get("epistemic_status", "speculative")
|
||||
try:
|
||||
entry_status = (
|
||||
raw_status
|
||||
if isinstance(raw_status, EpistemicStatus)
|
||||
else EpistemicStatus(raw_status)
|
||||
)
|
||||
except ValueError:
|
||||
entry_status = EpistemicStatus.SPECULATIVE
|
||||
if entry_status is not EpistemicStatus.SPECULATIVE:
|
||||
continue
|
||||
# Reconstruct EnergyProfile from stored metadata fields.
|
||||
# If energy metadata is absent, pass None so the policy returns
|
||||
# "missing_energy_profile" rather than guessing.
|
||||
energy: _EnergyProfile | None = None
|
||||
if (
|
||||
"energy_raw" in meta
|
||||
and "energy_class" in meta
|
||||
and "coherence_residual" in meta
|
||||
):
|
||||
try:
|
||||
ec = _EnergyClass(meta["energy_class"])
|
||||
energy = _EnergyProfile(
|
||||
raw=float(meta["energy_raw"]),
|
||||
energy_class=ec,
|
||||
coherence_residual=float(meta["coherence_residual"]),
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
energy = None
|
||||
decision = policy.decide(energy)
|
||||
if decision.promote:
|
||||
meta["epistemic_status"] = EpistemicStatus.COHERENT.value
|
||||
promoted += 1
|
||||
return promoted
|
||||
|
||||
def reproject(self) -> None:
|
||||
"""
|
||||
Re-project all stored versors onto the null cone.
|
||||
|
|
|
|||
Loading…
Reference in a new issue