* 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>
3.8 KiB
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)
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():
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:
- Parses the stored
epistemic_statusstring. - If SPECULATIVE, reconstructs a minimal
EnergyProfilefrom the storedenergy_raw,energy_class,coherence_residualfields. - Calls
policy.decide(energy). - If
decision.promote, updatesepistemic_statustoCOHERENTin-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():
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.COHERENTrecall. - 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_entriesmutates only_metadatadicts. - No normalization:
vault/store.pyis a forbidden normalization site perCLAUDE.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_statuson 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.COHERENTand receive crystallized entries as recognition anchors.