Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,25 @@ name: mutation
# the job, while the documented residue doesn't ratchet. Lower a ceiling when
# you kill residue; never raise one without the same class of justification.
#
# THIS JOB CAN GO RED WITHOUT A CODE CHANGE, and that is working as intended.
# `mutation` installs mutmut fresh each run under `>=3.0,<4`, so a release that
# mutates MORE can turn an unchanged commit red. It happened on 2026-09-14:
# mutmut 3.8.0 (released 09-12) fixed "methods of decorated classes are not
# mutated" and added ternary-condition mutation, and the same commit that was
# green on 09-07 came back +23 survivors. The bound stays wide on purpose —
# every one of those 23 was reachable code nobody was testing, and seven had
# never been executed at all.
#
# So when this goes red with no diff to explain it: reproduce both versions
# before touching a ceiling. `pip install mutmut==<last green>` and
# `mutmut run` gives the baseline; the delta against the new version tells you
# whether the net widened or the tests rotted. Raising a ceiling to match a
# wider net, without reading the new survivors, is how a gate gets muted.
#
# Not on every push (it copies + re-runs per mutant); on demand + weekly.
# It is also NOT a PR gate, so nothing turns a pull request red when this
# breaks — the weekly triage loop (.github/workflows/triage.yml) is what
# noticed the 09-14 failure, and issue #637 is where it says so.

on:
workflow_dispatch:
Expand Down Expand Up @@ -91,16 +109,44 @@ jobs:
# which is engineer-facing text rather than behaviour. Some
# cycle-reconstruction residue in _max_ratio_cycle is genuinely
# not yet killed and is worth another pass.
"budgets": 229,
"flash_safety": 15, # degradation branches + boundary-exact forms
#
# 229 -> 231 (2026-09-17), and the two are itemized because a
# ceiling that moves without an itemization is just a ratchet.
# mutmut 3.8.0 added "Mutate the condition of ternary
# expressions", so two conditions became mutable that never were:
# _cycle_analysis: `hi = (1.0/min(durs))*len(nodes) if durs
# else 1e6` — forcing the else gives the ratio search a
# larger-but-still-valid upper bound, so it converges to the
# same rate. Equivalent, not a hole.
# verify: `_beat_of(fig, scene) if scene else None` — the beat
# is diagnostic metadata on a Violation, the same
# engineer-facing class as the message strings above.
# The THIRD new ternary mutant was a real hole and is killed, not
# accepted: `(t.target == SELF) or True` collapsed every timeout
# hop into a self-loop, which a ring of equal scenes cannot see.
# test_budgets_cycle_math.py's asymmetric-cycle test kills it.
"budgets": 231,
# flash_safety.py — 15, degradation branches + boundary-exact
# forms. It went to 22 under mutmut 3.8.0 and is back at 15
# because all seven new mutants were KILLED, not accommodated:
# they were FlashReport.__str__, which no test had ever executed
# (see test_report_strings.py — the one apparent reader was an
# assert MESSAGE, which Python evaluates only on failure).
"flash_safety": 15,
# privacy.py — 1, MEASURED, as the previous comment here asked for.
# It was a provisional analytic bound of 3 written when mutmut was
# not installable in the authoring environment. Four scheduled runs
# (2026-07-13, -07-20, -07-27, -08-03) and a local run all report
# exactly 1: set_incognito's `bool(on)` truthiness coercion, which
# is behaviourally identical because the flag's only reader is
# `_paused or _incognito`. The `return None` -> bare `return`
# idioms the margin was reserved for do not survive.
# exactly 1.
#
# That mutant is `PrivacyGate.__init__`'s `self._incognito =
# False` -> `None`, NOT set_incognito's `bool(on)` coercion as
# this comment claimed until 2026-09-17 — mutmut show names it,
# and the reasoning was right about the wrong line. It is
# behaviourally identical for the reason given: the flag's only
# reader is `_paused or _incognito`, and None is falsy exactly as
# False is. The `return None` -> bare `return` idioms the margin
# was reserved for do not survive.
"privacy": 1,
}

Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/real-models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,18 @@ jobs:
# NO other job, which is precisely this job's charter. The rest
# (usearch, sqlite-vec, loro, crosshair-tool, wasmtime) are green in
# pytest.yml and only needed installing here too.
#
# "with the project's own bounds" was prose, not fact, for insightface
# until #673: it was the one entry installed UNPINNED, so when
# insightface 2.0 shipped (2026-09-08) this job silently started
# proving the face path against a version `face`'s own `<2` cap
# forbade. It passed — which is how we know 2.0 works — but a job
# that tests what nobody can install is testing the wrong thing, in
# either direction. Every entry on this line now carries its bound.
pip install -c /tmp/torch-cpu-constraints.txt -e ".[dev,privacy]" \
sentence-transformers open-clip-torch model2vec \
silero-vad faster-whisper \
insightface onnxruntime \
"insightface>=0.7,<3" "onnxruntime>=1.17,<2" \
"usearch>=2,<3" "sqlite-vec>=0.1,<1" "loro>=1.0,<2" \
"chromadb>=1,<2" "lancedb>=0.34,<1" "river>=0.23,<1" \
"supervision>=0.29,<1" "wasmtime>=46,<49" \
Expand Down
7 changes: 6 additions & 1 deletion host-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ dream-style = ["onnxruntime>=1.17,<2"]
# does nothing until the wearer turns face_recognition on, and never matches
# anyone who was not consensually enrolled — there is no stranger lookup and no
# face database beyond your own contacts. See truth_lens/face_backends.py.
face = ["insightface>=0.7,<2", "onnxruntime>=1.17,<2"]
face = ["insightface>=0.7,<3", "onnxruntime>=1.17,<2"]
# data-over-sound (ggwave — near-ultrasonic audio chirps, from the llama.cpp
# author) — transmit small payloads between devices as sound. A QR-free pairing
# fallback: the Brain sings a code, the phone catches it out of the air. Opt-in,
Expand Down Expand Up @@ -363,6 +363,11 @@ pytest_add_cli_args_test_selection = [
"src/dreamlayer/tests/test_budgets_cycle_math.py",
"src/dreamlayer/tests/test_flash_safety_unit.py",
"src/dreamlayer/tests/test_flash_safety.py",
# The report dataclasses' __str__ methods, which mutmut could not reach
# until 3.8.0 taught it to mutate methods of decorated classes. Nothing
# executed them: the one `str(rep)` in the suites above is an assert
# MESSAGE, evaluated only when that assert fails. See the file docstring.
"src/dreamlayer/tests/test_report_strings.py",
"src/dreamlayer/tests/test_signer_boundaries.py",
"src/dreamlayer/tests/test_rc2_vault.py",
"src/dreamlayer/tests/test_privacy.py",
Expand Down
34 changes: 34 additions & 0 deletions host-python/src/dreamlayer/tests/test_budgets_cycle_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,40 @@ def test_an_edge_to_a_scene_that_does_not_exist_is_not_an_arc(self):
assert v == [], f"the dangling edge changed the verdict: {v}"


class TestATransitionGoesWhereItSaysNotBackToItself:
"""`target = sid if t.target == SELF else t.target` — the SELF rewrite.

Only a transition that names SELF becomes a self-loop; everything else
keeps the scene it names. mutmut 3.8.0 learned to mutate the condition of
a ternary (changelog: "Mutate the condition of ternary expressions"), and
`(t.target == SELF) or True` — every hop collapsed into a self-loop —
survived every test in this file. A ring of equal scenes cannot see it:
collapsing s0→s1→s0 (2 emits / 2 s) into s0→s0 (1 emit / 1 s) leaves the
rate at 1/s either way.

An ASYMMETRIC cycle sees it immediately. One fast emitting scene and one
slow silent one sustain 1 emit per 10.1 s — comfortably under budget — but
the fast scene alone, read as a self-loop, is 10 emits/s. So the mutant
invents a flood in a figment that does not have one, which on the wearer's
side is a Repertoire refusing to compile something perfectly safe.
"""

def test_a_slow_cycle_is_not_a_flood_because_one_scene_is_fast(self):
fig = Figment(name="t", initial="s0")
fig.add_scene(Scene(
id="s0", duration_sec=0.1, lines=[TextLine("hi", row=1)],
on_timeout=[Transition(target="s1", emit="e")]))
fig.add_scene(Scene(
id="s1", duration_sec=10.0, lines=[TextLine("hi", row=1)],
on_timeout=[Transition(target="s0", emit=None)]))
v: list = []
rate = _cycle_analysis(fig, v)
# 1 emit around a 10.1 s loop. The self-loop reading is 10/s.
assert rate == pytest.approx(1.0 / 10.1, rel=1e-6)
assert rate < EMIT_REFILL_PER_S
assert v == [], f"a safe asymmetric cycle was flagged: {v}"


class TestTheArcScorerPicksTheCycleSArcNotTheLoudestArc:
"""`score = w_e - lo * w_s`, and the minus sign is load-bearing.

Expand Down
120 changes: 120 additions & 0 deletions host-python/src/dreamlayer/tests/test_report_strings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""The safety core's reports say their verdict in words, and nothing read them.

Found 2026-09-17, by mutmut 3.8.0 widening its net: its changelog entry "Fix
methods of decorated classes (for example ``@dataclass``) not being mutated"
(#480/#539) made ``__str__`` on ``FlashReport``, ``BudgetReport`` and
``Violation`` mutable for the first time. Seventeen new mutants appeared, seven
of them ``no tests`` — mutmut's way of saying no selected test ever executes
that line.

The reason is the shape this repository keeps meeting (CLAUDE.md #1), in a new
disguise. ``test_flash_safety.py`` contains, and has always contained:

assert isinstance(rep, FlashReport) and rep.ok, str(rep)

which reads exactly like coverage of ``__str__``. It is not: Python evaluates
an assert's message **only when the assertion fails**, so on every green run
that ``str(rep)`` was never called. The one apparent reader of these methods
was a reader that never ran.

What that left unguarded is not cosmetic. ``FlashReport.__str__`` renders

"FLASH-SAFE" if self.ok else "FLASH RISK"

— the eye-safety verdict, in the words a human actually reads — and a mutant
that inverts it prints FLASH-SAFE over a figment the analyzer rejected.
``BudgetReport.__str__`` has the same conditional head plus the lines that
enumerate the violations, so a mutant dropping them prints a violated report
as a clean one.

These tests execute the three ``__str__`` methods for real and assert both
directions of every verdict, so the mutants die and the text keeps meaning what
it says. They only count because ``test_report_strings.py`` is listed in
``[tool.mutmut] pytest_add_cli_args_test_selection``: a killer suite mutmut is
not told to run cannot kill anything.
"""
from __future__ import annotations

from dreamlayer.reality_compiler.v2.budgets import BudgetReport, Violation
from dreamlayer.reality_compiler.v2.flash_safety import FLASH_LIMIT, FlashReport


class TestTheFlashVerdictReadsCorrectly:
"""``FlashReport.__str__`` is what a human is shown for an eye-safety
call. Both heads are asserted, and each excludes the other: an inverted
conditional has to fail one of them."""

def test_a_safe_report_says_safe_and_never_says_risk(self):
text = str(FlashReport(ok=True, general_hz=1.0, red_hz=0.5))
# `startswith`, not `in`: an `in` check passes on a head mutmut has
# wrapped to "[XXFLASH-SAFEXX]", which is not a verdict anyone can
# read. The head is the one token this whole report exists to carry,
# so it is pinned exactly.
assert text.startswith("[FLASH-SAFE]")
assert "RISK" not in text

def test_an_unsafe_report_says_risk_and_never_says_safe(self):
text = str(FlashReport(ok=False, general_hz=9.0, red_hz=4.0))
assert text.startswith("[FLASH RISK]")
assert "SAFE" not in text

def test_it_reports_the_rates_it_measured_and_the_limit_it_used(self):
text = str(FlashReport(ok=False, general_hz=9.0, red_hz=4.0))
# A report that drops its numbers, or prints one rate in place of the
# other, is a report nobody can act on.
assert "general<=9/s" in text
assert "red<=4/s" in text
assert f"(limit {FLASH_LIMIT:g}/s)" in text


class TestTheBudgetVerdictReadsCorrectly:
def test_a_passing_report_says_ok_and_never_says_violated(self):
text = str(BudgetReport(ok=True, scene_count=3))
assert text.startswith("[BUDGETS OK]")
assert "VIOLATED" not in text

def test_a_failing_report_says_violated(self):
text = str(BudgetReport(ok=False, scene_count=3))
assert text.startswith("[BUDGETS VIOLATED]")

def test_the_violations_are_actually_printed(self):
"""The head alone is not the report. A mutant that drops the
violation lines leaves a failing verdict with nothing under it."""
rep = BudgetReport(
ok=False,
violations=[Violation(code="pulse_rate", message="too fast")],
warnings=[Violation(code="scene_len", message="long scene")],
scene_count=2,
)
lines = str(rep).splitlines()
# Exact lines, not `in`: the separator is mutable too, and a report
# joined on "XX\nXX" still *contains* every violation while rendering
# "XX ✗ pulse_rate: too fast". Pinning the line pins the join.
assert len(lines) == 3
assert lines[0].startswith("[BUDGETS VIOLATED]")
assert lines[1] == " ✗ pulse_rate: too fast"
assert lines[2] == " ⚠ scene_len: long scene"

def test_it_reports_the_proof_numbers(self):
text = str(BudgetReport(ok=True, scene_count=7,
worst_display_hz=30.0, worst_emit_per_sec=2.5))
assert "scenes=7" in text
assert "display<=30Hz" in text
assert "emit<=2.5/s" in text


class TestAViolationNamesItselfAndItsPlace:
def test_code_and_message_both_survive_to_the_text(self):
text = str(Violation(code="pulse_rate", message="12Hz exceeds 3Hz"))
assert text == "pulse_rate: 12Hz exceeds 3Hz"

def test_a_scene_is_named_when_there_is_one(self):
text = str(Violation(code="scene_len", message="too long",
scene="intro"))
assert text == "scene_len [scene intro]: too long"

def test_no_empty_scene_brackets_when_there_is_none(self):
"""The `if self.scene` guard is the whole point: without it a
sceneless violation renders a dangling `[scene None]`."""
assert "[scene" not in str(Violation(code="c", message="m"))
assert "None" not in str(Violation(code="c", message="m"))
Loading