SAFETY-119 round 3: rolling 15-minute budget for the hidden integrator bound
At most 8 hidden bound cycles in any 900 s across all hand-backs, replacing the per-run budget. Chosen from a sweep (N 3 to 10, 1 to 15 s meters, both cases); DECISIONS has the table. Tests: never case at 5, 10, 15 s, quarter edge at 2, 10, 15 s, the rolling budget at its boundaries. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
85542e443b
commit
103db2fcda
@@ -299,10 +299,10 @@ REFUSED_PROGRESS_W = _ctl.PROBE_ANSWER_W / 2
|
||||
# was told over the same span as above (the plant's measured settle time), so
|
||||
# a healthy battery still settling on a new command is not short.
|
||||
SHORT_CYCLES, SHORT_SPAN_S = _ctl.SETTLE_CYCLES, REFUSED_SPAN_S
|
||||
# SAFETY-119: a run of hand-backs (the wire away less than a settle between
|
||||
# them) may hide the loop's bound for at most this many cycles and
|
||||
# SHORT_SPAN_S, in all.
|
||||
RUN_BOUND_CYCLES = SHORT_CYCLES - 1
|
||||
# SAFETY-119: across all hand-backs, the loop's bound is hidden on at most
|
||||
# this many cycles in any ROLLING_S seconds (a budget measured in
|
||||
# DECISIONS/SAFETY-119-bound-after-hand-back.md).
|
||||
ROLLING_BOUND_CYCLES, ROLLING_S = 8, 900.0
|
||||
|
||||
|
||||
def _refused_interval(sign: int) -> tuple:
|
||||
@@ -1276,13 +1276,12 @@ class Controller:
|
||||
self._loop_decision = None
|
||||
self._asked_hist: dict = {}
|
||||
self._short_run = 0
|
||||
# SAFETY-119: did another owner set the wire last cycle; the open run
|
||||
# (monotonic s and count of the loop's cycles in a row, bound cycles
|
||||
# hidden, monotonic s of the first); the wire's cycles away in a row
|
||||
# (first s, count).
|
||||
# SAFETY-119: did another owner set the wire last cycle; the window
|
||||
# after a hand-back (monotonic s, the loop's cycles in a row); the
|
||||
# monotonic s of the bound cycles hidden in the last ROLLING_S.
|
||||
self._wire_lost = False
|
||||
self._handback = None
|
||||
self._away = None
|
||||
self._hidden = ()
|
||||
self.stopping = False
|
||||
# SAFETY-61. time.monotonic() at the end of the last run_control() pass.
|
||||
self.loop_pass_mono = None
|
||||
@@ -5825,7 +5824,6 @@ class Controller:
|
||||
self._short_run = 0
|
||||
self._asked_hist = {}
|
||||
self._wire_lost, self._handback = False, None
|
||||
self._away = None
|
||||
return
|
||||
unmet = self.allocation.unmet_w if self.allocation is not None else 0
|
||||
cut = (resolution.bound_by or "").split("(")[0]
|
||||
@@ -5864,41 +5862,35 @@ class Controller:
|
||||
|
||||
def _settling(self, owns, now) -> bool:
|
||||
"""SAFETY-119: does the loop's bound on this cycle count? Not within
|
||||
a run of hand-backs from another owner: its integrator is re-seeded
|
||||
from that owner's command and kicked by a meter that does not show it
|
||||
yet. A run opens at a hand-back and ends when the loop held the wire,
|
||||
or the wire was away, a whole settle in a row (SHORT_CYCLES cycles
|
||||
and SHORT_SPAN_S). Each hand-back restarts the settle, but the run
|
||||
hides the bound for RUN_BOUND_CYCLES cycles and SHORT_SPAN_S in all,
|
||||
so a bound that stays is counted after that, however often the wire
|
||||
changes hands. DECISIONS/SAFETY-119-bound-after-hand-back.md."""
|
||||
a settle after another owner handed the wire back: its integrator is
|
||||
re-seeded from that owner's command and kicked by a meter that does
|
||||
not show it yet. The settle is SHORT_CYCLES cycles and SHORT_SPAN_S
|
||||
after the hand-back; losing the wire restarts it. Across all
|
||||
hand-backs the bound is hidden on at most ROLLING_BOUND_CYCLES cycles
|
||||
in any ROLLING_S, so a bound that stays is counted however often the
|
||||
wire changes hands. DECISIONS/SAFETY-119-bound-after-hand-back.md."""
|
||||
def aged(t0, span):
|
||||
return now is None or t0 is None or now - t0 >= span
|
||||
|
||||
if not owns:
|
||||
self._wire_lost = True
|
||||
t0, k = self._away or (now, 0)
|
||||
self._away = (t0, k + 1)
|
||||
if k + 1 >= SHORT_CYCLES and aged(t0, SHORT_SPAN_S):
|
||||
self._handback = None
|
||||
self._wire_lost, self._handback = True, None
|
||||
return False
|
||||
self._away = None
|
||||
if self._wire_lost:
|
||||
self._wire_lost = False
|
||||
self._handback = (now, 0) + (self._handback or (0,) * 4)[2:]
|
||||
self._wire_lost, self._handback = False, (now, 0)
|
||||
if self._handback is None:
|
||||
return False
|
||||
tc, n, hid, tb = self._handback
|
||||
tc, n = self._handback
|
||||
n += 1
|
||||
self._handback = (tc, n, hid, tb)
|
||||
self._handback = (tc, n)
|
||||
if aged(tc, SHORT_SPAN_S) and n >= SHORT_CYCLES:
|
||||
self._handback = None
|
||||
return False
|
||||
if self.loop_clamped[0] and not self.loop_clamped[2]:
|
||||
if hid >= RUN_BOUND_CYCLES and aged(tb, SHORT_SPAN_S):
|
||||
kept = tuple(t for t in self._hidden if not aged(t, ROLLING_S))
|
||||
if len(kept) >= ROLLING_BOUND_CYCLES:
|
||||
self._hidden = kept
|
||||
return False
|
||||
tb = tb if hid else now
|
||||
self._handback = (tc, n, hid + 1, tb)
|
||||
self._hidden = kept + (now,)
|
||||
return True
|
||||
|
||||
async def check_output_clamp(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user