SAFETY-38 round 4: the enumeration covered one sink, and it was not the one this codebase uses
checks / unit (pull_request) Successful in 5m12s

The scanner's two censuses between them scored ONE syntactic sink and called it
the family. Every one of census B's 16 members was a sentence that is APPENDED,
ASSIGNED to a `reason`-shaped name, or passed as a LOG ARGUMENT. Not one was
RETURNED - and census A's return bucket only ever held a callee formatting its
own PARAMETERS, so a method formatting its own ATTRIBUTES and handing the string
back was invisible to both. That is the whole describe() / explain() /
reason-builder family, which is where this repository actually keeps its
operator sentences. Worse than the gap: it was in NONE of the five blind spots
the tool printed about itself.

THE SCANNER, WIDENED - and it rediscovered both members without being told:

  * CENSUS C, the return sink, resolved through the returning method's CALL
    SITES so a returned KEY (store.unit_key, mqtt.unit_state_key - MAINT-05's
    card) is not mistaken for a returned SENTENCE. 53 returned pairs, 39
    reaching an operator, 16 of those sharing a format spec.
  * A sentence handed to a constructor as `reason=` or placed under a "text"
    dict key is a MESSAGE. That one line of the sink walker was hiding SIX more
    caller-built members, census B 16 -> 22.
  * Every census B and C row now prints whether the two interpolations share a
    FORMAT SPEC - the difference between a transposition that is silent and one
    that merely looks broken.
  * The blind-spot list is now TWO lists: what it scans (printed from the
    constants, so they cannot drift) and WHAT IT DOES NOT SCAN AT ALL, largest
    hole first - `%s`-style lazy logging, MEASURED at 31 calls across 8 modules
    and each one listed, not asserted.

CLOSED, both zero-byte survivors of a green 21-file suite:

  * clamp.py:253 ClampResult.describe(), requested_w <-> applied_w. No
    test_*.py mentioned describe() at all. It is the SS15 action-log row
    (main.py:1366) AND the ClampWatchdog notification reason (main.py:2452) -
    the alarm SAFETY-40 and SAFETY-41 are about. Under the swap the guard
    reports a setpoint clamped UPWARD, to +9999 W by a bound whose rating the
    same sentence gives as 2000 W. Pinned on the ORDERED PAIR on the contiguous
    literal at BOTH consumers, operands read off ctl.target and
    hass.writes[-1] - what the loop asked and what the device was sent - not
    off the ClampResult the swap lives in. 4 checks red.
  * peakguard.py:493 PeakDecision.explain(), projected_w <-> ceiling_w. "peak
    guard idle" was in no test_*.py. The row then reads "projects 5013 W under
    a 2300 W ceiling" - 2713 W OVER - on a row whose own words say idle and
    whose own flag says ok. Pinned on the decision and on the shipped status
    row. 3 checks red.
  * test_arbiter.py's scope-violation check, whose CLAIM was wider than its
    ASSERTION. Measured this round rather than left at source level: 0 bytes,
    21/21 green before, red now. 2 checks.

DISCLOSED, NOT CLOSED - 11 measured zero-byte survivors, each with its file
fingerprint and its 21/21 verdict, in the decision doc SS7e-SS7g. Three in
allocator.py's UnitPlan.explain(), three across arbiter.py's two explain()s,
two in tariff.py, two in arbitrage.py's trade reasons, one in main.py's
SoC-floor notification. An enumerated list with honest gaps beats a fifth round
finding a fifth instance.

CORRECTIONS THIS ROUND OWES:

  * The idle sentence does NOT reach main.py:3299/3302 - those are the
    floor-limited branch, where explain() returns self.reason before the idle
    literal is built. It reaches main.py:3313 and, on the release edge, 1951.
    1951 is disclosed unpinned.
  * SS4's "Every one of the ten is red" sat under a twelve-row table: it is the
    ten site_standing entries.
  * SS6b said "weaker evidence than A1-A3"; A4 was measured, so SS7d's
    "A1-A4 and B1" is the right list and SS6b now says it.
  * AND THE PROBE THAT GRADES THESE MUTATIONS WAS THE DEFECT, ONCE. Its first
    cut numbered the operands BY ORDER OF APPEARANCE, so after a transposition
    the other leaf took the same number and all thirteen mutant renders came
    back byte-identical - which reads exactly like "the edit is inert". Keying
    the value to the NAME fixed it. Habit 4, in the measuring instrument:
    MUTATION.md habit 16 is the new entry, and the count there is corrected to
    eleven rather than inflated.

14 mutations measured: fresh full-repo copy each, mounted :ro, bytecode purged
and the purge COUNT printed, the edit re-read from disk, length and sha256
printed on both sides, and every one required to change the EMITTED SENTENCE
and not merely the bytes. 12 survived the pre-round suite, 1 was already
caught, 3 are caught now. Full 21-file suite green on the final tree.

NOT ONE BYTE OF app/ MOVES. This card changes coverage only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
Claude Opus 5
2026-09-05 22:38:40 +02:00
parent 357cb4f07f
commit e2efc765af
7 changed files with 901 additions and 78 deletions
+80
View File
@@ -5774,6 +5774,86 @@ check("...and so is the last one, which is not a defeat at all but the EDGE OF "
not MUT[12][1] and EV["min_soc"] <= BMS_FLOOR + 0.05)
# ==============================================================================
print()
print("13. SAFETY-38 round 4: the ORDERED PAIR inside PeakDecision.explain()")
# ⚠️ THE STRING "peak guard idle" APPEARED IN NO test_*.py UNTIL THIS PART.
# `peakguard.py:493` formats its OWN ATTRIBUTES and RETURNS the sentence:
#
# f"peak guard idle - quarter projects {self.projected_w:.0f} W under a "
# f"{self.ceiling_w:.0f} W ceiling ({self.ceiling_from})"
#
# Transpose the two and this fixture's row reads "projects 5013 W under a
# 2300 W ceiling" - a quarter projected 2713 W OVER the ceiling, printed on a
# row whose own words say the guard is IDLE and whose own flag says ok.
# MEASURED on this tree: zero-byte edit (50 032 bytes
# before, 50 032 after, sha256 d95df61a51716c26 -> 2d2ee3d0bcd51251), the
# emitted sentence changes, and the full 21-file suite stayed GREEN.
#
# The sentence was invisible to SAFETY-38's scanner for two rounds because both
# censuses scored one syntactic sink - APPEND, ASSIGN, LOG ARG - and this one is
# RETURNED. `tools/symmetric_message_scan.py` census C now scans the return sink
# and resolves it through the method's call sites; it rediscovered this pair and
# clamp.py's on its own, which is the only reason to trust the rest of its list.
#
# ⚠️ THE OPERANDS COME OFF THE PROJECTION AND OFF `quarter_ceiling_w()`, NOT OFF
# THE DECISION. `d.projected_w` and `d.ceiling_w` are the very fields the swap
# exchanges, so grading the sentence against them would compare the swap against
# itself and pass under it.
_S38_PROJ = PeakTracker.project(averager_at(CAP_W - 1200.0, 450.0),
B + timedelta(seconds=450))
_S38_CEIL_W, _S38_CEIL_FROM = quarter_ceiling_w(CAP_W, obs_hi)
_S38_IDLE = decide(_S38_PROJ, batt_w=0.0, cap_w=CAP_W, reading=obs_hi,
achievable_w=LIVE_MAX_W, max_hold_s=MAX_HOLD_S,
soc=SOC_OK, soc_floor=SOC_FLOOR)
_S38_LITERAL = (f"quarter projects {_S38_PROJ.projected_w:.0f} W under a "
f"{_S38_CEIL_W:.0f} W ceiling")
_S38_SWAPPED = (f"quarter projects {_S38_CEIL_W:.0f} W under a "
f"{_S38_PROJ.projected_w:.0f} W ceiling")
print(f" projection {_S38_PROJ.projected_w:.0f} W against a "
f"{_S38_CEIL_W:.0f} W ceiling ({_S38_CEIL_FROM})")
print(f" {_S38_IDLE.explain()!r}")
check("PRECONDITION: the guard is IDLE with a projection to print, the ceiling "
"came from the MONTH PEAK rather than the cap, and the two operands are "
"DIFFERENT NUMBERS that RENDER differently. Equal operands would make the "
"transposed sentence identical and every check below decoration",
not _S38_IDLE.engaged and _S38_IDLE.projected_w is not None
and _S38_CEIL_FROM == CEILING_MONTH_PEAK
and _S38_PROJ.projected_w != _S38_CEIL_W
and f"{_S38_PROJ.projected_w:.0f}" != f"{_S38_CEIL_W:.0f}"
and _S38_PROJ.projected_w < _S38_CEIL_W)
check("⚠️ THE ORDERED PAIR ON THE CONTIGUOUS LITERAL. The PROJECTION is named "
"first and the CEILING second; a substring test for either number passes "
"under the transposition because both numbers are still in the sentence, "
"so only the ordered pair can tell the two slots apart (MUTATION.md "
"habit 14)", _S38_LITERAL in _S38_IDLE.explain())
check("...and the TRANSPOSED sentence is NOT what it says, so the check above "
"discriminates rather than decorates",
_S38_SWAPPED not in _S38_IDLE.explain())
check("...and the row still names WHERE the ceiling came from, which is the "
"difference between a cap an operator set and a peak the month already "
"banked", f"ceiling ({CEILING_MONTH_PEAK})" in _S38_IDLE.explain())
# THE SHIPPED CONSUMER. `explain()` is not read by anybody until main.py prints
# it, and round 3's lesson is that the pin belongs on the CALL SITE: the status
# row at main.py:3313 is what a browser shows, and the action-log line at
# main.py:1951 is what the release edge writes.
_c38, _h38 = built(TMP / "s38pin.db")
_c38.peak_guard = _S38_IDLE
_rows38 = [r for r in _c38.checks() if "peak guard" in r["text"]]
print(" status row: " + (_rows38[0]["text"] if _rows38 else "(none)"))
check("⚠️ ...AND THE SAME ORDERED PAIR ON THE STATUS PAGE (main.py:3313), read "
"off the shipped `checks()` rather than off `explain()` a second time. "
"This is the surface the owner actually looks at",
len(_rows38) == 1 and _S38_LITERAL in _rows38[0]["text"]
and _S38_SWAPPED not in _rows38[0]["text"])
check("...and the row is green and un-warned, which is what makes the "
"transposed version dangerous rather than merely untidy: an OK row "
"carrying a projection 2713 W OVER its own ceiling is the one an "
"operator scrolls past", _rows38[0]["ok"] and not _rows38[0]["warn"]
and _S38_CEIL_W - _S38_PROJ.projected_w == 2713.0)
# ==============================================================================
print()
shutil.rmtree(TMP, ignore_errors=True)