SAFETY-119 round 3b: no-clock budget, fixed-seed tests, accepted limits
The rolling budget now holds without a monotonic clock (it never refills). Quarter-edge tests use seeds 1-3 at 2, 5, 10 and 15 s (no hand-picked seeds). Re-pin comments point at part 6. DECISIONS lists the limits the owner accepted with N=8 on 2026-10-20. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
86b94bedb5
commit
e27e62ff72
@@ -58,6 +58,22 @@ and 15 s the meter's lag puts the highest quarter 20 to 55 W over 5000 W,
|
||||
the same with and without the fix. Quarter-edge case, 15 s meter: the highest
|
||||
quarter is 2327 W to 2680 W, the same with and without the fix.
|
||||
|
||||
**Accepted limits (owner, 2026-10-20, with N=8).** The owner accepted N=8
|
||||
knowing that:
|
||||
- at a 2 s meter the quarter-edge false alarm still shows on about 1 of 30
|
||||
seeds (one notification in 2 h);
|
||||
- at a 15 s meter the case above warns in only 1 to 3 of the 7 quarters
|
||||
(some runs are silent for more than one quarter in a row);
|
||||
- at a 30 s meter and slower it gives no warning in that case (0 in 2 h,
|
||||
against 7 and 11 without the fix): a real bound is suppressed there;
|
||||
- the cap is exceeded at 10 s and slower in that case (and up to 2680 W of
|
||||
2500 W at 15 s on the quarter edge), the same with and without the fix.
|
||||
A budget that follows the cycle length (10 cycles up to 3.5 s, 8 to 11 s, 6
|
||||
to 16 s, 4 beyond) met both cases on every swept seed at 1 to 20 s (at
|
||||
30 s: no false alarm, 3 to 5 of 7 quarters) in the dev round-3 sweep; it was
|
||||
not adopted. Without a monotonic clock the budget
|
||||
never refills, so a bound that stays is counted after 8 hidden cycles.
|
||||
|
||||
**Kept.** The loop in charge at its rail warns on the same cycle as before.
|
||||
The start and a restart after a stop are not hand-backs. The quarter-edge
|
||||
kick of `test_safety119.py` part 1 stays hidden (0 warnings in 2 h at
|
||||
|
||||
@@ -301,7 +301,8 @@ REFUSED_PROGRESS_W = _ctl.PROBE_ANSWER_W / 2
|
||||
SHORT_CYCLES, SHORT_SPAN_S = _ctl.SETTLE_CYCLES, REFUSED_SPAN_S
|
||||
# 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).
|
||||
# DECISIONS/SAFETY-119-bound-after-hand-back.md). Without a monotonic clock
|
||||
# the budget never refills.
|
||||
ROLLING_BOUND_CYCLES, ROLLING_S = 8, 900.0
|
||||
|
||||
|
||||
@@ -5886,7 +5887,8 @@ class Controller:
|
||||
self._handback = None
|
||||
return False
|
||||
if self.loop_clamped[0] and not self.loop_clamped[2]:
|
||||
kept = tuple(t for t in self._hidden if not aged(t, ROLLING_S))
|
||||
kept = tuple(t for t in self._hidden if now is None or (
|
||||
t is not None and now - t < ROLLING_S))
|
||||
if len(kept) >= ROLLING_BOUND_CYCLES:
|
||||
self._hidden = kept
|
||||
return False
|
||||
|
||||
@@ -361,11 +361,9 @@ check("...and at 5 s the cap held",
|
||||
# The quarter-edge case (part 1) at the slower meters: still no warning.
|
||||
# The meter's own lag limits the cap there, the same with and without the fix.
|
||||
_qe = {}
|
||||
for _t, _seeds in ((10.0, ((1, 1), (2, 1), (3, 2))),
|
||||
(15.0, ((1, 1), (2, 1), (3, 2))),
|
||||
(2.0, ((8, 1), (9, 2)))): # a budget of 7 warns here
|
||||
for _t in (2.0, 5.0, 10.0, 15.0): # with part 1: 1, 2, 5, 10, 15 s
|
||||
rig.TICK = _t
|
||||
for _seed, _dl in _seeds:
|
||||
for _seed, _dl in ((1, 1), (2, 1), (3, 2)): # the brief's fixed set
|
||||
_qe[_t, _seed] = (run(house(_seed), delay=_dl),
|
||||
no_fix(lambda: run(house(_seed), delay=_dl)))
|
||||
rig.TICK = 1.0
|
||||
@@ -373,14 +371,15 @@ for _t, _seeds in ((10.0, ((1, 1), (2, 1), (3, 2))),
|
||||
f"{[len(notes(r[0][1])) for k, r in _qe.items() if k[0] == _t]} "
|
||||
f"(without the fix "
|
||||
f"{[len(notes(r[1][1])) for k, r in _qe.items() if k[0] == _t]})")
|
||||
check("⭐ the quarter-edge case at 10 s and 15 s meters, and at 2 s on two "
|
||||
"more seeds: no notification",
|
||||
check("⭐ the quarter-edge case at 2, 5, 10 and 15 s meters, seeds 1 to 3: "
|
||||
"no notification",
|
||||
all(notes(r[0][1]) == [] and r[0][1].clamp_wd.bound == 0
|
||||
for r in _qe.values()))
|
||||
check("...every command the same as without the fix; without it the false "
|
||||
"alarm is there (non-vacuous)",
|
||||
"alarm is there at each meter (non-vacuous)",
|
||||
all(commands(r[0][0]) == commands(r[1][0]) for r in _qe.values())
|
||||
and all(len(notes(r[1][1])) >= 1 for r in _qe.values()))
|
||||
and all(any(len(notes(_qe[_t, k][1][1])) >= 1 for k in (1, 2, 3))
|
||||
for _t in (2.0, 5.0, 10.0, 15.0)))
|
||||
check("the budget is the one the sweep chose (8). Changing it means "
|
||||
"re-running the sweep in DECISIONS/SAFETY-119-bound-after-hand-back.md",
|
||||
app_main.ROLLING_BOUND_CYCLES == 8 and app_main.ROLLING_S == 900.0)
|
||||
@@ -538,6 +537,18 @@ _c.loop_clamped = (True, "", False)
|
||||
check("an over-rating does not use up the budget",
|
||||
_c._settling(True, _t + 5.0) is True and len(_c._hidden) == 1)
|
||||
|
||||
# No monotonic clock: the budget cannot refill, so a bound that stays is
|
||||
# counted after the budget however often the wire changes hands.
|
||||
_c = _C()
|
||||
_nc = []
|
||||
for _ in range(40):
|
||||
for _ in range(3):
|
||||
_c._settling(False, None)
|
||||
_c.loop_clamped = (True, "", False)
|
||||
_nc.append(_c._settling(True, None))
|
||||
check("no monotonic clock, repeated hand-backs: the budget holds (8 hidden, "
|
||||
"then every bound counts)", _nc == [True] * M + [False] * (40 - M))
|
||||
|
||||
# An over-rating (a command over `max_w`) is a bug, never settled away.
|
||||
_ctl = _hb[1]
|
||||
_ctl.loop_clamped = (True, "x", True)
|
||||
|
||||
@@ -474,7 +474,7 @@ print("\n8. the N=1 and N=2 digests")
|
||||
# pin lifts (t=1800) and B no longer jumps 1390 W, and after the surplus step.
|
||||
# SAFETY-119 re-pinned all six: only the log. OUTPUT_CLAMP rows from the
|
||||
# loop's bound just after the guard hands back are gone; every command and
|
||||
# claim is the same (test_safety119.py part 5).
|
||||
# claim is the same (test_safety119.py part 6).
|
||||
PINNED = {
|
||||
"n1_charge":
|
||||
"19781f90b9751b10994dedeaafb38e42d48ad2551640792e601336b7f9b5bb4f",
|
||||
|
||||
@@ -521,7 +521,7 @@ print("\n9. N=1 is byte-identical to release/1.0 b8ddfd7")
|
||||
# SAFETY-119 re-pinned n1_crossing, n1_above and n1_charge_low: only the
|
||||
# log. The loop's bound just after a hand-back is not counted, so OUTPUT_CLAMP
|
||||
# rows are gone or count fewer; every command and claim is the same
|
||||
# (test_safety119.py part 5).
|
||||
# (test_safety119.py part 6).
|
||||
PINNED = { # tools/safety87_identity.py on b8ddfd7
|
||||
"n1_under":
|
||||
"d2395ef8d74fb23607ad864d5a4bf62c03044bbd9367e873675d7ad10e8c169f",
|
||||
|
||||
Reference in New Issue
Block a user