VIOLET V3b: Cadence Control Plane — universal per-action quantization
Spec prod/docs/VIOLET_SPEC__CADENCE_CONTROL_PLANE.md + cadence.py: every scan-governed action (entry/sizing/each exit reason/each input plane) carries an INDEPENDENT, runtime-tunable Q knob surfaced in a control plane (HZ-backed, code defaults as floor). Evaluate-every-tick / actuate-at-Q; SL defaults insta, TP/entry default scan, OBF ~1s — all loosenable per-action. 9 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
170
prod/clean_arch/violet/cadence.py
Normal file
170
prod/clean_arch/violet/cadence.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"""VIOLET V3b: Cadence Control Plane — per-action quantization.
|
||||||
|
|
||||||
|
Spec: prod/docs/VIOLET_SPEC__CADENCE_CONTROL_PLANE.md (BINDING).
|
||||||
|
|
||||||
|
VIOLET is a reactor substrate (V0 clock/DeadlineScheduler); BLUE's scan-quantized
|
||||||
|
behaviour is a guest hosted on it. Scan cadence is a QUANTIZATION SETTING, not the
|
||||||
|
architecture. Every scan-governed action is *evaluated* every reactor tick (the
|
||||||
|
would-be action shadow-logged) and *actuated* only when its per-action quantum Q is
|
||||||
|
crossed. Each action's Q is:
|
||||||
|
|
||||||
|
- UNIVERSAL — every action (entry, sizing, each exit reason, each input plane)
|
||||||
|
has one; nothing is hardcoded at scan. SL merely DEFAULTS tighter.
|
||||||
|
- INDEPENDENT — changing one action's Q never touches another's.
|
||||||
|
- CONTROL-PLANE — readable/writable at runtime (HZ-backed via refresh_from); code
|
||||||
|
defaults are the floor when the control plane is silent.
|
||||||
|
- LOOSENABLE — Q steps down per-action on its own schedule, each step gated on
|
||||||
|
the shadow deltas (evaluate-vs-actuate) this layer makes visible.
|
||||||
|
|
||||||
|
Exit-priority (CATASTROPHIC/ADVSL > mechanical TP > discretionary v7) is INVARIANT to
|
||||||
|
Q and is enforced by the decision engine, not here — a faster Q never lets a
|
||||||
|
discretionary exit mask a mechanical one (the LINK -$1,248 bug).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import enum
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable, Dict, Optional
|
||||||
|
|
||||||
|
# ── quantum constants (nanoseconds) ───────────────────────────────────────────
|
||||||
|
INSTA_Q_NS = 0 # actuate every reactor tick (no quantization)
|
||||||
|
OBF_Q_NS = 1_000_000_000 # ~1s — OBF effective cadence (fastest service)
|
||||||
|
SCAN_Q_NS = 5_000_000_000 # 5s — NG7 scan cadence (the certification anchor)
|
||||||
|
|
||||||
|
|
||||||
|
class Action(str, enum.Enum):
|
||||||
|
"""Every scan-governed VIOLET action that carries an independent cadence knob."""
|
||||||
|
|
||||||
|
CATASTROPHIC_SL = "catastrophic_sl"
|
||||||
|
ADVSL = "advsl"
|
||||||
|
TP = "tp"
|
||||||
|
V7_EXIT = "v7_exit"
|
||||||
|
ENTRY = "entry"
|
||||||
|
SIZING = "sizing"
|
||||||
|
CONSUME_SCAN = "consume_scan"
|
||||||
|
CONSUME_OBF = "consume_obf"
|
||||||
|
CONSUME_EXF = "consume_exf"
|
||||||
|
CONSUME_ESOF = "consume_esof"
|
||||||
|
CONSUME_MARAS = "consume_maras"
|
||||||
|
CONSUME_ACB = "consume_acb"
|
||||||
|
|
||||||
|
|
||||||
|
# Default Q per action (spec §3). SL-class defaults to insta (the safety deviation);
|
||||||
|
# everything else defaults to its source cadence — all loosenable independently.
|
||||||
|
_DEFAULT_Q_NS: Dict[Action, int] = {
|
||||||
|
Action.CATASTROPHIC_SL: INSTA_Q_NS,
|
||||||
|
Action.ADVSL: INSTA_Q_NS,
|
||||||
|
Action.TP: SCAN_Q_NS,
|
||||||
|
Action.V7_EXIT: SCAN_Q_NS,
|
||||||
|
Action.ENTRY: SCAN_Q_NS,
|
||||||
|
Action.SIZING: SCAN_Q_NS,
|
||||||
|
Action.CONSUME_SCAN: SCAN_Q_NS,
|
||||||
|
Action.CONSUME_OBF: OBF_Q_NS,
|
||||||
|
Action.CONSUME_EXF: SCAN_Q_NS,
|
||||||
|
Action.CONSUME_ESOF: SCAN_Q_NS,
|
||||||
|
Action.CONSUME_MARAS: SCAN_Q_NS,
|
||||||
|
Action.CONSUME_ACB: SCAN_Q_NS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CadenceKnob:
|
||||||
|
"""One action's tunable cadence. Mutable — the control plane is its only mutator.
|
||||||
|
|
||||||
|
``q_ns`` is the actuation quantum: 0 ⇒ actuate every reactor tick (insta); else
|
||||||
|
actuate once ``q_ns`` has elapsed since the last actuation. ``evaluate_every_tick``
|
||||||
|
keeps the would-be action computed (and shadow-logged) at full reactor speed
|
||||||
|
regardless of Q.
|
||||||
|
"""
|
||||||
|
|
||||||
|
action: Action
|
||||||
|
q_ns: int
|
||||||
|
evaluate_every_tick: bool = True
|
||||||
|
enabled: bool = True
|
||||||
|
source: str = "default" # "default" | "control_plane"
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if int(self.q_ns) < 0:
|
||||||
|
raise ValueError(f"q_ns must be >= 0 (got {self.q_ns} for {self.action})")
|
||||||
|
self.q_ns = int(self.q_ns)
|
||||||
|
|
||||||
|
|
||||||
|
class CadenceControlPlane:
|
||||||
|
"""Runtime registry of per-action cadence knobs (spec §4).
|
||||||
|
|
||||||
|
Seeded from code defaults; ``refresh_from`` layers a runtime provider (HZ map /
|
||||||
|
env) on top, per action, leaving untouched actions at their current value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._knobs: Dict[Action, CadenceKnob] = {
|
||||||
|
a: CadenceKnob(action=a, q_ns=q) for a, q in _DEFAULT_Q_NS.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(self, action: Action) -> CadenceKnob:
|
||||||
|
return self._knobs[action]
|
||||||
|
|
||||||
|
def set(
|
||||||
|
self, action: Action, *,
|
||||||
|
q_ns: Optional[int] = None,
|
||||||
|
evaluate_every_tick: Optional[bool] = None,
|
||||||
|
enabled: Optional[bool] = None,
|
||||||
|
source: str = "control_plane",
|
||||||
|
) -> CadenceKnob:
|
||||||
|
"""Independently override one action. Other actions are never touched."""
|
||||||
|
k = self._knobs[action]
|
||||||
|
new = CadenceKnob(
|
||||||
|
action=action,
|
||||||
|
q_ns=k.q_ns if q_ns is None else int(q_ns),
|
||||||
|
evaluate_every_tick=k.evaluate_every_tick if evaluate_every_tick is None else bool(evaluate_every_tick),
|
||||||
|
enabled=k.enabled if enabled is None else bool(enabled),
|
||||||
|
source=source,
|
||||||
|
)
|
||||||
|
self._knobs[action] = new
|
||||||
|
return new
|
||||||
|
|
||||||
|
def refresh_from(self, provider: Callable[[Action], Optional[dict]]) -> int:
|
||||||
|
"""Pull runtime overrides from a provider (HZ map / env reader).
|
||||||
|
|
||||||
|
``provider(action)`` returns a dict of overrides or None (no override → keep
|
||||||
|
current value, i.e. the code-default floor). Returns the count of actions
|
||||||
|
updated. The control plane stays authoritative for live tuning.
|
||||||
|
"""
|
||||||
|
n = 0
|
||||||
|
for action in Action:
|
||||||
|
ov = provider(action)
|
||||||
|
if not ov:
|
||||||
|
continue
|
||||||
|
self.set(action, **{k: v for k, v in ov.items()
|
||||||
|
if k in ("q_ns", "evaluate_every_tick", "enabled")})
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
def due(self, action: Action, now_ns: int, last_actuation_ns: Optional[int]) -> bool:
|
||||||
|
"""Q-boundary test: should this action ACTUATE now?
|
||||||
|
|
||||||
|
Disabled ⇒ never. ``last_actuation_ns is None`` (never actuated) ⇒ yes.
|
||||||
|
q_ns == 0 (insta) ⇒ every tick. Else ⇒ once the quantum has elapsed.
|
||||||
|
"""
|
||||||
|
k = self._knobs[action]
|
||||||
|
if not k.enabled:
|
||||||
|
return False
|
||||||
|
if last_actuation_ns is None:
|
||||||
|
return True
|
||||||
|
if k.q_ns == 0:
|
||||||
|
return True
|
||||||
|
return (int(now_ns) - int(last_actuation_ns)) >= k.q_ns
|
||||||
|
|
||||||
|
# Alias matching spec §4 caller idiom (evaluate always, then consult to actuate).
|
||||||
|
should_actuate = due
|
||||||
|
|
||||||
|
def snapshot(self) -> Dict[str, dict]:
|
||||||
|
"""Surfaced view for live inspection (spec §4)."""
|
||||||
|
return {
|
||||||
|
a.value: {
|
||||||
|
"q_ns": k.q_ns, "evaluate_every_tick": k.evaluate_every_tick,
|
||||||
|
"enabled": k.enabled, "source": k.source,
|
||||||
|
}
|
||||||
|
for a, k in self._knobs.items()
|
||||||
|
}
|
||||||
96
prod/clean_arch/violet/test_violet_cadence.py
Normal file
96
prod/clean_arch/violet/test_violet_cadence.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
"""V3b: Cadence Control Plane — defaults, independence, control-plane, Q boundaries."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, "/mnt/dolphinng5_predict")
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from hypothesis import given, settings, strategies as st
|
||||||
|
|
||||||
|
from prod.clean_arch.violet.cadence import (
|
||||||
|
INSTA_Q_NS, OBF_Q_NS, SCAN_Q_NS, Action, CadenceControlPlane, CadenceKnob,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_match_spec_table():
|
||||||
|
cp = CadenceControlPlane()
|
||||||
|
assert cp.get(Action.CATASTROPHIC_SL).q_ns == INSTA_Q_NS
|
||||||
|
assert cp.get(Action.ADVSL).q_ns == INSTA_Q_NS
|
||||||
|
assert cp.get(Action.TP).q_ns == SCAN_Q_NS
|
||||||
|
assert cp.get(Action.CONSUME_OBF).q_ns == OBF_Q_NS
|
||||||
|
assert cp.get(Action.ENTRY).q_ns == SCAN_Q_NS
|
||||||
|
# SL-class defaults strictly tighter than TP (the safety deviation).
|
||||||
|
assert cp.get(Action.CATASTROPHIC_SL).q_ns < cp.get(Action.TP).q_ns
|
||||||
|
# every action is registered (universality) and evaluates every tick.
|
||||||
|
for a in Action:
|
||||||
|
assert cp.get(a).evaluate_every_tick is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_is_independent_per_action():
|
||||||
|
cp = CadenceControlPlane()
|
||||||
|
before = {a: cp.get(a).q_ns for a in Action}
|
||||||
|
cp.set(Action.TP, q_ns=1_000_000_000)
|
||||||
|
assert cp.get(Action.TP).q_ns == 1_000_000_000
|
||||||
|
assert cp.get(Action.TP).source == "control_plane"
|
||||||
|
# nothing else moved
|
||||||
|
for a in Action:
|
||||||
|
if a is Action.TP:
|
||||||
|
continue
|
||||||
|
assert cp.get(a).q_ns == before[a]
|
||||||
|
assert cp.get(a).source == "default"
|
||||||
|
|
||||||
|
|
||||||
|
def test_control_plane_override_beats_default_absence_falls_back():
|
||||||
|
cp = CadenceControlPlane()
|
||||||
|
overrides = {Action.TP: {"q_ns": 250_000_000}, Action.ENTRY: {"enabled": False}}
|
||||||
|
n = cp.refresh_from(lambda a: overrides.get(a))
|
||||||
|
assert n == 2
|
||||||
|
assert cp.get(Action.TP).q_ns == 250_000_000 # overridden
|
||||||
|
assert cp.get(Action.ENTRY).enabled is False
|
||||||
|
assert cp.get(Action.SIZING).q_ns == SCAN_Q_NS # untouched → default floor
|
||||||
|
|
||||||
|
|
||||||
|
def test_negative_q_rejected_at_construction():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
CadenceKnob(action=Action.TP, q_ns=-1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_due_insta_actuates_every_tick():
|
||||||
|
cp = CadenceControlPlane()
|
||||||
|
assert cp.due(Action.CATASTROPHIC_SL, now_ns=1000, last_actuation_ns=999) is True
|
||||||
|
# never-actuated always due
|
||||||
|
assert cp.due(Action.TP, now_ns=0, last_actuation_ns=None) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_due_scan_respects_quantum():
|
||||||
|
cp = CadenceControlPlane()
|
||||||
|
last = 1_000_000_000
|
||||||
|
assert cp.due(Action.TP, now_ns=last + SCAN_Q_NS - 1, last_actuation_ns=last) is False
|
||||||
|
assert cp.due(Action.TP, now_ns=last + SCAN_Q_NS, last_actuation_ns=last) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_disabled_action_never_due():
|
||||||
|
cp = CadenceControlPlane()
|
||||||
|
cp.set(Action.V7_EXIT, enabled=False)
|
||||||
|
assert cp.due(Action.V7_EXIT, now_ns=10**12, last_actuation_ns=None) is False
|
||||||
|
|
||||||
|
|
||||||
|
@given(
|
||||||
|
q=st.integers(min_value=0, max_value=10_000_000_000),
|
||||||
|
elapsed=st.integers(min_value=0, max_value=20_000_000_000),
|
||||||
|
)
|
||||||
|
@settings(max_examples=100, deadline=None)
|
||||||
|
def test_due_boundary_property(q, elapsed):
|
||||||
|
cp = CadenceControlPlane()
|
||||||
|
cp.set(Action.ENTRY, q_ns=q)
|
||||||
|
last = 5_000_000_000
|
||||||
|
due = cp.due(Action.ENTRY, now_ns=last + elapsed, last_actuation_ns=last)
|
||||||
|
assert due == (q == 0 or elapsed >= q)
|
||||||
|
|
||||||
|
|
||||||
|
def test_snapshot_surfaces_all_actions():
|
||||||
|
snap = CadenceControlPlane().snapshot()
|
||||||
|
assert set(snap.keys()) == {a.value for a in Action}
|
||||||
|
assert all({"q_ns", "enabled", "source"} <= set(v) for v in snap.values())
|
||||||
97
prod/docs/VIOLET_SPEC__CADENCE_CONTROL_PLANE.md
Normal file
97
prod/docs/VIOLET_SPEC__CADENCE_CONTROL_PLANE.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# VIOLET Spec — Cadence Control Plane (per-action quantization)
|
||||||
|
|
||||||
|
**Status:** BINDING for V3b (`prod/clean_arch/violet/cadence.py`). Authored 2026-06-13
|
||||||
|
from operator doctrine this session. Companion to the master charter
|
||||||
|
(memory `violet_subsecond_rebuild_plan`, cadence-quantizer section) and
|
||||||
|
`violet_v3_alpha_doctrine` (points #6, #8). This spec is the painstaking per-ACTION
|
||||||
|
quantization layer the charter only sketched as a principle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The inversion (why this layer exists)
|
||||||
|
|
||||||
|
BLUE's clock **is** its architecture: every decision/action is denominated in 5–6 s
|
||||||
|
scans. VIOLET inverts this — the architecture is the **event-driven / clockless
|
||||||
|
(fastest-clock) reactor** (V0: `clock.py` mono_ns timebase + `DeadlineScheduler`).
|
||||||
|
BLUE's scan-quantized behaviour is a **guest** hosted on that reactor. The scan
|
||||||
|
cadence is a **quantization setting, not the architecture.**
|
||||||
|
|
||||||
|
At first, every action is quantized at **Q = scan cadence** → bit-faithful BLUE
|
||||||
|
replication (warts and all). Over time each action's Q can be **loosened
|
||||||
|
independently** toward the reactor's faster clock. This is the certification-preserving
|
||||||
|
path: champion params are 5 s-bar-denominated (BIBLE §22.3), so loosening is a
|
||||||
|
deliberate, measured, per-action promotion — never a global flip.
|
||||||
|
|
||||||
|
## 2. Core rules
|
||||||
|
|
||||||
|
1. **Evaluate at fastest cadence; actuate at Q.** Every action is *evaluated* every
|
||||||
|
reactor tick (the would-be action is computed and **shadow-logged** — this is the
|
||||||
|
evidence trail). The action is only *actuated* when its quantization boundary Q is
|
||||||
|
crossed. Shadow deltas (would-be-at-fast vs actuated-at-Q) are the data that
|
||||||
|
justifies each loosening step.
|
||||||
|
2. **Adjustability is UNIVERSAL.** EVERY scan-governed activity carries its own
|
||||||
|
tunable Q — entry, sizing, each exit reason (TP, catastrophic SL, ADVSL, v7),
|
||||||
|
AND each input-plane consumption (scan, OBF, ExoF, EsoF, MARAS, ACB). No action is
|
||||||
|
hardcoded at scan. SL merely **defaults** to a tighter Q; mechanically it is the
|
||||||
|
same adjustable primitive as everything else.
|
||||||
|
3. **Per-action knobs are INDEPENDENTLY configurable.** Changing TP's Q must not
|
||||||
|
touch entry's Q. Each action is its own registry entry.
|
||||||
|
4. **Knobs are SURFACED IN A CONTROL PLANE.** The registry is readable and writable at
|
||||||
|
runtime for live inspection and tuning — not compile-time constants. HZ-backed at
|
||||||
|
runtime (mirroring how BLUE reads `DOLPHIN_FEATURES["live_tp_threshold"]`); code
|
||||||
|
defaults are the floor/fallback when the control plane is silent.
|
||||||
|
5. **Loosening is per-reason and measured.** Q steps down per action on its own
|
||||||
|
schedule (e.g. 6s→3s→1s→500ms→100ms→50ms), each promotion gated on the shadow
|
||||||
|
deltas from rule (1). SL-class first; TP later (TP at scan has desirable properties).
|
||||||
|
|
||||||
|
## 3. Per-action default Q table (initial state — all loosenable)
|
||||||
|
|
||||||
|
| Action | Class | Default Q | Loosening intent |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Catastrophic SL | exit/safety | **tight VIOLET / insta-exec** | sub-second from early (the versioned safety deviation) |
|
||||||
|
| ADVSL | exit/safety | tight VIOLET (after min-bars gate) | sub-second early |
|
||||||
|
| TP (mechanical) | exit | **scan** | loosen *cautiously* (desirable at scan) |
|
||||||
|
| v7 discretionary exit | exit | scan | as-is first; per-reason later |
|
||||||
|
| Entry | entry | scan | cautious (champion params 5s-denominated) |
|
||||||
|
| Sizing (conviction) | sizing | scan | cautious (couples to entry) |
|
||||||
|
| Scan plane consume | input | scan (~5–6s native) | n/a (source cadence) |
|
||||||
|
| OBF plane consume | input | **~1s** (500ms purported / ~1s effective) | toward VIOLET clock — fastest service |
|
||||||
|
| ExoF / EsoF / MARAS / ACB consume | input | scan | adjustable too, though sources are orders slower |
|
||||||
|
|
||||||
|
Exit priority is INVARIANT regardless of Q: CATASTROPHIC/ADVSL > mechanical TP >
|
||||||
|
discretionary (v7). A faster Q never lets a discretionary exit mask a mechanical one
|
||||||
|
(the LINK −$1,248 bug).
|
||||||
|
|
||||||
|
## 4. Design (`cadence.py`)
|
||||||
|
|
||||||
|
- **`Action`** — enum/identifier per row of §3 (CATASTROPHIC_SL, ADVSL, TP, V7_EXIT,
|
||||||
|
ENTRY, SIZING, CONSUME_SCAN, CONSUME_OBF, CONSUME_EXF, CONSUME_ESOF, CONSUME_MARAS,
|
||||||
|
CONSUME_ACB).
|
||||||
|
- **`CadenceKnob`** (V-TYPES `StrictModel`-style, but mutable via the control plane) —
|
||||||
|
per action: `q_ns` (actuation quantum; 0 ⇒ every reactor tick = "insta"),
|
||||||
|
`evaluate_every_tick: bool` (default True), `enabled: bool`, `source: "default"|"control_plane"`.
|
||||||
|
- **`CadenceControlPlane`** — the registry: `get(action) -> CadenceKnob`,
|
||||||
|
`set(action, **overrides)` (independent per action), `snapshot() -> dict` (surfaced
|
||||||
|
for inspection), `refresh_from(provider)` where `provider` reads the HZ map / env at
|
||||||
|
runtime; code defaults seed it. `due(action, now_ns, last_actuation_ns) -> bool`
|
||||||
|
(Q-boundary test, integrates with `DeadlineScheduler`).
|
||||||
|
- **Evaluate/actuate split helper** — `should_actuate(action, now_ns) -> bool`;
|
||||||
|
callers always evaluate, then consult this to actuate, and emit the shadow-delta
|
||||||
|
telemetry on the gap.
|
||||||
|
|
||||||
|
## 5. Tests (V3b)
|
||||||
|
|
||||||
|
- defaults match §3 table; SL defaults tighter than TP.
|
||||||
|
- `set` on one action leaves all others unchanged (independence).
|
||||||
|
- control-plane override beats default; absence falls back to default (floor).
|
||||||
|
- `due`/`should_actuate` honors Q boundaries (insta Q=0 actuates every tick; scan Q
|
||||||
|
actuates once per scan interval), property-tested with hypothesis.
|
||||||
|
- evaluate-always invariant: evaluation count ≥ actuation count for every action.
|
||||||
|
- exit-priority invariant unaffected by Q (a fast discretionary Q never preempts a
|
||||||
|
mechanical exit in the ordering).
|
||||||
|
|
||||||
|
## 6. Related
|
||||||
|
|
||||||
|
`violet_subsecond_rebuild_plan` (charter) · `violet_v3_alpha_doctrine` #6/#8 ·
|
||||||
|
`clock.py`/`DeadlineScheduler` (V0 substrate) · `decision_engine.py` (V3c consumer) ·
|
||||||
|
`prod/docs/TODO_TP_SCAN_CADENCE_BUGFIX.md` (TP-at-scan rationale).
|
||||||
Reference in New Issue
Block a user