Skip to content

feat(governor): Lane H PR-3c1 — cascade evaluator pure function + CascadeThresholds#1356

Merged
joelteply merged 2 commits into
canaryfrom
feat/substrate-governor-pr3c1-cascade-evaluator
May 17, 2026
Merged

feat(governor): Lane H PR-3c1 — cascade evaluator pure function + CascadeThresholds#1356
joelteply merged 2 commits into
canaryfrom
feat/substrate-governor-pr3c1-cascade-evaluator

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

Lane H PR-3c1 per GENOME-FOUNDRY-SENTINEL #1327 Part 11 §'Adjustment Cascade'. Stacks on #1354 (PR-3b LocalSubstrateGovernor, MERGED).

PR-3b ships LocalSubstrateGovernor that RECORDS pressure signals. This PR-3c1 ships the pure-function cascade evaluator — given (current step, signal, thresholds), decide Advance / Retreat / Hold / EmergencyAdvanceToMax. PR-3c2 wires the evaluator into on_pressure_signal to actually transition + rewrite policy.

Splitting PR-3c into atomic sub-slices: this PR-3c1 (pure evaluator), future PR-3c2 (wiring + time-in-step gate + apply_action_to_policy).

What ships

src/workers/continuum-core/src/governor/cascade.rs:

  • CASCADE_STEP_MIN (0) + CASCADE_STEP_MAX (5) — typed bounds
  • CascadeAction enum (ts-rs tagged-union): Hold / Advance / Retreat / EmergencyAdvanceToMax
  • CascadeThresholds struct (ts-rs camelCase) with defaults pinned to spec §"Adjustment Cascade" table values
  • evaluate_next_step(current_step, signal, thresholds) -> CascadeAction — pure function, no I/O, no time, no globals
  • apply_action(current_step, action) -> u8 — pure function, bounded [MIN, MAX]

Emergency-priority semantics (per spec)

  • Thermal::Critical from any step → EmergencyAdvanceToMax (protect hardware)
  • BatteryLow < emergency_pct (10%) → EmergencyAdvanceToMax (protect user)

Both checked BEFORE step-specific evaluation; emergency beats all.

Hysteresis (anti-oscillation)

Each pressure dimension has separate advance + retreat thresholds with a gap:

  • Speculation miss: advance > 0.5, retreat < 0.3 (gap is Hold)
  • VRAM: advance > 85%, retreat < 70%
  • Queue depth: advance > 16, retreat < 8
  • System mem: advance > 85%, retreat < 70%
  • Battery: advance < 15%, retreat > 25%

Prevents flapping around a single threshold.

Test plan

30 passing on cargo test --lib --features metal,accelerate governor::cascade::

  • Emergency (3): Thermal Critical from any step / Battery < emergency / Battery AT boundary
  • Step 0 → 1 advance triggers (4): spec miss high / at threshold / queue high / VRAM high+at threshold
  • Step 1 retreats (4): spec miss low / hysteresis gap holds / queue low / VRAM low
  • Step 1 → 2 advances (2): system mem high / thermal Hot
  • Step 2/3/4/5 transitions (4): battery low advances step 2 / mem clear retreats / steps 3/4 battery recovered / step 5 ONLY Cool retreats
  • UserActive holds at every step (1)
  • apply_action: Hold keeps / Advance bumps capped / Retreat saturated / Emergency jumps from any step
  • Determinism + serde round-trips
  • CascadeThresholds defaults match spec table (drift catcher)
  • Emergency priority over step evaluation
  • ts-rs export bindings (2: CascadeAction, CascadeThresholds)

Stack

VDD evidence

N/A — pure function. Evidence with PR-3c2 when actual policy mutations land + with PR-4 when real pressure flows.

Coordination

The branch creation hit a multi-tab worktree collision (codex was on feat/perf-harness-chat-roundtrip in the shared continuum-scope worktree; my commit briefly landed on their branch ref). Corrected by git update-ref, codex's branch reset to canary, no work lost. The follow-up 09ce4b032 commit added the ts-rs bindings + barrel that were missed in the initial commit due to the same race.

Test added 2 commits May 16, 2026 18:44
…cadeThresholds

Per GENOME-FOUNDRY-SENTINEL #1327 Part 11 §'Adjustment Cascade'.
Stacks on #1354 (PR-3b LocalSubstrateGovernor, MERGED).

PR-3b ships LocalSubstrateGovernor that RECORDS pressure signals.
This PR-3c1 ships the pure-function CASCADE EVALUATOR — given
(current step, signal, thresholds), decide Advance/Retreat/Hold/
EmergencyAdvanceToMax. PR-3c2 wires the evaluator into
on_pressure_signal to actually transition + rewrite policy.

What ships in src/workers/continuum-core/src/governor/cascade.rs:

- CASCADE_STEP_MIN (0) + CASCADE_STEP_MAX (5) — typed bounds
- CascadeAction enum: Hold / Advance / Retreat / EmergencyAdvanceToMax
  (tagged-union with 'kind' discriminator, ts-rs export)
- CascadeThresholds struct (ts-rs export):
  * spec_miss_rate_advance/retreat (default 0.5/0.3 per spec table)
  * inference_queue_depth_advance/retreat (16/8)
  * vram_used_pct_advance/retreat (85/70)
  * system_mem_used_pct_advance/retreat (85/70)
  * thermal_advance (Hot per spec)
  * battery_pct_advance/retreat/emergency (15/25/10)
- evaluate_next_step(current_step, signal, thresholds) -> CascadeAction
  Pure fn. No I/O, no time, no globals.
- apply_action(current_step, action) -> u8
  Pure fn. Bounded to [MIN, MAX]; Advance caps at MAX; Retreat
  saturates at MIN; EmergencyAdvanceToMax jumps from any step.

Emergency-priority semantics (per spec):
- Thermal::Critical → EmergencyAdvanceToMax (protects hardware)
- BatteryLow < emergency_pct (10%) → EmergencyAdvanceToMax (protects user)
Both checked BEFORE step-specific evaluation; emergency beats all.

Hysteresis (anti-oscillation):
- Step 1 advance trigger: SpeculationMissRate > 0.5
- Step 1 retreat trigger: SpeculationMissRate < 0.3
- Gap (0.3 → 0.5) is Hold; prevents flapping around a single threshold
- Same shape for VRAM (70/85), queue (8/16), mem (70/85), battery (15/25)

Failure-mode discipline:
- All thresholds typed + named (no magic floats/ints in call sites)
- Pure function — same inputs → same output (testable + replayable)
- UserActive signal participates but doesn't drive transitions in PR-3c1
  (PR-3c2 may use it for user-foreground weighting)
- Unmatched (step, signal) combos return Hold (do nothing > panic)

Tests: 30 passing on cargo test --lib --features metal,accelerate
governor::cascade::

Emergency (3):
- Thermal Critical from any step → EmergencyAdvanceToMax
- Battery < emergency_pct → EmergencyAdvanceToMax
- Battery AT emergency_pct does NOT emergency (boundary, strict <)

Step 0 → 1 advances (4):
- spec miss high
- spec miss at threshold (boundary)
- inference queue high
- VRAM high + VRAM at threshold

Step 1 retreats (4):
- spec miss low
- spec miss in hysteresis gap holds
- inference queue low
- VRAM low

Step 1 → 2 advances (2):
- system mem high
- thermal Hot (and Warm/Cool retreats)

Step 2/3/4/5 transitions (4):
- battery low → step 2 advances (NOT emergency)
- step 2 mem-clear retreats
- steps 3/4 battery-recovered retreats
- step 5 ONLY Cool thermal retreats (strictest)

UserActive informational (1):
- UserActive holds at every step

apply_action (4):
- Hold keeps step
- Advance bumps + caps at MAX
- Retreat drops + saturates at MIN
- EmergencyAdvanceToMax jumps from any step

Determinism + serde (3):
- evaluate is deterministic
- CascadeAction tagged-union round-trips
- CascadeThresholds defaults match spec table (drift catcher)

Emergency priority over step evaluation (1)

Plus 2 ts-rs export bindings (CascadeAction, CascadeThresholds).

Stack:
- #1345 PR-1 governor-types (MERGED)
- #1350 PR-2 TOML loader (MERGED)
- #1352 PR-3a policy_selector (MERGED)
- #1354 PR-3b LocalSubstrateGovernor (MERGED)
- This PR (PR-3c1): cascade evaluator pure function
- Future PR-3c2: wire into LocalSubstrateGovernor::on_pressure_signal
  + apply_action_to_policy (rewrites tier_sizes / cadence /
  concurrency / speculation per cascade_step) + time-in-step gate
  (sustained-30s-before-advance rule)
- Future PR-3d: file watcher (notify crate)
- Future PR-4: PressureBroker → governor wiring

VDD evidence N/A — pure function. Evidence with PR-3c2 when actual
policy mutations land + with PR-4 when real pressure flows.
…Action + CascadeThresholds

Follow-up to the prior PR-3c1 commit that landed cascade.rs but
missed the auto-generated TS bindings + barrel update (worktree race
during the multi-tab collision moment — bindings generated by the
cargo test run but were still untracked when the commit was staged).

Adds CascadeAction.ts + CascadeThresholds.ts + index.ts entry. No
Rust changes.
@joelteply joelteply merged commit fd16e61 into canary May 17, 2026
4 checks passed
@joelteply joelteply deleted the feat/substrate-governor-pr3c1-cascade-evaluator branch May 17, 2026 00:04
joelteply added a commit that referenced this pull request May 17, 2026
Stacks on canary post-#1360 merge.

PR-3c2 wired cascade evaluator into on_pressure_signal to update
cascade_step. This PR-3c3 ships apply_cascade_step_to_policy — the
pure function that ACTUALLY transforms tier_sizes/cadence/concurrency/
speculation/federation/consolidation per the cascade step.

Per spec §'Adjustment Cascade' table:

- Step 0: unchanged (normal operation)
- Step 1: speculation_aggressiveness drops one notch toward Off
  (Aggressive → Balanced → Conservative → Off → Off)
- Step 2: cumulative + personas_concurrent -= 1 (floor 1) + defer
  non-realtime (cadence_multipliers.delayed/.background = max(current, 2.0))
- Step 3: cumulative + tier_sizes.l1_lora_layers + l1_kv_tokens
  shrunk to 75% (floor 1)
- Step 4: cumulative + federation_pull_cadence.pull_cadence_seconds
  = MAX_FEDERATION_PULL_CADENCE_SECONDS (3600s = once-per-hour)
- Step 5: cumulative + consolidation_schedule = Manual (operator
  must explicitly trigger; substrate stops on its own under max pressure)

Transformations are CUMULATIVE — step N includes all transformations
from steps 1..N. Caller passes BASE policy (cascade_step=0) and step;
function returns a NEW policy with cascade_step + transformations
applied. Caller is responsible for bumping policy_version + updating
committed_at_ms at publish time.

Pure function — no I/O, no state, no globals. Deterministic.

Anti-oscillation note (caller responsibility, documented in fn
docstring): the spec's 'restore-speculation-one-step-later' rule lives
in the WIRING layer (LocalSubstrateGovernor follow-up), not this pure
transformation. When retreating N → N-1, caller applies step N-1 for
everything EXCEPT speculation, which uses step N for one more cycle.
This separation keeps apply_cascade_step_to_policy a clean
deterministic mapping.

Also documented (test pins this): apply_cascade_step_to_policy is NOT
reversible from a transformed policy. apply(transformed, 0) does NOT
restore base — the caller must hold the original base separately and
re-apply step 0 from it. LocalSubstrateGovernor will need to evolve
to store base + active separately (PR-3c4).

Constants:
- MAX_FEDERATION_PULL_CADENCE_SECONDS = 3600 (once-per-hour ceiling)
  Pinned by test to catch silent tuning.

Tests: 46 passing on cargo test --lib --features metal,accelerate
governor::cascade:: (30 from PR-3c1 + 16 new)

NEW (16) for apply_cascade_step_to_policy:
- step 0 == base except cascade_step (identity)
- step 1 drops Aggressive → Balanced
- step 1 covers full speculation ladder (4 variants)
- step 2 personas-1 + cumulative speculation drop
- step 2 personas floor at 1 (defensive)
- step 2 stretches non-realtime cadence (delayed + background → 2.0)
- step 2 doesn't shrink already-stretched cadence (max-not-set semantics)
- step 3 shrinks l1 by 25% (8→6, 16384→12288)
- step 3 l1 floors at 1 (1*0.75=0.75→0→max(0,1)=1)
- step 4 federation_pull_cadence_seconds = MAX (60→3600)
- step 5 consolidation = Manual
- step 5 cumulative — all prior transformations applied
- step > MAX clamps to MAX (defensive against caller bugs)
- determinism
- not reversible from transformed (documented limitation, test pinned)
- MAX_FEDERATION_PULL_CADENCE_SECONDS const pinned

Stack:
- #1345 PR-1 governor-types (MERGED)
- #1350 PR-2 TOML loader (MERGED)
- #1352 PR-3a policy_selector (MERGED)
- #1354 PR-3b LocalSubstrateGovernor (MERGED)
- #1356 PR-3c1 cascade evaluator (MERGED)
- #1360 PR-3c2 cascade wiring + time-in-step gate (MERGED)
- This PR (PR-3c3): apply_cascade_step_to_policy field rewrites
- Future PR-3c4: wire apply_cascade_step_to_policy into
  LocalSubstrateGovernor + restore-speculation-one-step-later
  semantics + base-vs-active policy split
- Future PR-3d: file watcher (notify crate)
- Future PR-4: PressureBroker → governor wiring

VDD evidence N/A — pure transformation. Evidence with PR-3c4 wiring
+ PR-4 + downstream consumers reading the throttled policy.

Coordination: explicit claim posted to airc 00:25Z; codex on
orthogonal VDD work per their 00:25:13Z broadcast. No collision.

Co-authored-by: Test <test@test.com>
joelteply pushed a commit that referenced this pull request May 17, 2026
…ase/active split + restore-speculation-one-step-later

Stacks on #1364 (PR-3c3 apply_cascade_step_to_policy, MERGED).

PR-3c3 shipped the pure function. PR-3c4 wires it into
LocalSubstrateGovernor with the base-vs-active policy split + the
spec's restore-speculation-one-step-later anti-oscillation rule.

What changed in local.rs:

- LocalSubstrateGovernor.base_policy: Mutex<GovernorPolicy> field
  added. Holds the canonical un-throttled policy (cascade_step always
  0). Cascade transitions re-derive active from base via
  apply_cascade_step_to_policy, never from the already-throttled
  current. This addresses PR-3c3's not-reversible-from-transformed
  documented limitation.

- SnapshotState.pending_speculation_retreat: bool added. Tracks
  whether the cascade just retreated; if true, the NEXT Hold or
  Retreat restores speculation to the lower-step value. The first
  retreat keeps speculation at the higher-step (pre-retreat) value
  for one more cycle.

- new() initializes base_policy from the supplied initial_policy
  (cascade_step normalized to 0 on the base; active keeps the supplied
  cascade_step).

- try_hardware_detected() refreshes base_policy + resets cascade
  (step 0, last_step_change_ms now, pending_speculation_retreat
  cleared). New hardware = fresh start; existing pressure state
  discarded.

- on_pressure_signal() rewired:
  * Same time-in-step gate as PR-3c2 (Advance from step > 0 within
    MIN_TIME_IN_STEP_MS Hold; emergency bypasses; retreat never gated)
  * On step change: clone base_policy + call apply_cascade_step_to_policy
    + bump policy_version + update committed_at_ms
  * On retreat: also apply prev_step's speculation to next_policy
    (one-step-later semantics) + set pending_speculation_retreat
  * On Advance after pending-retreat: clear marker (new pressure
    re-throttles speculation immediately)
  * On Hold with pending marker: deliver the restoration (publish
    new policy with current_step's speculation; clear marker)

Restore-speculation-one-step-later rationale (from spec):

  Speculation thrash is the most user-visible cascade flapping. By
  keeping speculation throttled for ONE EXTRA cycle after the cascade
  retreats, we dampen the most observable form of oscillation while
  letting the rest of the policy (tier sizes, cadence, concurrency)
  restore immediately. The cost is one cycle of slightly-throttled
  speculation; the benefit is no observable flicker between
  Aggressive and Balanced (or whatever pair the cascade is bouncing
  between).

Failure-mode discipline:

- Base policy is the ONLY source of truth for transformations.
  Active is always derived; never mutated in place.
- Restore-one-step-later is typed (bool marker, not a magic time
  comparison or a sentinel value).
- Hardware change wipes pending retreat marker — new hardware = clean
  slate; old cascade state doesn't bleed into new policy.

Tests: 29 passing on cargo test --lib --features metal,accelerate
governor::local:: (22 prior + 7 new for PR-3c4)

NEW (7):
- advance_derives_active_from_base_with_step_transformations
- emergency_advance_applies_full_throttle_transformations (full
  step-5 cumulative: tier_sizes shrunk, federation maxed,
  consolidation Manual, speculation dropped, personas-1)
- retreat_holds_speculation_for_one_more_cycle (anti-oscillation rule
  pinned: Advance 0→1 drops Aggr→Balanced; Retreat 1→0 KEEPS Balanced;
  next Hold RESTORES Aggressive)
- advance_during_pending_retreat_clears_marker
- hardware_detected_refreshes_base_and_resets_cascade
- advance_then_retreat_returns_to_base_values_modulo_speculation_dampening
  (proves derive-from-base prevents compounding transformations —
  was PR-3c3's not-reversible warning)
- (helpers: policy_with_l1, policy_with_l1_nvidia)

Stack:
- #1345 / #1350 / #1352 / #1354 / #1356 / #1360 / #1364 — Lane H PRs MERGED
- This PR (PR-3c4): wire apply_cascade_step_to_policy + base/active
  split + restore-speculation-one-step-later
- Future PR-3d: file watcher (notify crate) — hot-reload policy file
  changes via set_candidates
- Future PR-4: PressureBroker → governor wiring (subscribe to typed
  pressure events from broker)

VDD evidence N/A — wiring + state machine. Evidence with PR-4 +
harness measurements when real pressure flows + downstream consumers
read throttled policy fields.

Coordination: explicit claim posted 00:40Z; codex on demand-aligned-
recall PR-1 per their 00:40:22Z broadcast. claude-tab-1 on whatever-
next. No collision.
joelteply pushed a commit that referenced this pull request May 17, 2026
… bridge

Pure-function bridge between PressureBroker's PressureAlert surface
(disk/memory pool eviction events) and the governor's typed
PressureSignal cascade input. Per GENOME-FOUNDRY-SENTINEL.md Part 11
line 1121: "PressureBroker informs the SubstrateGovernor. Pressure
signals from the broker drive the governor's adjustment cascade."

Scope:
- `alert_to_signal(&PressureAlert) -> Option<PressureSignal>` — pure
  mapping. High/Critical tier → SystemMemHigh{used_pct}; Normal/
  Warning/unknown → None.
- `governor_alert_sink(Arc<dyn SubstrateGovernor>) -> AlertSink` —
  factory that wraps a governor as an AlertSink the broker can register
  via `PressureBroker::add_alert_sink`. Sink derives the signal and
  forwards via `governor.on_pressure_signal` when Some; drops when None.

NOT in this PR (deferred to PR-5):
- Wiring the sink into PressureBrokerModule's boot path. The bridge is
  the data-side primitive; the wiring is a separate concern.
- Pool-name-aware mapping (vram → VRAMHigh, etc.). Today's broker pools
  are all memory-adjacent (Docker disk, HF cache, future VRAM via
  GpuMemoryManager); SystemMemHigh is the conservative single-mapping
  the cascade reacts to identically. Refinement when pool tier_name
  conventions stabilize.

Discipline:
- No silent default-on-error. Mapping is total — every alert maps to
  either Some(signal) or None explicitly.
- Pressure clamped to [0.0, 1.0] before percent conversion so transient
  over-budget snapshots map to 100% and negative artifacts map to 0%
  rather than wrapping via `as u8`.
- Sink forwards via `Arc<dyn SubstrateGovernor>` (object-safe trait) so
  the bridge does not depend on LocalSubstrateGovernor concretely.

Tests (14, all passing):
- normal/warning/unknown tiers -> None (4 tests)
- high/critical tiers -> SystemMemHigh with rounded used_pct (3 tests)
- pressure clamping above 1.0 + below 0.0 + rounding (3 tests)
- sink forwarding high/critical + non-forwarding normal/warning (4 tests)
- sink survives construction-scope drop + multi-call ordering (2 tests)

Lane H 8-PR stack progress: PR-1 (#1330/1331) -> PR-2 (#1345) -> PR-3a
(#1352) -> PR-3b (#1354) -> PR-3c1 (#1356) -> PR-3c2 (#1360) -> PR-3c3
(#1364) -> PR-3c4 (#1365) -> **PR-4 (this PR)**. PR-3d governor file
watcher in flight from codex on parallel branch (no overlap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply pushed a commit that referenced this pull request May 17, 2026
… bridge

Pure-function bridge between PressureBroker's PressureAlert surface
(disk/memory pool eviction events) and the governor's typed
PressureSignal cascade input. Per GENOME-FOUNDRY-SENTINEL.md Part 11
line 1121: "PressureBroker informs the SubstrateGovernor. Pressure
signals from the broker drive the governor's adjustment cascade."

Scope:
- `alert_to_signal(&PressureAlert) -> Option<PressureSignal>` — pure
  mapping. High/Critical tier → SystemMemHigh{used_pct}; Normal/
  Warning/unknown → None.
- `governor_alert_sink(Arc<dyn SubstrateGovernor>) -> AlertSink` —
  factory that wraps a governor as an AlertSink the broker can register
  via `PressureBroker::add_alert_sink`. Sink derives the signal and
  forwards via `governor.on_pressure_signal` when Some; drops when None.

NOT in this PR (deferred to PR-5):
- Wiring the sink into PressureBrokerModule's boot path. The bridge is
  the data-side primitive; the wiring is a separate concern.
- Pool-name-aware mapping (vram → VRAMHigh, etc.). Today's broker pools
  are all memory-adjacent (Docker disk, HF cache, future VRAM via
  GpuMemoryManager); SystemMemHigh is the conservative single-mapping
  the cascade reacts to identically. Refinement when pool tier_name
  conventions stabilize.

Discipline:
- No silent default-on-error. Mapping is total — every alert maps to
  either Some(signal) or None explicitly.
- Pressure clamped to [0.0, 1.0] before percent conversion so transient
  over-budget snapshots map to 100% and negative artifacts map to 0%
  rather than wrapping via `as u8`.
- Sink forwards via `Arc<dyn SubstrateGovernor>` (object-safe trait) so
  the bridge does not depend on LocalSubstrateGovernor concretely.

Tests (14, all passing):
- normal/warning/unknown tiers -> None (4 tests)
- high/critical tiers -> SystemMemHigh with rounded used_pct (3 tests)
- pressure clamping above 1.0 + below 0.0 + rounding (3 tests)
- sink forwarding high/critical + non-forwarding normal/warning (4 tests)
- sink survives construction-scope drop + multi-call ordering (2 tests)

Lane H 8-PR stack progress: PR-1 (#1330/1331) -> PR-2 (#1345) -> PR-3a
(#1352) -> PR-3b (#1354) -> PR-3c1 (#1356) -> PR-3c2 (#1360) -> PR-3c3
(#1364) -> PR-3c4 (#1365) -> **PR-4 (this PR)**. PR-3d governor file
watcher in flight from codex on parallel branch (no overlap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply added a commit that referenced this pull request May 17, 2026
)

Pure-function bridge between PressureBroker's PressureAlert surface
(disk/memory pool eviction events) and the governor's typed
PressureSignal cascade input. Per GENOME-FOUNDRY-SENTINEL.md Part 11
line 1121: "PressureBroker informs the SubstrateGovernor. Pressure
signals from the broker drive the governor's adjustment cascade."

Scope:
- `alert_to_signal(&PressureAlert) -> Option<PressureSignal>` — pure
  mapping. High/Critical tier → SystemMemHigh{used_pct}; Normal/
  Warning/unknown → None.
- `governor_alert_sink(Arc<dyn SubstrateGovernor>) -> AlertSink` —
  factory that wraps a governor as an AlertSink the broker can register
  via `PressureBroker::add_alert_sink`. Sink derives the signal and
  forwards via `governor.on_pressure_signal` when Some; drops when None.

NOT in this PR (deferred to PR-5):
- Wiring the sink into PressureBrokerModule's boot path. The bridge is
  the data-side primitive; the wiring is a separate concern.
- Pool-name-aware mapping (vram → VRAMHigh, etc.). Today's broker pools
  are all memory-adjacent (Docker disk, HF cache, future VRAM via
  GpuMemoryManager); SystemMemHigh is the conservative single-mapping
  the cascade reacts to identically. Refinement when pool tier_name
  conventions stabilize.

Discipline:
- No silent default-on-error. Mapping is total — every alert maps to
  either Some(signal) or None explicitly.
- Pressure clamped to [0.0, 1.0] before percent conversion so transient
  over-budget snapshots map to 100% and negative artifacts map to 0%
  rather than wrapping via `as u8`.
- Sink forwards via `Arc<dyn SubstrateGovernor>` (object-safe trait) so
  the bridge does not depend on LocalSubstrateGovernor concretely.

Tests (14, all passing):
- normal/warning/unknown tiers -> None (4 tests)
- high/critical tiers -> SystemMemHigh with rounded used_pct (3 tests)
- pressure clamping above 1.0 + below 0.0 + rounding (3 tests)
- sink forwarding high/critical + non-forwarding normal/warning (4 tests)
- sink survives construction-scope drop + multi-call ordering (2 tests)

Lane H 8-PR stack progress: PR-1 (#1330/1331) -> PR-2 (#1345) -> PR-3a
(#1352) -> PR-3b (#1354) -> PR-3c1 (#1356) -> PR-3c2 (#1360) -> PR-3c3
(#1364) -> PR-3c4 (#1365) -> **PR-4 (this PR)**. PR-3d governor file
watcher in flight from codex on parallel branch (no overlap).

Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant